2

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();
CrazyEight
  • 147
  • 1
  • 18
  • Consider adding `AsNotracking()` – Anduin Xue Jan 21 '20 at 09:21
  • Another Option might be https://learn.microsoft.com/en-us/ef/ef6/querying/raw-sql in EF Core you can use this and afterwards put your input statements. If this works with inputstatements in EF I am not sure. – Jan Jan 21 '20 at 09:23
  • Your question is very broad and many solutions may apply. Usually for query performance you need to open the profiler on SQL Server asumming you are using MSSQL. See the execution plan and then try and optimize it. Also Lazy Loading is sth that degrades performance not enhances.Also indexes might help but i doubt it in this scenario. Also `Select` may also help since you are selecting which data to provide. Check for extra queries with the profiler 2,5 minutes is quite a lot. – panoskarajohn Jan 21 '20 at 09:31
  • @panoskarajohn how can i specify the question? What information do you need? I need to load all references listed in the includes of the codesnippet into dbcontext because i need them to write xml. If i reload them piece by piece the whole process is even slower – CrazyEight Jan 21 '20 at 10:04
  • @CrazyEight as i mention in my above comments the profiled time of the query would be a start. The execution plan would be a good addition. as well. The answer that has been provided is also valid. Try debugging and see what is happening. There might be extra queries that are happening. Try disabling `Proxies` and `LazyLoading` in terms see what is happening. Try investigating. Honestly these stuff are trial and error. You mentioned that you take all the data into an XML. You might want to rethink this. Do all data have value? – panoskarajohn Jan 21 '20 at 10:10

3 Answers3

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://learn.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.

Anduin Xue
  • 3,266
  • 2
  • 22
  • 43
  • But I would like to load all data at once because I create a xml file afterwards. If I load the data bit by bit a lot of queries are fired, which makes the performance completely slow. AsNoTracking() seems to make the whole thing even slower – CrazyEight Jan 21 '20 at 09:55
0

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();
0

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

Jonathan Magnan
  • 10,874
  • 2
  • 38
  • 60