【问题标题】:How to search sub-classes using the repository pattern?如何使用存储库模式搜索子类?
【发布时间】:2011-08-02 22:41:43
【问题描述】:

我有几个支付交易基础类型的子类(信用卡、支票、现金、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(base 上的参数),该方法应该返回 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 对象映射到域对象,并将它们返回给调用它们的域服务。

【问题讨论】:

    标签: c# domain-driven-design repository


    【解决方案1】:

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

    您提供的域模型看起来非常适合Table-per-type Inheritance。

    然后您可以使用 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 语句,并将过滤委托给特定方法。

    【讨论】:

    • 我想出的一个我认为还不错的解决方案是拥有一个事务存储库工厂,它为我提供了一个适用事务存储库的列表。然后我可以搜索每个 repo 并将结果添加到 List。想法?
    • 客户端需要返回多种交易类型的“神奇”方法。我宁愿提供多个方法调用,因为这样我就可以使它全部通用并完成它。不幸的是,我被“魔法”困住了。
    • @Byron - 你能用来自客户的一些示例电话更新你的问题吗?
    • 啊,还有别的东西.. 你注意到我对 DBA 所需的难以理解的混乱的暗示了吗?每种类型尝试 5 个表。
    • @Byron - 我感觉到你的痛苦。您应该看到我们的 EF 模型。基本上不可能不跨越聚合边界。
    【解决方案2】:

    在 DDD 中,聚合的主要目标是维护和管理一致性。如果我正确地按照您的示例进行操作,那么您有两种类型的聚合 - 每种类型由 CreditCardTransaction 和 CheckTransaction 的聚合根表示。

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

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

    【讨论】:

      猜你喜欢
      • 2014-09-11
      • 2021-10-10
      • 2019-04-13
      • 1970-01-01
      • 2010-12-02
      • 2012-03-28
      • 2011-07-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多