5

J'essaie d'écrire une méthode de référentiel pour Entity Framework Core 2.0 qui peut gérer renvoyer des collections enfant de propriétés en utilisant .ThenInclude, mais j'ai des problèmes avec la seconde expression. Voici une méthode de travail pour .Include, qui retournera les propriétés enfants (vous fournissez une liste de lambdas) de votre entité.Comment écrire la méthode Repository pour .ThenInclude dans 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(); 
} 

Maintenant, voici ma tentative d'écrire une méthode qui prendra un tuple de deux expressions et nourrir ceux qui en .include (a => a.someChild) .ThenInclude (b => b.aChildOfSomeChild) chaîne . Ce n'est pas une solution parfaite car elle ne gère qu'un enfant d'un enfant, mais c'est un début.

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 renvoie une erreur « Le type ne peut pas être déduit de l'utilisation, essayez de spécifier explicitement le type ». J'ai le sentiment que c'est parce que l'expression dans Item2 doit être classée comme liée à Item1, parce qu'elle a besoin de connaître la relation de l'enfant qu'elle a.

Des idées ou de meilleures techniques pour écrire une méthode comme celle-ci?

+1

Cela a été demandé à plusieurs reprises depuis qu'il était de facto une norme de spécification souhaité comprend des méthodes référentiel à l'aide EF6. Il serait intéressant d'entendre un membre de l'équipe EFC quelle était la raison derrière la décision de changer le motif en 'Include' /' ThenInclude' qui ne peut apparemment pas être représenté de cette façon, et plus important encore, quel est le remplacement EFC. –

Répondre

3

J'ai trouvé ce re méthode positoire en ligne et fait exactement ce que je voulais. La réponse de Yared était bonne, mais pas tout le chemin.

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

Utilisation:

 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

Alors que cela fonctionne, il expose une classe Entity Framework, ce qui peut ne pas être souhaitable. – SebastianR

1

J'ai eu le même problème puisque EF Core ne prend pas en charge le chargement paresseux, mais j'ai essayé de contourner le problème de la manière suivante:

d'abord créer une classe d'attributs pour marquer nos propriétés de navigation souhaitées à partir d'autres propriétés d'une donnée classe. Méthodes d'extension pour filtrer les propriétés de navigation et appliquer Include/ThenInclude à l'aide de la fonction de chargement Eager basée sur une chaîne.

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

    } 

Sample classes POCO mise en œuvre 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; } 
} 

Utilisation dans le référentiel

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

résultat JSON pour la classe d'échantillons C seraient:

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