简体   繁体   中英

Handling slow performance of the Entity Framework when eagerly loading with include statements?

I am attempting to pull back a large number of relations form a SQL Server database using the entity framework for display on a summary web page and I am finding the performance of using many include statement in the query is abysmal.

The requirement is to display all of a single user's data on a page at once, generally, this isn't a huge amount of data, but fetching it does require traversing quite a few EF relations with a query something like this

var class = context.Class.Where(a => a.Id.Equals(Id))
                      .Include(a => a.Teacher.Address)
                      .Include(a => a.Teacher.Supplies.Notebooks)
                      .Include(a => a.Teacher.Supplies.Pencils)
                      .Include(a => a.Teacher.Supplies.Textbooks)
                      .Include(a => a.Teacher.Supplies.Erasers)
                      .Include(a => a.Students.Select(d => d.Supplies.Notebooks))
                      .Include(a => a.Students.Select(d => d.Supplies.Pencils))
                      .Include(a => a.Students.Select(d => d.Supplies.Textbooks))
                      .Include(a => a.Students.Select(d => d.Supplies.Erasers))
                      .Include(a => a.Configuration)
                      .Include(a => a.Payment.Payer.Address)
                      .Include(a => a.Payment.PaymentMethod)
                      .First();

That takes more than 10 seconds to run against a test database that contains minimal data. However if I do this instead, performance takes ~1 second:

var class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Teacher.Address).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Teacher.Supplies.Notebooks).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Teacher.Supplies.Pencils).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Teacher.Supplies.Textbooks).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Teacher.Supplies.Erasers).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Students.Select(d => d.Supplies.Notebooks)).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Students.Select(d => d.Supplies.Pencils)).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Students.Select(d => d.Supplies.Textbooks)).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Students.Select(d => d.Supplies.Erasers)).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Configuration).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Payment.Payer.Address).First();
    class = context.Class.Where(a => a.Id.Equals(Id)).Include(a => a.Payment.PaymentMethod).First();

Is this really the best way to run a query to fetch all this data or am I doing this completely wrong?

Well, I count 18 joins there, so it's not going to be fast by any means. However, depending on how you're testing this, your results may or may not be significant. In particular, if you're debugging or just running locally in general, everything is going to be slower in general. If you're using LocalDb, it's going to be slower than SQL Server. IIS Express is single-threaded, while IIS is multi-threaded, so that has an effect on performance as well.

First, and foremost, you should get something like Glimpse running with your project. This will let you actually separate the time it takes to load the page in general from the time it takes to run your queries, as well as give you visibility into the number and scope of queries being run. However, until you test on an actual production-like machine, with full IIS and SQL Server, you won't really know how this is going to perform.

If it's a real problem, you can look into creating a stored procedure to return all this information. That will be much quicker than anything EF can ever do as SQL Server can store the execution plan and optimize the queries. If you're finding that Entity Framework is too slow for your purposes, in general, you can also investigate using alternate ORMs like Dapper . Of course, you'll have a learning curve there, but if your primary focus is performance, you can pretty much always to better than Entity Framework, though you might lose some of the niceties in the process.

Unfortunately, I can't explain why the Include technique is so slow, but this is how I would try to fetch that data:

var class = context
    .Class
    .Where(a => a.Id.Equals(Id))
    .First()
.Select(a => new 
{
    TA = a.Teacher.Address,
    TN = a.Teacher.Supplies.Notebooks,
    TP =a.Teacher.Supplies.Pencils,
    TT = a.Teacher.Supplies.Textbooks,
    TE = a.Teacher.Supplies.Erasers,
    SN = a.Students.Select(d => d.Supplies.Notebooks),
    SP = a.Students.Select(d => d.Supplies.Pencils),
    ST = a.Students.Select(d => d.Supplies.Textbooks),
    SE = a.Students.Select(d => d.Supplies.Erasers),
    a.Configuration,
    PA = a.Payment.Payer.Address,
    a.Payment.PaymentMethod
};

Does that improve the performance? If it does I'd be interested in the difference between the sql generated by the 2 techniques

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM