如何使用存储库模式搜索子类
本文关键字:模式搜索 子类 存储 何使用 | 更新日期: 2023-09-27 18:04:32
我有几个支付事务基类型的子类(信用卡、支票、现金、billMeLater等)。每个子类都有自己的存储库,因为每个子类都有自己的属性和获取方式。我需要能够搜索支付交易,我已经走了一条路,最终导致更多的头痛。诀窍在于,有时客户需要搜索常见的属性,如金额或客户名称,有时客户需要搜索支付类型特定的属性,如信用卡号码或银行路由号码……但是域级搜索方法需要能够返回所有类型的事务基。
我有以下抽象层次:
-
使用SearchTransactions()方法的WCF层
-
使用SearchTransactions()方法的域层
-
具有多个存储库的数据层,每个存储库都有Search()方法
-
数据库(通过EF)与典型的dba所需的难以理解的混乱
你会怎么做?
编辑:为了增加上下文,这里有一些可能的支付类型及其基础的例子:
public abstract class TransactionBase
{
public int TransactionId { get; set; }
public decimal Amount { get; set; }
}
public class CreditCardTransaction : TransactionBase
{
public string CardNumber { get; set; }
public int ExpirationMonth { get; set; }
public int ExpirationYear { get; set; }
}
public class CheckTransaction : TransactionBase
{
public string BankAccountNumber { get; set; }
public string RoutingNumber { get; set; }
}
因此,客户端应该能够从一个方法搜索CardNumber、RoutingNumber、Amount等。如果客户端搜索金额(基数上的参数),该方法应该返回CreditCardTransaction和CheckTransaction的值。如果客户端搜索BankAccountNumber,它应该只返回CheckTransactions。
客户端需求和早期解决方案:
客户端要求有一个调用来搜索多个事务类型。客户端不太关心传入的参数,除非需要不止一次调用来覆盖所有的支付类型。我之前的一个想法是使用带有搜索条件的类。然后我可以有搜索条件类的子类来搜索更具体的支付类型属性。这样的:
public class TransactionSearchCriteriaBase
{
public int TransactionId { get; set; }
public decimal Amount { get; set; }
}
public class CreditCardTransactionSearchCriteria : TransactionSearchCriteriaBase
{
public string CardNumber { get; set; }
public int ExpirationMonth { get; set; }
public int ExpirationYear { get; set; }
}
因此,如果客户端想要搜索像Amount这样的常见属性,它们将传入TransactionSearchCriteriaBase。如果它们传入CreditCardTransactionSearchCriteria,它们最终将搜索信用卡交易。例如:
var listOfTransactions = _transactionService.Search(new CreditCardTransactionSearchCriteria{ Amount = 10m, CardNumber = "1111" });
我用一个存储库工厂替换了几乎不可避免的switch/if块,该工厂根据传入工厂的标准对象的类型返回一组适用的存储库。
兔子洞越钻越深。我不喜欢兔子洞。
另一条信息:
因为我们在EF 3.5中这样做,所以我们没有POCO支持。因此,我们不认为EF生成的对象是领域对象。我们的存储库将各种断开连接的EF对象映射到域对象,并将它们返回给调用它们的域服务。
我会重新考虑你的实体框架模型
您所提供的域模型看起来非常适合每类型表继承。
然后您可以使用LINQ .OfType<T>()
方法来过滤不同的事务类型,基于您的Repository上的泛型类型参数:
public class TransactionRepository : IRepository<Transaction>
{
public TTransaction Find<TTransaction>(int transactionId)
where TTransaction : TransactionBase, new()
{
return _ctx.Transactions.OfType<TTransaction>().SingleOrDefault(tran => tran.TransactionId == transactionId);
}
}
那么你可以这样做:
var ccTran = _repository.Find<CreditCardTransaction>(x => x.TransactionId == 1);
var cTran = _repository.Find<CheckTransaction>(x => x.TransactionId == 2);
关于你的问题:
因此,客户端应该能够搜索CardNumber, RoutingNumber, Amount等,所有这些都来自一个方法
我不认为这是可能的一个方法,当然不可能没有某种if/switch语句。
关键是过滤——这一切都归结为存储库方法的签名——提供了什么,有什么通用约束,等等。
如果你说每个子类型都有自己的存储库,那么用一个方法服务所有三个存储库真的有意义吗?这种神奇的方法应该放在哪里呢?
所以总的来说,我认为你已经达到了很多人已经达到的地步,你的领域正在与实体框架作斗争。
基本上,如果你处理的是AbstractA类型的对象集,你就不能为了进行过滤而"下cast"到DerivedA类型的对象集。
这取决于你愿意在多大程度上妥协你的领域模型。我自己也有类似的问题,我最终使用TPT继承(然后切换到TPH,因为性能更好)。
所以不知道太多关于你的领域,除了你提到的,我认为你需要重新思考语句"每个子类都有自己的存储库,因为每个子类都有自己的属性和获取方式"。
看起来你应该有一个单一的存储库,在你的EF模型中有TPT/TPH,在你的存储库上的"搜索"方法对事务类型有一个泛型约束。
如果你想拥有一个神奇的单一方法,你将需要一个讨厌的switch/If语句,并将过滤委托给一个特定的方法。
在DDD中,聚合的主要目标是维护和管理一致性。如果我正确地遵循了您的示例,那么您有两种类型的聚合——每一种都由CreditCardTransaction和CheckTransaction的聚合根表示。
然而,你所描述的场景与维护一致性无关,因为它没有改变任何数据。您想要实现的是向用户提供一种报告。因此,我不会试图改变聚合,而是引入另一个存储库——TransactionRepository,它带有一个方法FindTransaction(TransactionQuery)。这个repo的存在只有一个原因——查询数据库中需要显示给用户的数据(是的,它将是一个只读存储库)。
换句话说,我建议在执行一些实际更改数据的操作时使用您的聚合和域实体,而不是用于仅向用户显示数据的查询-使用更简单的对象代替(加上您可以聚合数据而不会混淆您的域结构,就像在您的示例中一样)。