繁体   English   中英

EF Core 中的多个 Includes()

[英]Multiple Includes() in EF Core

我有一个扩展方法,可让您在 EF 中一般包含数据:

public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query, params Expression<Func<T, object>>[] includes)
    where T : class
{
    if (includes != null)
    {
        query = includes.Aggregate(query, (current, include) => current.Include(include));
    }
    return query;
}

这允许我在我的存储库中有这样的方法:

public Patient GetById(int id, params Expression<Func<Patient, object>>[] includes)
{
    return context.Patients
        .IncludeMultiple(includes)
        .FirstOrDefault(x => x.PatientId == id);
}

我相信扩展方法在 EF Core 之前有效,但现在包括“孩子”是这样完成的:

var blogs = context.Blogs
    .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author);

有没有办法改变我的通用扩展方法以支持 EF Core 的新ThenInclude()实践?

正如其他人在评论中所说,您可以使用EF6 代码来解析您的表达式并应用相关的Include / ThenInclude调用。 毕竟它看起来并不难,但由于这不是我的想法,我宁愿不要用代码给出答案。

相反,您可以更改您的模式以公开某些接口,从而允许您从调用者指定您的包含,而不让它访问底层可查询。

这将导致类似:

using YourProject.ExtensionNamespace;

// ...

patientRepository.GetById(0, ip => ip
    .Include(p => p.Addresses)
    .ThenInclude(a=> a.Country));

using on 命名空间必须与包含在最后一个代码块中定义的扩展方法的命名空间名称匹配。

GetById现在是:

public static Patient GetById(int id,
    Func<IIncludable<Patient>, IIncludable> includes)
{
    return context.Patients
        .IncludeMultiple(includes)
        .FirstOrDefault(x => x.EndDayID == id);
}

扩展方法IncludeMultiple

public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query,
    Func<IIncludable<T>, IIncludable> includes)
    where T : class
{
    if (includes == null)
        return query;

    var includable = (Includable<T>)includes(new Includable<T>(query));
    return includable.Input;
}

可包含的类和接口,它们是简单的“占位符”,附加扩展方法将在其上完成模仿 EF IncludeIncludable方法的ThenInclude

public interface IIncludable { }

public interface IIncludable<out TEntity> : IIncludable { }

public interface IIncludable<out TEntity, out TProperty> : IIncludable<TEntity> { }

internal class Includable<TEntity> : IIncludable<TEntity> where TEntity : class
{
    internal IQueryable<TEntity> Input { get; }

    internal Includable(IQueryable<TEntity> queryable)
    {
        // C# 7 syntax, just rewrite it "old style" if you do not have Visual Studio 2017
        Input = queryable ?? throw new ArgumentNullException(nameof(queryable));
    }
}

internal class Includable<TEntity, TProperty> :
    Includable<TEntity>, IIncludable<TEntity, TProperty>
    where TEntity : class
{
    internal IIncludableQueryable<TEntity, TProperty> IncludableInput { get; }

    internal Includable(IIncludableQueryable<TEntity, TProperty> queryable) :
        base(queryable)
    {
        IncludableInput = queryable;
    }
}

IIncludable扩展方法:

using Microsoft.EntityFrameworkCore;

// others using ommitted

namespace YourProject.ExtensionNamespace
{
    public static class IncludableExtensions
    {
        public static IIncludable<TEntity, TProperty> Include<TEntity, TProperty>(
            this IIncludable<TEntity> includes,
            Expression<Func<TEntity, TProperty>> propertySelector)
            where TEntity : class
        {
            var result = ((Includable<TEntity>)includes).Input
                .Include(propertySelector);
            return new Includable<TEntity, TProperty>(result);
        }

        public static IIncludable<TEntity, TOtherProperty>
            ThenInclude<TEntity, TOtherProperty, TProperty>(
                this IIncludable<TEntity, TProperty> includes,
                Expression<Func<TProperty, TOtherProperty>> propertySelector)
            where TEntity : class
        {
            var result = ((Includable<TEntity, TProperty>)includes)
                .IncludableInput.ThenInclude(propertySelector);
            return new Includable<TEntity, TOtherProperty>(result);
        }

        public static IIncludable<TEntity, TOtherProperty>
            ThenInclude<TEntity, TOtherProperty, TProperty>(
                this IIncludable<TEntity, IEnumerable<TProperty>> includes,
                Expression<Func<TProperty, TOtherProperty>> propertySelector)
            where TEntity : class
        {
            var result = ((Includable<TEntity, IEnumerable<TProperty>>)includes)
                .IncludableInput.ThenInclude(propertySelector);
            return new Includable<TEntity, TOtherProperty>(result);
        }
    }
}

IIncludable<TEntity, TProperty>几乎与 EF 中的IIncludableQueryable<TEntity, TProperty>类似,但它不扩展IQueryable并且不允许重塑查询。

当然,如果调用者在同一个程序集中,它仍然可以将IIncludableIncludable并开始摆弄可查询对象。 但是好吧,如果有人想弄错,我们没有办法阻止他这样做(反射允许任何事情)。 重要的是暴露的合同。

现在,如果您不关心将IQueryable暴露给调用者(我对此表示怀疑),显然只需更改Func<Queryable<T>, Queryable<T>> addIncludes参数的params参数,并避免对上述所有内容进行编码。

最后最好的:我没有测试过这个,我目前不使用实体框架!

对于后代,另一个不太雄辩但更简单的解决方案是利用使用navigationPropertyPathInclude()重载:

public static class BlogIncludes
{
    public const string Posts = "Posts";
    public const string Author = "Posts.Author";
}

internal static class DataAccessExtensions
{
    internal static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query, 
        params string[] includes) where T : class
    {
        if (includes != null)
        {
            query = includes.Aggregate(query, (current, include) => current.Include(include));
        }
        return query;
    }
}

public Blog GetById(int ID, params string[] includes)
{
    var blog = context.Blogs
        .Where(x => x.BlogId == id)
        .IncludeMultiple(includes)
        .FirstOrDefault();
    return blog;
}

存储库调用是:

var blog = blogRepository.GetById(id, BlogIncludes.Posts, BlogIncludes.Author);

你可以这样做:

public Patient GetById(int id, Func<IQueryable<Patient>, IIncludableQueryable<Patient, object>> includes = null)
        {
            IQueryable<Patient> queryable = context.Patients;

            if (includes != null)
            {
                queryable = includes(queryable);
            }

            return  queryable.FirstOrDefault(x => x.PatientId == id);
        }

var patient = GetById(1, includes: source => source.Include(x => x.Relationship1).ThenInclude(x => x.Relationship2));

我做了这个方法来做动态包含。 这样,“选择”命令可以在 lambda 中使用,就像过去一样包含在内。

调用是这样的:

repository.IncludeQuery(query, a => a.First.Second.Select(b => b.Third), a => a.Fourth);

private IQueryable<TCall> IncludeQuery<TCall>(
    params Expression<Func<TCall, object>>[] includeProperties) where TCall : class
{
    IQueryable<TCall> query;

    query = context.Set<TCall>();

    foreach (var property in includeProperties)
    {
        if (!(property.Body is MethodCallExpression))
            query = query.Include(property);
        else
        {
            var expression = property.Body as MethodCallExpression;

            var include = GenerateInclude(expression);

            query = query.Include(include);
        }
    } 

    return query;
}

private string GenerateInclude(MethodCallExpression expression)
{
    var result = default(string);

    foreach (var argument in expression.Arguments)
    {
        if (argument is MethodCallExpression)
            result += GenerateInclude(argument as MethodCallExpression) + ".";
        else if (argument is MemberExpression)
            result += ((MemberExpression)argument).Member.Name + ".";
        else if (argument is LambdaExpression)
            result += ((MemberExpression)(argument as LambdaExpression).Body).Member.Name + ".";
    }

    return result.TrimEnd('.');
} 

当然有,

您可以遍历原始参数的表达式树,以及任何嵌套包含,将它们添加为

 .Include(entity => entity.NavigationProperty)
 .ThenInclude(navigationProperty.NestedNavigationProperty)

但它不是微不足道的,但绝对是非常可行的,如果你这样做,请分享,因为它肯定可以重复使用!

public Task<List<TEntity>> GetAll()
    {
        var query = _Db.Set<TEntity>().AsQueryable();
        foreach (var property in _Db.Model.FindEntityType(typeof(TEntity)).GetNavigations())
            query = query.Include(property.Name);
        return query.ToListAsync();

    }

我坚持使用使用字符串 navigationPropertyPath 的 Include() 重载的更简单的解决方案。 我能写的最简单的就是下面这个扩展方法。

using Microsoft.EntityFrameworkCore;
using System.Linq;

namespace MGame.Data.Helpers
{
    public static class IncludeBuilder
    {
        public static IQueryable<TSource> Include<TSource>(this IQueryable<TSource> queryable, params string[] navigations) where TSource : class
        {
            if (navigations == null || navigations.Length == 0) return queryable;

            return navigations.Aggregate(queryable, EntityFrameworkQueryableExtensions.Include);  // EntityFrameworkQueryableExtensions.Include method requires the constraint where TSource : class
        }
    }
}
    public TEntity GetByIdLoadFull(string id, List<string> navigatonProoperties)
    {
        if (id.isNullOrEmpty())
        {
            return null;
        }

        IQueryable<TEntity> query = dbSet;

        if (navigationProperties != null)
        {
            foreach (var navigationProperty in navigationProperties)
            {
                query = query.Include(navigationProperty.Name);
            }
        }

        return query.SingleOrDefault(x => x.Id == id);
    }

这是一个更简单的解决方案,想法是将 dbset 转换为 iqueryable 然后递归地包含属性

我就这样处理它;

我有Article实体。 它包括ArticleCategory实体。 而且ArticleCategory实体还包括Category实体。

所以: Article -> ArticleCategory -> Category

在我的通用存储库中;

public virtual IQueryable<T> GetIncluded(params Func<IQueryable<T>, IIncludableQueryable<T, object>>[] include)
{
     IQueryable<T> query = Entities; // <- this equals = protected virtual DbSet<T> Entities => _entities ?? (_entities = _context.Set<T>());
     if (include is not null)
     {
          foreach (var i in include)
          {
              query = i(query);
          }
     }
     return query;
}

我可以这样使用它;

var query = _articleReadRepository.GetIncluded(
               i => i.Include(s => s.ArticleCategories).ThenInclude(s => s.Category),
               i => i.Include(s => s.User)
            );

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM