简体   繁体   English

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

[英]How to search sub-classes using the repository pattern?

I have several sub-classes of a payment transaction base type (credit card, check, cash, billMeLater, etc). 我有几个支付交易基类型的子类(信用卡,支票,现金,billMeLater等)。 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. 使用SearchTransactions()方法的WCF层。

  • Domain layer with a SearchTransactions() method. 具有SearchTransactions()方法的域层。

  • Data layer with multiple repositories, each with a Search() method (payment type specific). 具有多个存储库的数据层,每个存储库都具有Search()方法(特定于付款类型)。

  • Database (via EF) with typical DBA-required unintelligible mess 数据库(通过EF)具有典型的DBA所需的无法理解的混乱

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. 因此,客户端应该能够从一种方法搜索CardNumber,RoutingNumber,Amount等。 If the client searches on Amount (param on the base), the method should return both CreditCardTransaction's and CheckTransaction's. 如果客户端搜索Amount(基础上的param),则该方法应返回CreditCardTransaction和CheckTransaction。 If the client searches on BankAccountNumber, it should only return CheckTransactions. 如果客户端在BankAccountNumber上搜索,它应该只返回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. 因此,如果客户端想要搜索Amount等常见属性,则会传递TransactionSearchCriteriaBase。 If they pass in the CreditCardTransactionSearchCriteria, they end up searching credit card transactions. 如果他们传入CreditCardTransactionSearchCriteria,他们最终会搜索信用卡交易。 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. 我用一个存储库工厂替换了几乎不可避免的switch / if块,该工厂根据传入工厂的条件对象的类型返回了一个适用的存储库列表。

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. 由于我们在EF 3.5中执行此操作,因此我们没有POCO支持。 So, we don't consider the objects that EF generates as domain objects. 因此,我们不会将EF生成的对象视为域对象。 Our repositories map the various disconnected EF objects to domain objects and returns them to the domain services that call them. 我们的存储库将各种断开连接的EF对象映射到域对象,并将它们返回给调用它们的域服务。

I would rethink your Entity Framework model. 我会重新考虑您的Entity Framework模型。

That domain model you have provided looks perfect for Table-per-type Inheritance . 您提供的域模型看起来非常适合Table-per-type继承

Then you could use the LINQ .OfType<T>() method to filter different transaction types, based on a generic type parameter on your Repository: 然后,您可以使用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);
   }
}

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 因此,客户端应该能够从一种方法搜索CardNumber,RoutingNumber,Amount等

I don't think that's possible with one method, well certainly not without some kind of if/switch statement. 我不认为使用一种方法是可能的,当然不是没有某种if / switch语句。

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. 基本上,如果您处理类型为AbstractA的对象集,则无法“向下转换”为DerivedA类型的对象集以进行过滤。

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). 我自己有类似的问题,我最终使用TPT继承(然后切换到TPH,因为性能更好)。

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. 看起来你应该在你的EF模型中有一个存储库,TPT / TPH,以及你的Repository上的“搜索”方法,它对事务类型有一个通用的类型约束。

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. 如果您使用魔法单一方法,则需要一个讨厌的switch / if语句,并将过滤委托给特定方法。

In DDD the main goal of an Aggregate is to maintain and manage consistency. 在DDD中,Aggregate的主要目标是维护和管理一致性。 If I follow your example correctly, you have two types of Aggregates - each one represented by Aggregate Roots of CreditCardTransaction and CheckTransaction . 如果我正确地遵循您的示例,您有两种类型的聚合 - 每个聚合由CreditCardTransactionCheckTransaction的聚合根代表。

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) . 因此,我不会尝试弯曲聚合,而是使用FindTransaction(TransactionQuery)的单个方法引入另一个存储库 - TransactionRepository 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). 这个repo只存在于一个原因 - 查询数据库中需要向用户显示的数据(是的,它将是一个只读存储库)。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM