I\'m trying to write a repository method for Entity Framework Core 2.0 that can handle returning child collections of properties using .ThenInclude, but I\'m having trouble
I had the same issue since EF Core doesn't support lazy loading but i tried to get workaround in the following way:
First create an attribute class to mark our desired navigation properties from other properties of a given class.
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class NavigationPropertyAttribute : Attribute
{
public NavigationPropertyAttribute()
{
}
}
Extension methods to filter out navigation properties and apply Include/ThenInclude using string based Eager loading.
public static class DbContextHelper
{
public static Func, IQueryable> GetNavigations() where T : BaseEntity
{
var type = typeof(T);
var navigationProperties = new List();
//get navigation properties
GetNavigationProperties(type, type, string.Empty, navigationProperties);
Func, IQueryable> includes = ( query => {
return navigationProperties.Aggregate(query, (current, inc) => current.Include(inc));
});
return includes;
}
private static void GetNavigationProperties(Type baseType, Type type, string parentPropertyName, IList accumulator)
{
//get navigation properties
var properties = type.GetProperties();
var navigationPropertyInfoList = properties.Where(prop => prop.IsDefined(typeof(NavigationPropertyAttribute)));
foreach (PropertyInfo prop in navigationPropertyInfoList)
{
var propertyType = prop.PropertyType;
var elementType = propertyType.GetTypeInfo().IsGenericType ? propertyType.GetGenericArguments()[0] : propertyType;
//Prepare navigation property in {parentPropertyName}.{propertyName} format and push into accumulator
var properyName = string.Format("{0}{1}{2}", parentPropertyName, string.IsNullOrEmpty(parentPropertyName) ? string.Empty : ".", prop.Name);
accumulator.Add(properyName);
//Skip recursion of propert has JsonIgnore attribute or current property type is the same as baseType
var isJsonIgnored = prop.IsDefined(typeof(JsonIgnoreAttribute));
if(!isJsonIgnored && elementType != baseType){
GetNavigationProperties(baseType, elementType, properyName, accumulator);
}
}
}
}
Sample POCO classes implementing NavigationPropertyAttribute
public class A : BaseEntity{
public string Prop{ get; set; }
}
public class B : BaseEntity{
[NavigationProperty]
public virtual A A{ get; set; }
}
public class C : BaseEntity{
[NavigationProperty]
public virtual B B{ get; set; }
}
Usage in Repository
public async Task GetAsync(Expression> predicate)
{
Func, IQueryable> includes = DbContextHelper.GetNavigations();
IQueryable query = _context.Set();
if (includes != null)
{
query = includes(query);
}
var entity = await query.FirstOrDefaultAsync(predicate);
return entity;
}
Json result for sample class C would be:
{
"B" : {
"A" : {
"Prop" : "SOME_VALUE"
}
}
}