c# - Entity Framework 5 性能问题

标签 c# sql asp.net-mvc performance entity-framework

现在我正在处理一个非常复杂的数据库。我们的对象模型旨在映射到数据库。我们将 EF 5 与手动生成的 POCO 类结合使用。

一切正常,但有些人提示性能。我从来没有遇到过 EF 的性能问题,所以我想知道这次我是不是做错了什么,或者问题可能出在其他地方。

主查询可能由动态参数组成。我有几个 if 和 switch block ,它们在概念上是这样的:

if (parameter != null) { query = query.Where(c => c.Field == parameter); }

另外,对于一些复杂的 And/Or 组合,我使用了 Albahari 的 LinqKit 扩展。

查询是针对“订单”的大表,其中包含多年的数据。不过,平均使用时间为 2 个月的范围过滤器。

现在,当主查询组成时,它会使用 Skip/Take 组合进行分页,其中 Take 设置为 10 个元素。

所有这些之后,IQueryable 层层发送,到达使用 Automapper 的 MVC 层。

在这里,当 Automapper 开始迭代(因此查询真正执行)时,它会调用一堆导航属性,这些导航属性有自己的导航属性等等。如果要包含超过 3 或 4 个不同的实体,根据 EF 的建议,一切都设置为延迟加载以避免急切加载。我的场景是这样的:

  • 订单(最多 10 个)
    • Order 下的许多导航属性
      • 其中一些在它们下面有其他导航(本地化实体)
    • 订单详情(每个订单有许多订单详情)
      • 每个订单详细信息下的许多导航属性
        • 其中一些在它们下面有其他导航(本地化实体)

这很容易导致对单个呈现的“页面”进行总共 300 多次查询。这些查询中的每一个都非常快,只需几毫秒即可运行,但仍然存在两个主要问题:

  • 延迟加载的属性是按顺序调用的,不是并行的,因此需要更多的时间
  • 作为上一点的结果,每个查询之间有一些死时间,因为数据库必须为每个查询接收 sql、运行它、返回它等等。

只是为了看看它是如何进行的,我尝试使用预先加载进行相同的查询,正如我所预测的那样,这完全是一场灾难,翻译的 sql 超过 7K 行(是的,七千行)而且速度更慢总体而言。

现在我不愿意认为 EF 和 Linq 不是这种情况的正确选择。有人说,如果他们要编写一个存储过程来获取所有需要的数据,它的运行速度会快几十倍。我不相信这是真的,我们会失去所有相关实体的自动物化。

我想到了一些可以改进的地方,例如:

  • 拆分表以减少所选列
  • 关闭对象跟踪,因为此场景是只读的(有未跟踪的实体)

综上所述,主要的提示是结果页面(在 MVC 4 中完成)呈现速度太慢,经过一些诊断后似乎都是“服务器时间”而不是“网络时间”,大约从8 到 12 秒的服务器时间。

根据我的经验,这不应该发生。我想知道我是否以错误的方式处理了这个查询需求,或者我是否必须将注意力转移到其他事情上(可能是配置错误的 IIS 服务器,或者其他我真的一无所知的事情)。不用说,数据库的索引没问题,我们的 dba 非常仔细地检查过。

因此,如果有人有任何我遗漏的提示、建议、最佳实践,或者可以告诉我在这种情况下将 EF 与延迟加载结合使用是完全错误的……不客气。

最佳答案

对于产生大量分层数据的非常复杂的查询,如果您采用正确的方法,存储过程通常不会帮助您在性能方面优于 LINQ/EF。正如您所指出的,EF 的两个“开箱即用”选项(延迟加载和急切加载)在这种情况下效果不佳。但是,仍然有几种好的方法可以对此进行优化:

(1) 与其将一堆实体读入内存,然后通过自动映射器进行映射,不如在可能的情况下直接在查询中执行“自动映射”。例如:

var mapped = myOrdersQuery.Select(o => new OrderInfo { Order = o, DetailCount = o.Details.Count, ... })
    // by deferring the load until here, we can bring only the information we actually need 
    // into memory with a single query
    .ToList();

如果您只需要复杂层次结构中的一部分字段,则此方法非常有效。此外,如果您需要返回比平面表格数据更复杂的内容,EF 选择分层数据的能力使这比使用存储过程更容易。

(2) 手动运行多个 LINQ 查询并将结果组装到内存中。例如:

// read with AsNoTracking() since we'll be manually setting associations
var myOrders = myOrdersQuery.AsNoTracking().ToList();
var orderIds = myOrders.Select(o => o.Id);
var myDetails = context.Details.Where(d => orderIds.Contains(d.OrderId)).ToLookup(d => d.OrderId);
// reassemble in memory
myOrders.ForEach(o => o.Details = myDetails[o.Id].ToList());

当您需要所有数据并且仍希望尽可能多地利用 EF 物化时,这种方法非常有效。请注意,在大多数情况下,存储过程方法不会比这更好(它使用原始 SQL,因此它必须运行多个表格查询)但不能重用您已经在 LINQ 中编写的逻辑。

(3) 使用 Include() 手动控制哪些关联是预先加载的。这可以与 #2 结合使用,以利用 EF 加载某些关联,同时让您灵活地手动加载其他关联。

关于c# - Entity Framework 5 性能问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16741956/

相关文章:

c# - 入口后面带有 Azure OAuth 的 ASP.NET Core MVC 进入无限登录循环

c# - 在 Visual Studio 2015 中安装 Entity Framework 失败

c# - 如何将 RTF 文件转换为 pdf 文件?

c# - C# 中的 C 回调函数问题 - 如何将值传递给指针?

mysql - 尝试将范围选定的数据从一个数据库复制到另一个数据库;我可以加快这个查询吗?

asp.net-mvc - 在调试 MVC View 时检查 HTML 输出

c# - 标准命名空间中是否有黄金比例的定义值?

C#,html-agility-pack 获取不在标签内的文本

java - WHERE 子句问题

mysql - SQL Multiple Joins 多个where