Optimize entity framework query

前端 未结 5 1412
星月不相逢
星月不相逢 2020-12-02 12:27

I\'m trying to make a stackoverflow clone in my own time to learn EF6 and MVC5, i\'m currently using OWin for authentication.

Everything works fine when i have like

5条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2020-12-02 13:07

    I disagree with Ken2k's answer and am surprised that it has as many upvotes as it does.

    The code may be fine in the sense that it compiles, but having that many includes is definitely not OK if you care about your queries being performant. See 8.2.2 of MSFT's EF6 Performance Whitepaper:

    When we hear performance questions that involve server response time problems, the source of the issue is frequently queries with multiple Include statements.

    Taking a look at the TSQL that EF generates from eagerly loading that many navigation properties in one query (via the numerous .Include() statements) will make it obvious why this is no good. You're going to end up with way too many EF generated joins in one query.

    Break up your query so that there are no more than 2 .Include() statements per table fetch. You can do a separate .Load() per dataset but you most likely don't need to go that far, YMMV.

    var query = ctx.Questions.Where(...);
    // Loads Questions, Attachments, Location tables
    query.Include(q => q.Attachments)
         .Include(q => q.Location)
         .Load();
    
    // Loads IdentityUsers Table
    query.Select(q => q.CreatedBy).Load();
    // Loads Tags
    query.Select(q => q.Tags).Load();
    
    // Loads Upvotes and Downvotes
    query.Include(q => q.Upvotes)
         .Include(q => q.Downvotes)
         .Load();
    
    // Assuming Upvotes.CreatedBy and Downvotes.CreatedBy are also an IdentityUser,
    // then you don't need to do anything further as the IdentityUser table is loaded
    // from query.Select(q => q.CreatedBy).Load(); and EF will make this association for you
    

    Erik mentions that you can use .AsNoTracking(), and I'm not totally sure at what point he is recommending to use this but if you need to consume the resulting entity set with populated navigation properties (for example query above) you cannot use .AsNoTracking() at this invalidates the association between entities in EF's cache (once again, from 8.2.2 of MSFT's doc):

    This [breaking up the EF query] will work only on tracked queries, as we are making use of the ability the context has to perform identity resolution and association fixup automatically.

    For added performance if your query is read only, i.e. you are not updating values you can set the following properties on your DbContext (assuming you eagerly load all required data):

            Configuration.LazyLoadingEnabled = false;
            Configuration.AutoDetectChangesEnabled = false;
            Configuration.ProxyCreationEnabled = false;
    

    Finally your DbContext should have a Per-Request lifetime/scope.

    To Ken's point, certainly if your database architecture is a mess running profiler / viewing the execution plan can help you tweak indexes / identify other problems, but before even thinking of opening profiler break up your query limiting the number of .Includes() per .Load() and you should see a tremendous speed improvement from this alone.

提交回复
热议问题