Как написать метод репозитория.ThenInclude в ядре EF 2

Я пытаюсь написать метод репозитория для Entity Framework Core 2.0, который может обрабатывать возвращаемые дочерние коллекции свойств .Но у меня проблемы со вторым выражением. Вот метод работы для .Include, который вернет дочерние свойства (вы предоставляете список лямбд) вашей сущности.

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

теперь вот моя попытка написать метод, который возьмет кортеж из двух выражений и скормит их в a .Включают(a => а.someChild).ThenInclude(b => b.achildofsomechild) цепь. Это не идеальное решение, потому что оно обрабатывает только одного ребенка ребенка, но это начало.

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 возвращает сообщение об ошибке "тип не может быть выведен из использования, попробуйте явно указать тип". У меня такое чувство, что это потому, что выражение в Item2 должно быть классифицировано как каким-то образом связанное с Item1, потому что оно должно знать о своих детских отношениях.

какие-либо идеи или лучшие методы для написания такого метода?

2 ответов


Я нашел этот метод репозитория в интернете, и это именно то, что я хотел. Ответ яреда был хорош, но не до конца.

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

использование:

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

у меня была та же проблема, так как EF Core не поддерживает ленивую загрузку, но я попытался обойти ее следующим образом:

сначала создайте класс атрибутов, чтобы отметить наши желаемые свойства навигации из других свойств данного класса.

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

методы расширения для фильтрации свойств навигации и применения Include / ThenInclude с использованием строки на основе нетерпеливой загрузки.

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

пример реализации классов POCO 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; }
}

загрузка в хранилище

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 для образца класса C будет:

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