5

Próbuję napisać metodę repozytorium dla Entity Framework Core 2.0, która może obsłużyć zwracające zbiory potomne właściwości przy użyciu .ThenInclude, ale mam problem z drugim wyrażenie. Oto działająca metoda .Include, która zwróci właściwości podrzędne (dostarczasz listę lambd) twojego obiektu.Jak napisać metodę repozytorium dla .ThenInclude w EF Core 2

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(); 
} 

Teraz tutaj jest mój próba pisania metodę, która odbędzie krotką dwóch wyrażeń i nakarmić tych, w .Include (a => a.someChild) .ThenInclude (b => b.aChildOfSomeChild) łańcuch . To nie jest idealne rozwiązanie, ponieważ obsługuje tylko jedno dziecko dziecka, ale jest to początek.

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 zwraca komunikat o błędzie "Typ nie może zostać wywnioskowany z użycia, spróbuj podać ten typ jawnie". Mam przeczucie, że to dlatego, że wyrażenie w Item2 musi być sklasyfikowane jako w jakiś sposób powiązane z Item1, ponieważ musi wiedzieć o jego relacji podrzędnej.

Jakieś pomysły lub lepsze techniki pisania takiej metody?

+1

ten został poproszony kilkakrotnie ponieważ de facto standardem określania pożądanego obejmuje sposoby repozytorium przy EF6. Byłoby interesujące usłyszeć jakiegoś członka zespołu EFC, co było przyczyną decyzji o zmianie wzorca na 'Include' /' ThenInclude', który widocznie nie może być reprezentowany w ten sposób, a co ważniejsze, co to jest zastąpienie EFC. –

Odpowiedz

3

Znalazłem re metoda pository online i robi dokładnie to, co chciałem. Odpowiedź Yareda była dobra, ale nie do końca.

/// <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(); 
     } 
    } 

Zastosowanie:

 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)); 
+1

Podczas tej operacji ujawnia klasa Entity Framework, która może nie być pożądana. – SebastianR

1

miałem ten sam problem, ponieważ EF Rdzenia nie obsługuje leniwy załadunku, ale starał się obejście w następujący sposób:

najpierw utworzyć klasę atrybutu do oznaczania naszych pożądanych właściwości nawigacyjnych od innych właściwości danego klasa.

[AttributeUsage(AttributeTargets.Property, Inherited = false)] 
public class NavigationPropertyAttribute : Attribute 
{ 
    public NavigationPropertyAttribute() 
    { 
    } 
} 

Metody rozszerzeń w celu odfiltrowania właściwości nawigacyjnych i zastosowania opcji Uwzględnij/następnie zastosuj za pomocą ładowania opartego na łańcuchu.

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); 
      } 
     } 

    } 

klas POCO Próbka wykonawcze 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; } 
} 

Wykorzystanie repozytorium

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 wynik dla klasy C próbka będzie:

{ 
    "B" : { 
     "A" : { 
       "Prop" : "SOME_VALUE" 
      } 
     } 
}