Entity Framework Core 2.0.1 Eager Loading on All Nested Related Entities

Entity Framework Core 2.0.1 Eager Loading on all nested related entities

Such feature officially does not exist currently (EF Core 2.0.2 and also the incoming 2.1). It's been requested in Eager load all navigation properties #4851(Closed) and currently is tracked by Rule-based eager load (include) #2953 and Allow for declaring aggregates in the model (e.g. defining included properties or by some other means) #1985 (both in Backlog, i.e. no concrete schedule).

I can offer the following two custom extension methods:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Microsoft.EntityFrameworkCore
{
public static partial class CustomExtensions
{
public static IQueryable<T> Include<T>(this IQueryable<T> source, IEnumerable<string> navigationPropertyPaths)
where T : class
{
return navigationPropertyPaths.Aggregate(source, (query, path) => query.Include(path));
}

public static IEnumerable<string> GetIncludePaths(this DbContext context, Type clrEntityType, int maxDepth = int.MaxValue)
{
if (maxDepth < 0) throw new ArgumentOutOfRangeException(nameof(maxDepth));
var entityType = context.Model.FindEntityType(clrEntityType);
var includedNavigations = new HashSet<INavigation>();
var stack = new Stack<IEnumerator<INavigation>>();
while (true)
{
var entityNavigations = new List<INavigation>();
if (stack.Count <= maxDepth)
{
foreach (var navigation in entityType.GetNavigations())
{
if (includedNavigations.Add(navigation))
entityNavigations.Add(navigation);
}
}
if (entityNavigations.Count == 0)
{
if (stack.Count > 0)
yield return string.Join(".", stack.Reverse().Select(e => e.Current.Name));
}
else
{
foreach (var navigation in entityNavigations)
{
var inverseNavigation = navigation.FindInverse();
if (inverseNavigation != null)
includedNavigations.Add(inverseNavigation);
}
stack.Push(entityNavigations.GetEnumerator());
}
while (stack.Count > 0 && !stack.Peek().MoveNext())
stack.Pop();
if (stack.Count == 0) break;
entityType = stack.Peek().Current.GetTargetType();
}
}

}
}

The first is just a convenient way of applying multiple string base Include.

The second does the actual job of collecting all Include paths for a type using EF Core provided metadata. It's basically directed cyclic graph processing starting with the passed entity type, excluding the inverse navigations of the included paths and emitting only the paths to "leaf" nodes.

The usage in your example could be like this:

public virtual async Task<IEnumerable<T>> GetAllAsync(Expression<Func<T, bool>> predicate = null)
{
var query = Context.Set<T>()
.Include(Context.GetIncludePaths(typeof(T));
if (predicate != null)
query = query.Where(predicate);
return await query.ToListAsync();
}

EF Core - why 'infinate' nested eager loading?

Microsoft replaced Microsoft.AspNetCore.Mvc.NewtonsoftJson in ASP.NET Core 3 with their own implementation which is System.Text.Json but it doesn't support Reference Loop Handling handling yet.

So in order to configure Reference Loop Handling you need to add the nuget package for Microsoft.AspNetCore.Mvc.NewtonsoftJson then configure it this way:

services.AddControllersWithViews().AddNewtonsoftJson(options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});

Entity Framework Core 6 eager loading include too many level

I would have expected to stop at the category level, according to include stantment.

It did. However when each SiteImageCategory is loaded, if its SiteImage is already in the change tracker, the relationship will be "fixed up". So if you load all the SiteImages you will already have each SiteImageCategory's SiteImages in the Change Tracker, and the inverse navigation property collection will be populated.

Why is Entity Framework Core eager loading Related Entities that aren't Included

If you look at the documentation, there's this specific note that is actually the trap you fell into:

Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.

By doing

.ThenInclude(e => e.Employee);

You request the EF to fetch this Employee thus causing it to be eager-loaded wherever it's referenced. And because in this specific case your Event.Creator is the same as EventResponsibilities.Employee you explicitly requested, after it's fetched into EventResponsibilities it's also fetched into your Event.
If your Event.Creator was a different Employee than the EeventsResponsibilities.Employee you wouldn't experience this abnormality.

Such kind of unexpected behaviors and redundant data is one of the reasons people use separate DTOs for returning data from controller to make sure API returns just the right amount of data needed.

As a quick workaround, you could simply add JsonIgnoreAttribute on your Creator property:

[JsonIgnore]
public Employee Creator { get; set; }

But personally I don't think mixing up DTO and Entity in the same class is a very good idea as that will cause all sorts of troubles whenever any of entities will change.

EF Core Eager Loading nested collections

I thought the idea of eager loading was to only include the related models you specify in the include statement but it seems this is not the case. Could someone explain what's happening here?

You are right, that's not the case. The idea of eager loading is to ensure the related data you specify is loaded. It doesn't mean/guarantee that related data won't be included.

It's partially explained in the Loading Related Data section of the EF Core documentation:

Tip

Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.

The navigation property fix-up means that anytime entity instance is materialized, all related navigation properties are updated to reflect it, for instance Driver is added to Driver.DriverStatusType.Drivers and vice versa.

Note that when using tracking queries, this might happen after the non including query is materialized (ToList()) because change tracker keeps track of object references and automatically updates them when you perform other tracking queries.

Another effect of that fix-up process is that when you include one of the ends of the relationship, the inverse navigation property of the other end is automatically populated.

So even if the first case the Drivers property should be populated and contain single item. And this is what actually happening in my clean tests, don't know why you are getting difference - may be the serializer is hiding it?

Anyway, all that means that you can't really control the content of the navigation properties. The only way you can control exactly what are you returning is to use special DTO/ViewModel etc. classes and projection (Select).

Which is the better way to load a single entity with its related data with EF Core in ASP.NET Core?

Eager/lazy is not relevant here, since it's still you who decides the exact time when the requests will be done. Three requests will always be longer than one, so the question is, how often the linked arrays are needed. If they are needed every time, there is definitely no point in splitting the requests. If not... Well, you need to profile it yourself then.

But the performance should not be your main concern. To load everything in one request is simpler and easier to understand. So I would suggest to always go with the simpler solution, even if it's not the best performance-wise. And to start tweaking and optimizing only if it turns out that the performance is too low, and only if the profiling shows that the bad SQL is the reason for that. Spoiler: it probably won't.



Related Topics



Leave a reply



Submit