EF Core doesn't lazy-load virtual navigation properties
See original GitHub issueIssue: EF7 may not be lazy-loading all entities in a graph of related entities, despite all entities’ navigation properties being virtual:
I built a basic blog atop EF6. It behaves as expected and correctly models many-to-many relationships - in this case, the fact that posts may have zero or more authors.
I then rewrote this basic blog atop EF7.
This is where I found out that EF7 doesn’t currently support modelling many-to-many relationships (see #1368) and requires one to manually create and handle the join entity itself.
So, I added the PostAuthor type to the app’s model.
Aside: Many-to-many is going to be supported for RTW, right? This is a pretty essential feature
+----------+ +----------+ +-------------+ +--------+
| | 1 n | | 1 n | | n 1 | |
| Blog | -------- | Post | -------- | PostAuthors | -------- | Author |
| | | | | | | |
+----------+ +----------+ +-------------+ +--------+
When querying the model, I was found that EF wasn’t populating the Blog
’s Posts
collection requiring me to .Include(blog => b.Posts)
and then didn’t populate the Post
’s PostAuthors
collection, requiring me to .ThenInclude(p=>p.PostAuthors)
. Imagine my surprise, then, when I also found that I had to .ThenInclude(pa=>pa.Authors)
. The net result? This:
var blogs = ctx
.Blogs
.Include(b => b.Posts)
.ThenInclude(p => p.PostAuthors)
.ThenInclude(pa => pa.Author)
.ToList();
My expectation is that since all the entities’ navigation properties are virtual (Post
, PostAuthor
and Author ) that they’d be lazy-loaded on demand.
What am I doing wrong here?
TIA,
Issue Analytics
- State:
- Created 8 years ago
- Reactions:8
- Comments:49 (14 by maintainers)
Top GitHub Comments
@divega, @rowanmiller First off, great job so far with EF7. The team is doing an excellent job. I know a major re-write of an ORM can’t be easy - to say the least. Regarding Lazy Loading, I feel it is absolutely essential. Here’s why. A lot of us have domain logic coded into model classes and use the repository pattern to fetch entities. In this case, the domain model class expects the navigation property to have values that match what’s in the database (whether they are loaded on-demand or eager loaded). Otherwise, for a particular operation in the domain model class, you are ‘assuming’ that the property was pre-loaded. This is a dangerous assumption. If it wasn’t pre-loaded, then in the case of a Collection, it’s Count will be 0, and your logic is assuming it’s zero in the database - but it’s not - it just wasn’t loaded. Thus, in order to work properly, you need to now coordinate each type of action in your application with a proper fetch strategy from your repository. I’m working with EF7 now, and whenever I load something from a repository, I pass in an enum value that represents a “eager loading strategy” enum parameter so that I can determine which navigation properties need to be included, and add then include those dynamically to the Linq-to-entities query. This works - but the necessary coordination between repository fetching and domain model action is definitely brittle. The lazy loading is a nice safe haven to ensure it will always be loaded when some logic is expecting it will be. EF6 had the ability to disable lazy loading. I’m going to suggest going the opposite direction with EF7. Make it a feature that is disabled by default but can be enabled via an option in the ‘DbContextOptionsBuilder’. One other note along the same lines, is that, with a clearly defined separation of layers (where domain model has no knowledge of EF), I’m finding it difficult to deal with collections in a way that works as I would expect. Let’s say a have a method in a model class that removes some child entities based on some filter. If I remove an entity from a ICollection navigation property, I would expect that this would automatically set a ‘deleted’ state on that child entity, and a ‘SaveChanges’ on the tracked parent entity will know to delete that item from the child table.
Since we want the domain model to be persistence ignorant, we don’t want any references to EF in the domain model assembly, and thus can’t directly set a deleted state with EF methods. In this type of architecture, it’s necessary that this removal is tracked (again - maybe an optional setting or something - but without it - my architecture, of keeping logic in domain model classes, no longer works and it’s necessary to start putting some of this logic in the service(application) layer which is undesirable because I want to write automated tests against the domain model to test it’s logic without having any db involved in the process. Is this something that’s possible to do? If so, is it something that is intended to be included at some point?
Lazy loading is not implemented in EF7 (backlog item is https://github.com/aspnet/EntityFramework/issues/3797).
When you query, you can use the Include method to bring in related data: