优化实体框架查询

Lee*_*ary 18 c# performance entity-framework

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

当我有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..
Run Code Online (Sandbox Code Playgroud)

不用说我得到SQL超时并且在安装Miniprofiler之后,看看生成的sql语句,它是一个可怕的几百行.

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

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

Eri*_*ips 17

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

仅基于一些样本数据:

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
Run Code Online (Sandbox Code Playgroud)

这将返回向上的行数

50 x 20 x 10 x 5 x 5 x 5 x 5 x 5 x 5 = 156,250,000
Run Code Online (Sandbox Code Playgroud)

说真的......这是要返回的INSANE行数.

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

第一:简单的方法,依靠实体框架在进入上下文时自动连接模型.然后,使用实体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);
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这个答案有点过了 - 笛卡尔积将是所有相关表的交叉连接,但是这里的关联将使用内部连接加载,这意味着返回的最大行数是具有最大行数的集合与相关集合的交集.但是,生成的查询将包含每个连接实体的重复列和数据,在上面的查询中,这些数据是大量冗余数据.最好将关联加载为1-2以外的单独查询. (4认同)

ken*_*n2k 11

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

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

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

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

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

所以:

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


Jon*_*nan 6

如您所知,Include方法生成巨大的SQL。

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

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

不是生成一个怪异的SQL,而是生成多个SQL(每个包含一个)。此功能还作为奖励,它允许过滤相关实体。

文件:EF +查询包含最佳化

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();
Run Code Online (Sandbox Code Playgroud)