问题
[Updated - see update at bottom]
I am using EF code-first, and am generally happy with it. However, one simple (and common) operation is causing EF to generate ridiculously complex SQL, which is slowing my application down.
I am simply fetching a set of entities using a list of (integer) IDs, but because I need details of lots of sub-entities, I'm using .Include()
to get those sub-entities loaded at the same time, as follows:
db.MyEntities
.Where(x => x.ClientId == clientId)
.Where(x => ids.Contains(x.Id))
.Where(x => x.SubEntity1 != null)
.Include(x => x.SubEntity1)
.Include(x => x.SubEntity1.SubSubEntity1)
.Include(x => x.SubEntity1.SubSubEntity2)
.Include(x => x.SubEntity1.SubSubEntity3)
.Include(x => x.SubEntity1.SubSubEntity4)
.Include(x => x.SubEntity2)
.Include(x => x.SubEntity2.SubSubEntity1)
.Include(x => x.SubEntity2.SubSubEntity2)
.Include(x => x.SubEntity2.SubSubEntity3)
.Include(x => x.SubEntity2.SubSubEntity4)
.Include(x => x.SubEntity3)
As you can see, it's not a particularly complex query, with the exception of all those Include
s.
The SQL that EF generates for this is huge - around 74Kb of SQL. It doesn't take very long to execute (since normally the number of items in the list of IDs is small), but it takes EF more than a second just to construct the query - i.e. before the query is even sent to the database.
If I remove the Includes
, then the query is much smaller, and the whole thing takes much less time - but the various related entities are then loaded one-at-a-time, which doesn't scale well.
EF seems to give me two options for loading the data:
- Load all the sub-entities at once during the initial query (using
Include
as above), or - Load the sub-entities one-at-a-time (using lazy loading, or explicitly using
Load
/LoadProperty
).
Option 1 would be my preferred option if it worked, but since that doesn't work in this case, my only remaining option is 2 - and I don't think that's acceptable: there would be too many database queries where the input list of IDs (i.e. the number of entities) is large.
It seems to me that there is another option that EF doesn't seem to address: having fetched the main entities, fetch all the relevant SubEntity1 entities, then all the relevant SubEntity2 entities, etc. That way, the number of queries is related to the number of types of entity to be fetched, rather the number of entities. This would scale much better.
I can't see a way to do that in EF: in other words, to say "load this property for all these entities (in a single query)".
Will I just have to give up on EF and write my own SQL?
UPDATE
I have noticed that even if I remove the Include
s, the SQL generated is more complex than I think it should be, and I think this all stems from the fact that EF does not 'like' my table structure. I struggled for days to get EF to create the database structure I was looking for via Code First (and the Fluent API), and even when I had got to (nearly) where I wanted to be, I had to accept some compromises.
I think I'm now paying a further penalty for daring to do something that EF didn't want me to do. It looks like a simple query is more complex than it should be, and a slightly-more-complex query is massively more complex.
This is incredibly depressing - I thought I'd left all those EF hassles behind, and the system is now in production with dozens of users - which would make it very difficult for me to start over.
It seems I'm going to have to spend the eternity fighting EF tooth and nail at every turn. How I wish I'd never used it in the first place!
Anyway, back to my original question: if I have a bunch of entities of type A for which I want to load the related sub-entities of type B in one query, is there a way to do that?
回答1:
How about loading the data using stored procedures? Yes, it is a bit dirty, but this is what I do when I hit performance issues with EF. I hope I'm not missing something in your question.
http://msdn.microsoft.com/en-US/data/jj691402
来源:https://stackoverflow.com/questions/18128359/how-can-i-optimize-my-ef-code-first-query