Since no-tracking queries in Entity Framework Core have lazy-loading disabled, I've been looking for a way to eagerly load all needed properties.
I'm trying to create a class that will handle the generation of all navigational paths from a certain entity class. I would use these paths to call .Include(path)
on an IQueryable<TEntity>
of that entity, so they can get eagerly loaded.
This is the method that handles the generation of navigational paths:
private IEnumerable<string> GenIncludePaths(int depth, IEntityType entityType, HashSet<INavigation> includedNavigations = null)
{
if (depth <= 0)
yield break;
if (includedNavigations == null)
includedNavigations = new HashSet<INavigation>();
var entityNavigations = new List<INavigation>();
foreach (var navigation in entityType.GetNavigations())
{
//Check if duplicate
if (includedNavigations.Add(navigation))
entityNavigations.Add(navigation);
//Add inverse to avoid infinite loop
var inverseNavigation = navigation.FindInverse();
if (inverseNavigation != null)
includedNavigations.Add(inverseNavigation);
}
foreach (var navigation in entityNavigations)
{
//Recursively search for navigations
foreach (var nestedNavigation in GenIncludePaths(depth - 1, navigation.GetTargetType(), includedNavigations))
{
yield return $"{navigation.Name}.{nestedNavigation}";
}
yield return navigation.Name;
}
}
This method is a recursive version of the method that was suggested by Ivan in this answer. The method is organized recursively so that I can have control over how deep I want to search for navigable properties.
With the next method, I include each generated path to the inline LINQ:
public IQueryable<TEntity> IncludeAll(IQueryable<TEntity> source)
{
return this.propertyPaths.Aggregate(source, (query, path) => query.Include(path));
}
All instances of IQueryable<TEntity>
that I use this method on have tracking turned OFF, and the problem arises when I include paths from the depth of 2 or more, since some of those generated paths are cyclic. When I try to execute the query (with .ToList()
) to retrieve the results I get the following error:
System.InvalidOperationException: The Include path 'CompaniesAdditionalData->Company' results in a cycle. Cycles are not allowed in no-tracking queries. Either use a tracking query or remove the cycle.
Is there any way I can check whether a navigation would result in a cycle? Or would I have to move away from the automatic generation of paths? Any feedback is welcome.
I tried to explain the problem as concisely as I could, if more information is needed be sure to tell me.
EDIT: I have implemented the changes that Ivan suggested in the comments, by passing the HashSet<INavigation>
as a parameter in the recursive calls, state is maintained and no cyclic paths are generated. But now, another problem emerges. When there are multiple navigation properties of the same type in an entity, only one of them will be recursively searched for paths.
This happens because when the first property of that type is searched, every navigational property from that type is added to the includedNavigations
. This means that any subsequent attempt to search navigational properties of that type will fail, as they are all treated as already included.