优化entity framework查询

我正在尝试在我自己的时间制作一个stackoverflow克隆来学习EF6和MVC5,我目前正在使用OWin进行身份validation。

当我有50-60个问题的时候一切正常,我使用Red Gate数据生成器并尝试将其增加到100万个问题,并且几千个子表行没有任何关系只是为了“压力”ORM一点。 这是linq的样子

var query = ctx.Questions .AsNoTracking() //read-only performance boost.. http://visualstudiomagazine.com/articles/2010/06/24/five-tips-linq-to-sql.aspx .Include("Attachments") .Include("Location") .Include("CreatedBy") //IdentityUser .Include("Tags") .Include("Upvotes") .Include("Upvotes.CreatedBy") .Include("Downvotes") .Include("Downvotes.CreatedBy") .AsQueryable(); if (string.IsNullOrEmpty(sort)) //default { query = query.OrderByDescending(x => x.CreatedDate); } else { sort = sort.ToLower(); if (sort == "latest") { query = query.OrderByDescending(x => x.CreatedDate); } else if (sort == "popular") { //most viewed query = query.OrderByDescending(x => x.ViewCount); } } var complaints = query.Skip(skipCount) .Take(pageSize) .ToList(); //makes an evaluation.. 

毋庸置疑,我正在获得SQL超时,并在安装Miniprofiler之后 ,看看生成的sql语句,这是一个可怕的几百行。

我知道我加入/包括太多桌子,但现实生活中有多少项目,我们只需加入1或2个桌子? 可能有些情况我们必须用数百万行来做这么多连接,是唯一的存储过程吗?

如果是这样的话,EF本身是否只适合小规模项目?

您遇到的问题很可能是笛卡尔积 。

仅基于一些样本数据:

 var query = ctx.Questions // 50 .Include("Attachments") // 20 .Include("Location") // 10 .Include("CreatedBy") // 5 .Include("Tags") // 5 .Include("Upvotes") // 5 .Include("Upvotes.CreatedBy") // 5 .Include("Downvotes") // 5 .Include("Downvotes.CreatedBy") // 5 // Where Blah // Order By Blah 

这将返回向上的行数

 50 x 20 x 10 x 5 x 5 x 5 x 5 x 5 x 5 = 156,250,000 

严重的……这是要返回的INSANE行数。

如果您遇到此问题,您真的有两个选择:

第一:简单的方法,依靠entity framework在进入上下文时自动连接模型。 然后,使用实体AsNoTracking()并处理上下文。

 // Continuing with the query above: var questions = query.Select(q => q); var attachments = query.Select(q => q.Attachments); var locations = query.Select(q => q.Locations); 

这将为每个表发出一个请求,但不是156百万行,而是只下载110行。 但很酷的部分是它们都连接在EF Context Cache内存中,所以现在questions变量已完全填充。

第二步: 创建一个返回多个表并让EF实现类的存储过程 。

我没有看到你的LINQ查询有任何明显错误( .AsQueryable()不应该是强制性的,但是如果删除它就不会改变任何东西)。 当然, 不要包含不必要的导航属性 (每个都添加一个SQL JOIN ),但如果一切都是必需的,那就应该没问题

现在,当C#代码看起来没问题时,是时候看看生成的SQL代码了。 正如您所做的那样,第一步是检索执行的SQL查询。 有.Net方式 ,对于SQL Server我个人总是启动SQL Server分析会话 。

获得SQL查询后,尝试直接对数据库执行它,并且不要忘记包含实际的执行计划 。 这将在大多数情况下准确显示查询的哪个部分。 它甚至会指示您是否有明显的缺失索引。

现在问题是,如果您添加所有这些索引,您的SQL Server会告诉您它们丢失了吗? 不必要。 例如,请参阅不要盲目地创建缺少的索引 。 您必须选择应添加哪些索引,哪些不应该添加。

由于代码优先方法为您创建索引,我假设它们只是主键和外键的索引。 这是一个好的开始,但这还不够。 我不知道表中的行数,但只有你可以添加的明显索引(没有代码生成工具可以这样做,因为它与业务查询相关),例如CreatedDate列上的索引,因为您按此值订购商品。 如果不这样做,SQL Server将不得不在1M行上执行表扫描 ,这在性能方面当然是灾难性的。

所以:

  • 如果可以,尝试删除一些Include
  • 查看实际的执行计划,以查看查询中的性能问题
  • 只添加有意义的缺失索引,具体取决于您从数据库中获取/过滤数据的方式

如您所知,Include方法会生成可怕的SQL。

免责声明 :我是项目Entity Framework Plus(EF +)的所有者

EF + Query IncludeOptimized方法允许优化生成的SQL,就像EF Core一样。

生成多个SQL(每个包含一个SQL),而不是生成一个可怕的SQL。 此function也作为奖励,它允许过滤相关实体。

文档: EF + Query IncludeOptimized

 var query = ctx.Questions .AsNoTracking() .IncludeOptimized(x => x.Attachments) .IncludeOptimized(x => x.Location) .IncludeOptimized(x => x.CreatedBy) //IdentityUser .IncludeOptimized(x => x.Tags) .IncludeOptimized(x => x.Upvotes) .IncludeOptimized(x => x.Upvotes.Select(y => y.CreatedBy)) .IncludeOptimized(x => x.Downvotes) .IncludeOptimized(x => x.Downvotes.Select(y => y.CreatedBy)) .AsQueryable(); 

请查看Microsoft的本文档的 8.2.2部分:

8.2.2多个包含的性能问题

当我们听到涉及服务器响应时间问题的性能问题时,问题的根源通常是具有多个Include语句的查询。 虽然在查询中包含相关实体是强大的,但了解幕后发生的事情非常重要。

具有多个Include语句的查询需要相对较长的时间才能通过内部计划编译器来生成store命令。 大部分时间花在尝试优化生成的查询上。 生成的store命令将包含每个Include的外部联接或联合,具体取决于您的映射。 像这样的查询将在单个有效负载中引入数据库中的大型连接图,这将消除任何带宽问题,尤其是当有效负载中存在大量冗余时(即,包含多个级别的Include遍历关联中的一个 – – 多方向)。

通过使用ToTraceString访问查询的基础TSQL并在SQL Server Management Studio中执行store命令以查看有效负载大小,可以检查查询返回过大负载的情况。 在这种情况下,您可以尝试减少查询中Include语句的数量,以便只引入所需的数据。 或者,您可以将查询分解为较小的子查询序列,例如:

在打破查询之前:

 using (NorthwindEntities context = new NorthwindEntities()) { var customers = from c in context.Customers.Include(c => c.Orders) where c.LastName.StartsWith(lastNameParameter) select c; foreach (Customer customer in customers) { ... } } 

打破查询后:

 using (NorthwindEntities context = new NorthwindEntities()) { var orders = from o in context.Orders where o.Customer.LastName.StartsWith(lastNameParameter) select o; orders.Load(); var customers = from c in context.Customers where c.LastName.StartsWith(lastNameParameter) select c; foreach (Customer customer in customers) { ... } } 

这仅适用于跟踪查询,因为我们正在利用上下文自动执行身份解析和关联修复的能力。

与延迟加载一样,权衡将是对较小有效负载的更多查询。 您还可以使用各个属性的投影来显式选择每个实体所需的数据,但在这种情况下您不会加载实体,并且不支持更新。