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

发布于 2024-11-27 11:32:17 字数 2263 浏览 5 评论 0原文

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

I have several sub-classes of a payment transaction base type (credit card, check, cash, billMeLater, etc). Each sub-class has it's own repository since each has its own properties and way of being fetched. I need to be able to search payment transactions and I've gone down a road that has ended up causing more headaches. The trick is that sometimes the client needs to search on common properties like amount or customer name AND sometimes the client needs to search on payment-type-specific properties like credit card number or bank routing number... but the domain-level search method needs to be able to return all types of the transaction base.

I have the following levels of abstraction:

  • WCF Layer with a SearchTransactions() method.

  • Domain layer with a SearchTransactions() method.

  • Data layer with multiple repositories, each with a Search() method
    (payment type specific).

  • Database (via EF) with typical DBA-required unintelligible mess

How would you do this?

EDIT:

For added context, here are some examples of possible payment types and their base:

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; }
}

So, the client should be able to search on CardNumber, RoutingNumber, Amount, etc., all from one method. If the client searches on Amount (param on the base), the method should return both CreditCardTransaction's and CheckTransaction's. If the client searches on BankAccountNumber, it should only return CheckTransactions.

CLIENT REQUIREMENTS and EARLIER SOLUTION:

The client requires that there be one call to search multiple transaction types. The client doesn't care much what they pass in as arguments unless they require more than one call to cover all payment types. One idea I had earlier on was to use classes that carried search criteria. Then I could have sub-classes of the search criteria classes that searched the more specific payment type properties. Like this:

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; }
}

So, if the client wants to search on common properties like Amount, they pass in the TransactionSearchCriteriaBase. If they pass in the CreditCardTransactionSearchCriteria, they end up searching credit card transactions. For instance:

var listOfTransactions = _transactionService.Search(new CreditCardTransactionSearchCriteria{ Amount = 10m, CardNumber = "1111" });

I replaced the almost-inevitable switch/if block with a repository factory that handed back a list of applicable repositories based on the type of criteria object passed in to the factory.

The rabbit hole goes deeper. I'd like less of a rabbit hole.

ANOTHER PIECE OF INFO:

Since we're doing this in EF 3.5, we don't have POCO support. So, we don't consider the objects that EF generates as domain objects. Our repositories map the various disconnected EF objects to domain objects and returns them to the domain services that call them.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

忆离笙 2024-12-04 11:32:17

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

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

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

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 语句,并将过滤委托给特定的方法。

I would rethink your Entity Framework model.

That domain model you have provided looks perfect for Table-per-type Inheritance.

Then you could use the LINQ .OfType<T>() method to filter different transaction types, based on a generic type parameter on your 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);
   }
}

Then you could do this:

var ccTran = _repository.Find<CreditCardTransaction>(x => x.TransactionId == 1);
var cTran = _repository.Find<CheckTransaction>(x => x.TransactionId == 2);

As for your questions:

So, the client should be able to search on CardNumber, RoutingNumber, Amount, etc., all from one method

I don't think that's possible with one method, well certainly not without some kind of if/switch statement.

The kicker is the filtering - it all comes down to the signature of the repository method - what is provided, what generic constraints, etc.

If your saying each sub-type has it's own repository, then does it really make sense to have a single method which serves all three repositories? Where should this magical method live?

So overall, i think you've reached the point many have reached, where your domain is fighting against Entity Framework.

Basically, if your working on an objectset of type AbstractA, you can't "downcast" to a objectset of type DerivedA in order to do the filtering.

It comes down to how much of your domain model your willing to compromise. I myself had a similar problem, and i ended up using TPT inheritance (then switching to TPH because the performance was better).

So without knowing too much about your domain other than what you've mentioned, i think you need to re-think the statement "Each sub-class has it's own repository since each has its own properties and way of being fetched".

It looks like you should have a single repository, TPT/TPH in your EF model, and the "search" method on your Repository with a generic type constraint on the transaction type.

If your gun-ho on having the magic single method, you would need a nasty switch/if statement, and delegate the filtering to a specific method.

一身骄傲 2024-12-04 11:32:17

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

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

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

In DDD the main goal of an Aggregate is to maintain and manage consistency. If I follow your example correctly, you have two types of Aggregates - each one represented by Aggregate Roots of CreditCardTransaction and CheckTransaction.

However the scenario that you've described has nothing to do with maintaining consistency as it doesn't change any data. What you want to achieve is to provide a kind of report to a user.
So instead of trying to bend Aggregates, I would introduce another repository - TransactionRepository with a single method of FindTransaction(TransactionQuery). This repo would exist for a single reason only - to query your database for the data you require to show to user (yes, it would be a readonly repository).

In other words I would suggest using your Aggregates and domain entities when you perform some operations that actually change the data but not for queries that only show the data to users - use simpler objects instead (plus you can aggregate the data without messing with your domain structure, just like in your example).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文