efcore: EF Core doesn't lazy-load virtual navigation properties
Issue: 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,
About this issue
- Original URL
- State: closed
- Created 9 years ago
- Reactions: 8
- Comments: 49 (14 by maintainers)
@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:
@joehoeller The Stackoverflow page is removed!
@kccarter76 You realize that EF7 doesn’t actually exist, right?
I am developer that has ran into this issue with EF7 and I have long since decided to just build implement DAL that gets around the issue.
this lazy loading issue was the driving decision to resurrect subsonic for me.
https://github.com/kccarter76/SubSonic-Core is the DAL which is in beta https://github.com/kccarter76/SubSonic-Integration-Db is the integration DB created to enable unit integration tests against.
@hmdhasani there you go.
My Mistake done. Here is the completed code
var model = UOW.Movies.GetAll().Include(x => x.Reviews).OrderByDescending(m => m.Reviews.Count) .Select(m => new MovieViewModel { Id = m.Id, MovieName = m.MovieName, DirectorName = m.DirectorName, ReleaseYear = m.ReleaseYear, NoOfReviews = m.Reviews.Count });
@willotheblessed make sure you have a using for the
Microsoft.Data.Entity
namespace,Include
is an extension method in that namespace.@dwdickens, you will get a null reference exception if you don’t load the navigation property (and you didn’t init the property in the ctor), and an empty list if you did, so it is already easy to understand.
I’m happy with the current implementing o navigation properties in EF core.
I appreciate the performance reasons for discouraging lazy loading.
Developing an app now with EF, it gets confusing if a bug is due to bad db data or a failure to properly load - null is ambiguous.
Why doesn’t EF throw an error if you try to access data that hasn’t been loaded?
On 7 Mar 2016, at 6:05 AM, Doron Grinzaig notifications@github.com wrote:
@christophedemey , why? This proxy-lazy loading pattern caused people to hit the Database multiple times and possibly N+1 times. Now you must think before you query what relationships do you need, and you will never hit the DB multiple times. That’s not lazy loading, it’s lazy programming…
The lazy loading that is quite clever but not frequently used is “Lazy loading properties”, which means that unless you eagerly include (pre-configured) properties, they will stay null.
It’s helpful mainly when querying for entities that have lengthy string properties. So unless you actually need that property, it will remain null, just like with Navigation Properties.
— Reply to this email directly or view it on GitHub.