计算字段可提高性能但需要维护(EF)

时间:2017-04-25 21:47:47

标签: c# sql-server performance entity-framework

我有这个" 1到N"模型:

class Reception
{
    public int ReceptionId { get; set; }
    public string Code { get; set; }
    public virtual List<Item> Items { get; set; }
}

class Item
{
    public int ItemId { get; set; }
    public string Code { get; set; }
    public int Quantity { get; set; }
    public int ReceptionId { get; set; }
    public virtual Reception Reception { get; set; }
}

这个动作,api/receptions/list

public JsonResult List() 
{
     return dbContext.Receptions
        .Select(e => new 
         {
             code = e.Code,
             itemsCount = e.Items.Count,
             quantity = e.Items.Sum(i => i.Quantity)

         }).ToList();
}

返回接收列表及其项目数:

[
    {code:"1231",itemsCount:10,quantity:30},
    {code:"1232",itemsCount:5,quantity:70},
    {code:"1234",itemsCount:30,quantity:600},
    ...
]

这工作正常,但我有太多ReceptionItem因此查询花了太长时间......

所以我想通过向Reception添加一些持久字段来加快速度:

class Reception
{
    public int ReceptionId { get; set; }
    public string Code { get; set; }
    public virtual List<Item> Items { get; set; }
    public int ItemsCount { get; set; } // Persisted
    public int Quantity { get; set; } // Persisted
}

通过此更改,查询最终成为:

public JsonResult List() 
{
     return dbContext.Receptions
        .Select(e => new 
         {
             code = e.Code,
             itemsCount = e.ItemsCount,
             quantity = e.Quantity

         }).ToList();
}

我的问题是:
维持这两个领域的最佳方法是什么?
我会获得表现,但现在我需要更加谨慎地创建Item

今天可以创建,编辑和删除Item

  • api/items/create?receptionId=...
  • api/items/edit?itemId=...
  • api/items/delete?itemId=...

我还有一个通过Excel导入接收的工具:

  • api/items/createBulk?...

也许明天我会有更多方法来创建Item,所以问题是如何确保这两个新字段ItemsCountQuantity将会总是最新的?

我应该像这样在Reception内创建方法吗?

class Reception
{
    ...

    public void UpdateMaintainedFields() 
    {
        this.Quantity = this.Items.Sum(e => e.Quantity);
        this.ItemsCount = this.Items.Count();
    }
}    

然后记住从之前的所有网址中调用它? (items/createitems/edit,...)

或者我应该在数据库中有一个存储过程吗?

通常的做法是什么?我知道有calculated columns但这些引用了同一类的字段。还有indexed views,但我不确定它们是否适用于这样的场景。

3 个答案:

答案 0 :(得分:1)

在您的代码中,我觉得您没有业务逻辑层,并且所有内容都在控制器中实现,这会导致问题,当您有不同的方式时(似乎,您意味着一个不同的控制器)你必须再次实现这个逻辑,很容易忘记,如果你不忘记,你可能忘记以后维护。

所以我建议有一个业务逻辑层(比如添加新项目),并从你想要创建项目的控制器中使用它。

我还建议您按照要求编写UpdateMaintainedFields函数,但在添加项目之后在业务逻辑层中调用它,而不是在控制器中调用它!

如果您可以接受不能编写单元测试的话,也可以在数据库上编写逻辑(触发器)。

答案 1 :(得分:0)

假设在SQLServer中使用正确的执行计划无法改进原始查询,更新这些字段的方法是通过DB中的触发器。当发生插入时(如果持久化字段根据数据发生更改,则可能更新),那么当该表发生插入时,将运行触发器。它将负责使用新值更新所有行。

显然,您的插入性能会下降,但您的查询性能将是简单索引和单行读取。显然,如果您要返回表的一个子集,则无法使用此技巧,因为所有数量都将被修复。

另一种方法是将计数和数量总和保存在单独的表中,或保存在一个虚拟行中,该行包含总计数量作为其数量条目。 YMMV。

PS我讨厌在C#代码中解决了什么是SQL问题!学习SQL并直接在数据库中运行您需要的查询,这将向您展示更多关于您所需要的性能和结构的信息,而不是让EF参与其中。 / rant:)

答案 2 :(得分:0)

您希望以双重方式存储相同的信息,这可能会导致不一致。作为灵感,索引也在复制数据。你如何更新它们?你没有。它完全透明。我会在这里推荐相同的方法。

制作总和表,由触发器维护。该表不会包含在任何datacontext模式中,只有通过不可更新的视图或存储过程才能读取它。它的名字应该引起,没有人应该直接触摸这个表。

您现在可以从各种框架访问您的数据,而不必担心更新任何内容。数据库将确保预先计算的总和始终是正确的,只要您不自行写入总和表即可。实际上,您可以随时添加或删除此表,甚至没有应用程序会注意到。