我的代码中有很多这样的东西(这只是一个简单的例子):
var invoice = context.Invoices
.ForId(invoiceId)
.Include(i => i.Payments)
.Include(i => i.OrderLines)
.First();
Invoice
将字段UnpaidAmount
简化为
public double UnpaidAmount
{
get
{
return OrderLines.Sum(ol => ol.Amount) -
Payments.Sum(p => p.Amount);
}
}
现在,项目中经常发生的事情是,如果有人需要修改UnpaidAmount
逻辑,例如:
return OrderLines.Sum(ol => ol.Amount) -
Payments.Sum(p => p.Amount) -
CreditNotes.Sum(cn => cn.Amount);
然后,他们需要在项目中找到使用UnpaidAmount
的所有位置,并在获取CreditNotes
时将Include
添加到Invoice
。人们常常忘记这一点,在这种情况下,Sum
CreditNotes
实际上会在空集合上调用,而不是从数据库中获取。
这变得非常错误并且难以通过项目维护。
替代方案是要么丢失LazyLoading
,所以我们不必考虑包含在任何地方,但是这可能会导致性能问题,这些问题可能会在开发过程中检测到,但是后来在生产时会被提取的记录数量变大。
或者有一个方法可以获取具有所有导航属性的Invoice
对象+递归地为对象图中更深的导航属性执行此操作。但这样做太过分了,因为每次都会提到许多不需要的东西。
我认为这是我必须做出的权衡,但我只需要在大型项目中遇到此类问题的人提出建议,您认为哪种解决方案长期最易维护?
答案 0 :(得分:1)
然后他们需要在项目中找到使用UnpaidAmount的地方,并在获取Invoice时将CreditNotes添加到Include。
为什么这个责任不仅限于一个地方?
替代方案是要么丢失LazyLoading所以我们不必考虑包含在任何地方,但是这可能会导致性能问题,这些问题可能在开发过程中检测到,但是后来在生产时会被提取的记录数量增加。
或者有一个方法可以获取具有所有导航属性的Invoice对象+递归地为对象图中更深的导航属性执行此操作。但这样做太过分了,因为每次都会提到许多不需要的东西。
我认为您正在寻找的是存储库模式。
基本理念:
您有一个或多个role interfaces消费者可以用来描述他们将如何使用发票(或者,发票的视图满足他们的需求)。
您提供这些角色的实施,这些角色对发票数据的存储方式有着共同的理解。这意味着当您将CreditNotes引入模型时,只有一个地方需要更改。
您使用管道(依赖注入或其他)来确保为每个角色提供正确的实现。
简而言之,您在消费者和供应商之间建立了明确的合同;消费者描述他们需要的东西,供应商可以自由选择满足这种需求的方式。
Udi Dahan在当天写了一些与这个想法相关的帖子。
答案 1 :(得分:0)
可以提供介绍某种商店,例如发票。它将在构造函数中获取DbContext,并提供获取和保存发票所需的所有方法。因此,您只有一个地方可以编写和修改查询。
class Invoices
{
public Invoices(DbContext dbContext){....}
public Invoice GetInvoiceById(int invoiceId)
{
return this.dbContext.Invoices
.ForId(invoiceId)
.Include(i => i.Payments)
.Include(i => i.OrderLines)
.FirstOrDefault();
}
...