Entity Framework 6 - Query Performance

时间秒杀一切 提交于 2021-02-07 10:04:36

问题


I use Entity Framework 6 and i currently have a query with many includes which loads about 1200 entities into the dbContext. Loading the entities seems to be quite slow as the query takes almost a minute. Is there anything I can do about the performance? I have 4 such queries that take 2.5 minutes to load? LazyLoading is enabled but for performance reasons i preload the entities.

var report = DbContext.REPORT.Single(r => r.ID == reportId);

//this query takes a bit less than 1 minute
DbContext.REPORT_ELEMENT
    .Include(re => re.LAYOUT)
    .Include(re => re.PAGEMASTER)
    .Include(re => re.REPORT_ELEMENTS)
    .Include(re => re.SUBTITLE_CONTENT)
    .Include(re => re.REPORT_ELEMENT_NOTE)
    .Include("SUBTITLE_CONTENT.CONTENT_ELEMENT.LANGUAGE")
    .Include("TITLE_CONTENT.CONTENT_ELEMENT.LANGUAGE")
    .Where(re => re.REPORT_ID == report.ID)
    .Load();

回答1:


Performance suggestions:

  • Prevent tracking. Query in read-only mode.
  • Prevent getting too much data in one query. Try to page it.
  • Prevent include. The query has too many Includes which makes performance bad.

Prevent tracking

Consider adding AsNoTracking for this makes query performance better.

Reference: https://docs.microsoft.com/en-us/ef/core/querying/tracking#no-tracking-queries

Only get the data you need

And the key reason for your slow query is it outputs too much data. Consider adding: Take(200), Skip() to take only the data you need or the current page requires. Use a pager to generate the report. This might helps a lot.

Prevent Include

Include generates SQL to select multiple tables. Which greatly increased complexity. You can only select the data you need and prevent writing the Include function.

For example, if you only want to get the last ball in the box, consider writing like this:

public class Box
{
    public int Id { get; set; }
    public IEnumerable<Ball> Balls { get; set; }
}

public class Ball
{
    public int Id { get; set; }

    public int BoxId { get; set; }
    public Box Box { get; set; }
}
var boxes = await Boxes
            // DO NOT Call Include(t => t.Balls) here!
            .Where(somecondition)
            .Select(t => new Box(){
              Id = t.Id,
              Balls = t.Balls.OrderByDescending(x => x.CreationTime)
                         .Take(1) // Only get what you need
            })               
            .ToListAsync()

Also when we use Select we can remove .Include because it won’t have any effect here.




回答2:


Disclaimer: I'm the owner of the project Entity Framework Plus

The Query IncludeOptimized feature allows to filter with include and optimize the query performance at the same time.

It usually improves the performance (split the query in smaller queries)

DbContext.REPORT_ELEMENT
    .IncludeOptimized(re => re.LAYOUT)
    .IncludeOptimized(re => re.PAGEMASTER)
    .IncludeOptimized(re => re.REPORT_ELEMENTS)
    .IncludeOptimized(re => re.SUBTITLE_CONTENT)
    .IncludeOptimized(re => re.REPORT_ELEMENT_NOTE)
    .IncludeOptimized(re => re.SUBTITLE_CONTENT.Select(sc => sc.CONTENT_ELEMENT))  // SelectMany?
    .IncludeOptimized(re => re.SUBTITLE_CONTENT.Select(sc => sc.CONTENT_ELEMENT).Select(ce => ce.LANGUAGE)) // SelectMany?
    .IncludeOptimized(re => re.TITLE_CONTENT)
    .IncludeOptimized(re => re.SUBTITLE_CONTENT.Select(sc => sc.CONTENT_ELEMENT)) // SelectMany?
    .IncludeOptimized(re => re.SUBTITLE_CONTENT.Select(sc => sc.CONTENT_ELEMENT).Select(ce => ce.LANGUAGE)) // SelectMany?
    .Where(re => re.REPORT_ID == report.ID)
    .Load();

Documentation: EF+ Query IncludeOptimized




回答3:


Additionally to the advices from Anduin, I would like to add the advice to split up the Includes() in several distinct queries. EF will be able to keep track of the references between the entities within the same DBContext. As a general rule of thumb - do not use more then three Includes() in the same query. Also ensure, that you have an index in the DB for every resulting JOIN.

To be able to do so, you must expose your FK fields in the entities additionally to the navigation properties.

Your initial query would become something like this:

    DbContext.LAYOUT
        .Where(re => re.LAYOUT_ID == report.LAYOUT_FK)
        .Load();
    DbContext.PAGEMASTER
        .Where(re => re.PAGEMASTERT_ID == report.PAGEMASTER_FK)
        .Load();


来源:https://stackoverflow.com/questions/59837585/entity-framework-6-query-performance

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!