EF Core中的多个Includes()

im1*_*ike 18 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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

我相信扩展方法在EF Core之前有效,但现在包括"children"就像这样:

var blogs = context.Blogs
    .Include(blog => blog.Posts)
        .ThenInclude(post => post.Author);
Run Code Online (Sandbox Code Playgroud)

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

Fré*_*ric 12

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

您可以更改模式以显示某个界面,从而允许您从调用者指定包含而不让它访问底层可查询.

这将导致类似于:

using YourProject.ExtensionNamespace;

// ...

patientRepository.GetById(0, ip => ip
    .Include(p => p.Addresses)
    .ThenInclude(a=> a.Country));
Run Code Online (Sandbox Code Playgroud)

using名称空间必须包含在最后的码块中定义的扩展方法的命名空间名称相匹配.

GetById 现在是:

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

扩展方法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;
}
Run Code Online (Sandbox Code Playgroud)

Includable类和接口,它们是简单的"占位符",其他扩展方法将用于模拟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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

IIncludable<TEntity, TProperty>几乎就像IIncludableQueryable<TEntity, TProperty>来自EF,但它没有扩展IQueryable,也不允许重塑查询.

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

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

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


im1*_*ike 8

对于后代,另一个不那么雄辩,但更简单的解决方案,利用使用的Include()重载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;
}
Run Code Online (Sandbox Code Playgroud)

存储库调用是:

var blog = blogRepository.GetById(id, BlogIncludes.Posts, BlogIncludes.Author);
Run Code Online (Sandbox Code Playgroud)