【问题标题】:Calculated fields that improve performance but need to be maintained (EF)提高性能但需要维护的计算字段 (EF)
【发布时间】:2017-04-25 21:47:46
【问题描述】:

我有这个“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's

今天可以创建、编辑和删除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();
    }
}    

然后记得从所有以前的 URL 调用它吗? (items/create, items/edit, ...)

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

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

【问题讨论】:

  • 您可能想要查看 EF 正在生成的查询,并查看它为这些查询执行的查询计划是什么。您也许可以继续使用当前的方法,但您只需要添加适当的一两个索引。
  • 我很难想到这不会是高效的。你看过 SQL 探查器吗?您是否碰巧发出多个查询?这怎么可能编译?列表不能与 ActionResult 争吵......
  • 当前方法已经被很好的索引(列有索引ReceptionId,表Items

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


【解决方案1】:

从您的代码看来,您没有业务逻辑层,并且所有内容都在控制器中实现,这会给您带来问题,即当您有不同的方式时(看起来,您表示不同的控制器)你必须重新实现这个逻辑,很容易忘记,如果你不忘记,你可能会忘记以后维护。

因此,我建议为业务逻辑设置一个层(例如添加新项目),并从要创建项目的控制器中使用它。

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

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

【讨论】:

  • 今天我所有的控制器都可以访问dbContext 对象。这意味着他们可以读取(例如dbContext.Items.Where...)和写入(例如dbContext.Items.Add(...))。所以你的建议是我从我的所有控制器中删除dbContext,而是添加类似businessLogic 实例的东西
  • @sports 是的,类似的东西。您可以在解决方案中创建一个不同的项目并从 Web 项目中引用它。创建一个类“ItemService”,它将包含与创建/更新/删除...项目实体相关的所有逻辑。在 ItemService 中通过构造函数注入 dbContext。
【解决方案2】:

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

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

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


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

【讨论】:

    【解决方案3】:

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

    制作总和表,由触发器维护。该表不会包含在任何数据上下文模式中,读取它的唯一方法是通过不可更新的视图或存储过程。它的名字应该让人想起任何人都不应该直接接触这张桌子。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-03-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-06
      • 1970-01-01
      相关资源
      最近更新 更多