Ich versuche, eine Repository-Methode für Entity Framework Core 2.0 zu schreiben, die die Rückgabe untergeordneter Sammlungen von Eigenschaften mit .ThenInclude verarbeiten kann, aber ich habe Probleme mit dem zweiten Ausdruck. Hier ist eine Arbeitsmethode für .Include, die untergeordnete Eigenschaften (Sie liefern eine Liste von Lambdas) Ihrer Entität zurückgibt.
public T GetSingle(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includeProperties)
{
IQueryable<T> query = _context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty);
}
return query.Where(predicate).FirstOrDefault();
}
Jetzt hier ist mein Versuch, eine Methode zu schreiben, die ein Tupel von zwei Ausdrücken nimmt und diese in eine .Include (a => a.someChild) .ThenInclude (b => b.aChildOfSomeChild) -Kette eingibt. Dies ist keine perfekte Lösung, weil es nur ein Kind eines Kindes behandelt, aber es ist ein Anfang.
public T GetSingle(Expression<Func<T, bool>> predicate, params Tuple<Expression<Func<T, object>>, Expression<Func<T, object>>>[] includeProperties)
{
IQueryable<T> query = _context.Set<T>();
foreach (var includeProperty in includeProperties)
{
query = query.Include(includeProperty.Item1).ThenInclude(includeProperty.Item2);
}
return query.Where(predicate).FirstOrDefault();
}
Intellisense gibt einen Fehler zurück, der besagt "Der Typ kann nicht aus der Verwendung abgeleitet werden, versuchen Sie, den Typ explizit anzugeben". Ich habe das Gefühl, dass der Ausdruck in Item2 als irgendwie mit Item1 verbunden klassifiziert werden muss, weil er über die Child-Beziehung, die er hat, Bescheid wissen muss.
Irgendwelche Ideen oder bessere Techniken, um eine Methode wie diese zu schreiben?
Ich habe diese Repository-Methode online gefunden und es macht genau das, was ich wollte. Yareds Antwort war gut, aber nicht den ganzen Weg dorthin.
/// <summary>
/// Gets the first or default entity based on a predicate, orderby delegate and include delegate. This method default no-tracking query.
/// </summary>
/// <param name="selector">The selector for projection.</param>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <param name="orderBy">A function to order elements.</param>
/// <param name="include">A function to include navigation properties</param>
/// <param name="disableTracking"><c>True</c> to disable changing tracking; otherwise, <c>false</c>. Default to <c>true</c>.</param>
/// <returns>An <see cref="IPagedList{TEntity}"/> that contains elements that satisfy the condition specified by <paramref name="predicate"/>.</returns>
/// <remarks>This method default no-tracking query.</remarks>
public TResult GetFirstOrDefault<TResult>(Expression<Func<TEntity, TResult>> selector,
Expression<Func<TEntity, bool>> predicate = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
Func<IQueryable<TEntity>, IIncludableQueryable<TEntity, object>> include = null,
bool disableTracking = true)
{
IQueryable<TEntity> query = _dbSet;
if (disableTracking)
{
query = query.AsNoTracking();
}
if (include != null)
{
query = include(query);
}
if (predicate != null)
{
query = query.Where(predicate);
}
if (orderBy != null)
{
return orderBy(query).Select(selector).FirstOrDefault();
}
else
{
return query.Select(selector).FirstOrDefault();
}
}
Verwendung:
var affiliate = await affiliateRepository.GetFirstOrDefaultAsync(
predicate: b => b.Id == id,
include: source => source
.Include(a => a.Branches)
.ThenInclude(a => a.Emails)
.Include(a => a.Branches)
.ThenInclude(a => a.Phones));
Ich hatte das gleiche Problem, da EF Core das Lazy Loading nicht unterstützt, aber ich versuchte, auf folgende Weise Abhilfe zu schaffen:
Erstellen Sie zuerst eine Attributklasse, um die gewünschten Navigationseigenschaften von anderen Eigenschaften einer bestimmten Klasse zu markieren.
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class NavigationPropertyAttribute : Attribute
{
public NavigationPropertyAttribute()
{
}
}
Erweiterungsmethoden zum Ausfiltern von Navigationseigenschaften und Anwenden von Include / ThenInclude mithilfe von stringbasiertem Eager-Laden.
public static class DbContextHelper
{
public static Func<IQueryable<T>, IQueryable<T>> GetNavigations<T>() where T : BaseEntity
{
var type = typeof(T);
var navigationProperties = new List<string>();
//get navigation properties
GetNavigationProperties(type, type, string.Empty, navigationProperties);
Func<IQueryable<T>, IQueryable<T>> includes = ( query => {
return navigationProperties.Aggregate(query, (current, inc) => current.Include(inc));
});
return includes;
}
private static void GetNavigationProperties(Type baseType, Type type, string parentPropertyName, IList<string> 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);
}
}
}
}
Beispiel-POCO-Klassen, die NavigationPropertyAttribute
implementieren
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; }
}
Verwendung im Repository
public async Task<T> GetAsync(Expression<Func<T, bool>> predicate)
{
Func<IQueryable<T>, IQueryable<T>> includes = DbContextHelper.GetNavigations<T>();
IQueryable<T> query = _context.Set<T>();
if (includes != null)
{
query = includes(query);
}
var entity = await query.FirstOrDefaultAsync(predicate);
return entity;
}
Json-Ergebnis für Beispielklasse C wäre:
{
"B" : {
"A" : {
"Prop" : "SOME_VALUE"
}
}
}