从数据库

时间:2017-06-15 14:21:24

标签: c# entity-framework domain-driven-design lazy-loading eager-loading

我的代码中有很多这样的东西(这只是一个简单的例子):

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对象+递归地为对象图中更深的导航属性执行此操作。但这样做太过分了,因为每次都会提到许多不需要的东西。

我认为这是我必须做出的权衡,但我只需要在大型项目中遇到此类问题的人提出建议,您认为哪种解决方案长期最易维护?

2 个答案:

答案 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();
    }
...