如何使用存储库模式搜索子类?

时间:2011-08-02 22:41:43

标签: c# domain-driven-design repository

我有几个支付交易基类型的子类(信用卡,支票,现金,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等。如果客户端搜索Amount(基础上的param),则该方法应返回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对象映射到域对象,并将它们返回给调用它们的域服务。

2 个答案:

答案 0 :(得分:2)

我会重新考虑你的实体框架模型。

您提供的域模型看起来非常适合每类型表继承

然后,您可以使用LINQ .OfType<T>()方法根据存储库中的泛型类型参数过滤不同的事务类型:

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类型的对象集,则无法“向下转换”为DerivedA类型的对象集以进行过滤。

归结为您愿意妥协的域模型的数量。我自己有类似的问题,我最终使用TPT继承(然后切换到TPH,因为性能更好)。

因此,除了您所提到的内容之外,我不需要了解您的域名,我认为您需要重新思考“每个子类都有自己的存储库,因为每个子类都有自己的属性和获取方式”

您的EF模型中应该有一个存储库,TPT / TPH,以及您的存储库中的“搜索”方法,并且在事务类型上具有泛型类型约束。

如果您使用魔法单一方法,则需要一个讨厌的switch / if语句,并将过滤委托给特定方法。

答案 1 :(得分:1)

在DDD中,Aggregate的主要目标是维护和管理一致性。如果我正确地遵循您的示例,您有两种类型的聚合 - 每种聚合由 CreditCardTransaction CheckTransaction 的聚合根代表。

但是,您所描述的场景与维护一致性无关,因为它不会更改任何数据。您想要实现的是向用户提供一种报告。 因此,我不会尝试弯曲聚合,而是使用 FindTransaction(TransactionQuery)的单一方法引入另一个存储库 - TransactionRepository 。这个repo只存在于一个原因 - 查询数据库中需要向用户显示的数据(是的,它将是一个只读存储库)。

换句话说,当你执行一些实际更改数据的操作而不是仅向用户显示数据的查询时,我建议使用聚合和域实体 - 使用更简单的对象(加上你可以聚合数据而不会弄乱)使用您的域名结构,就像在您的示例中一样。)