简体   繁体   中英

How to work around slow joining queries in Entity Framework

I have an MVC 4 project that is using Code First with Entity Framework 6 (backed by SQL Server 2008). I'm trying to optimize a particularly nasty query that looks something like:

var tops = Context.Top
    .Include(t => 
        t.Foo
            .Select(f => f.FooChild1
                .Select(c => c.Baz)))
    .Include(t =>
        t.Foo
            .Select(f => f.FooChild3))
    .Include(t =>
        t.Foo
            .Select(f => f.FooChild2))
    .Include(t =>
        t.Foo
            .Select(f => f.FooChild1
                .Select(c => c.Bar)))
    .Where(t => t.Foo.Count > 0)
    .ToList();

Where the relationships look like:

Top  
    1 ----> 0..N  Foo
        1 ----> 0..N  FooChild1
            1 ----> 0..N Bar
            1 ----> 0..N Baz
        1 ----> 0..N FooChild2
        0..N ----> 1 FooChild3

As you can see, the query does a lot of eager loading, so the resulting query has a lot of joins. Lazy loading has proved much too slow for what I'm doing with the resulting data.

The generated query for this takes about 2 seconds to execute on my SQL Server, but a hand written query that obtains the data I need only takes about 91 ms. Is there anything I can do to improve this?

What I have tried

I tried pre-loading by calling Load() on all of the other tables that I need and getting rid of all the Include 's. I'm not sure why (maybe this trick does not work with DbContext ), but it had no effect. Navigation properties were lazy loaded.

What I am considering

  1. One option that occurs to me would be to hand write a SQL view that queries the data I need, and map an entity to it in Code First. Not sure how to do that exactly, but I hope that by doing that I could avoid the bad performance in the generated query.

  2. Modifying the design of my database so the information I need is cached in the Top table. I don't like the duplication of data in this option, but at least I wouldn't have to traverse so many navigation properties.

Any pointers?

You can use precompiled query to improve performance in your case. Something like (example from msdn cause I don't know your types):

static readonly Func<AdventureWorksEntities, Decimal, IQueryable<SalesOrderHeader>> s_compiledQuery2 = 
    CompiledQuery.Compile<AdventureWorksEntities, Decimal, IQueryable<SalesOrderHeader>>(
            (ctx, total) => from order in ctx.SalesOrderHeaders
                            where order.TotalDue >= total
                            select order);

static void CompiledQuery2()
{            
    using (AdventureWorksEntities context = new AdventureWorksEntities())
    {
        Decimal totalDue = 200.00M;

        IQueryable<SalesOrderHeader> orders = s_compiledQuery2.Invoke(context, totalDue);

        foreach (SalesOrderHeader order in orders)
        {
            Console.WriteLine("ID: {0}  Order date: {1} Total due: {2}",
                order.SalesOrderID,
                order.OrderDate,
                order.TotalDue);
        }
    }            
}

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