EF Core中的多个Includes()

时间:2017-03-20 13:06:54

标签: c# entity-framework entity-framework-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之前有效,但现在包括“children”就像这样:

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

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

8 个答案:

答案 0 :(得分:9)

正如其他人在comments中所述,您可以EF6 code解析表达式并应用相关的Include / ThenInclude来电。毕竟它看起来并不那么难,但由于这不是我的想法,我宁愿不回答它的代码。

您可以更改模式以显示某些界面,从而允许您从调用者指定包含而不让它访问基础查询。

这会导致类似:

using YourProject.ExtensionNamespace;

// ...

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

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

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

Includable课程&amp;接口,这是简单的&#34;占位符&#34;其他扩展方法将用于模仿EF IncludeThenInclude方法的工作:

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,也不允许重新整形查询。

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

现在如果你不关心将IQueryable暴露给调用者(我怀疑),显然只需更改params参数的Func<Queryable<T>, Queryable<T>> addIncludes参数,并避免编码所有这些事情上方。

最好的结果:我没有测试过,我目前没有使用Entity Framework!

答案 1 :(得分:7)

对于后代而言,另一个不太有说服力但更简单的解决方案是利用使用Include()的{​​{1}}重载:

navigationPropertyPath

存储库调用是:

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

答案 2 :(得分:3)

当然有,

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

2017-03-27 19:00:00.6866682 +03:00

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

答案 3 :(得分:2)

您可以执行以下操作:

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

答案 4 :(得分:1)

我制作了这种方法来进行动态包含。这样,可以在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('.');
} 

答案 5 :(得分:0)

我坚持使用更简单的解决方案,该解决方案利用了使用字符串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
        }
    }
}

答案 6 :(得分:0)

    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,然后以递归方式包含属性

答案 7 :(得分:-1)

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

    }