簡體   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