实体框架核心2.0.1渴望加载所有嵌套的相关实体

时间:2018-03-31 23:40:11

标签: c# entity-framework-core eager-loading

我有一个简单的问题,但似乎找不到解决方法。我正在使用Entity Framework Core版本2.0.1,并希望默认加载我的所有实体。

示例:

public class Order
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int CustomerId { get; set; }
    public Customer Customer { get; set; }
}

public class Customer
{
    public int Id { get; set; } 
    public string Name { get; set; }
    public int AddressId { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public int Id { get; set; }
    public string PostCode { get; set; }
    public string City { get; set; }
}

但是当我加载订单实体时,相关实体客户然后在其中地址为空

我尝试了什么:

  • 尝试升级到2.1版并使用LazyLoadingProxies设置为false

这只是一个例子,我有多个嵌套级别的实体,我想在Generic Repository中加载嵌套的相关数据,所以不能使用包含 ThenInclude 因为我在加载它时不知道实际的实体类型。

示例:

    public virtual async Task<IEnumerable<T>> GetAllAsync(Expression<Func<T, bool>> predicate = null)
    {
        if (predicate == null)
        {
            return await Context.Set<T>().ToListAsync();
        }
        return await Context.Set<T>().Where(predicate).ToListAsync();
    }

我错过了什么?我在存储库中有什么问题吗?任何有关更好设计的帮助或指针(如果这就是问题在这里)都会受到赞赏。

由于

3 个答案:

答案 0 :(得分:16)

此功能目前尚未正式存在(EF Core 2.0.2以及传入的2.1)。它已在Eager load all navigation properties #4851(已结束)中被请求,目前由Rule-based eager load (include) #2953Allow for declaring aggregates in the model (e.g. defining included properties or by some other means) #1985跟踪(两者均在Backlog中,即没有具体的时间表)。

我可以提供以下两种自定义扩展方法:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore.Metadata;

namespace Microsoft.EntityFrameworkCore
{
    public static partial class CustomExtensions
    {
        public static IQueryable<T> Include<T>(this IQueryable<T> source, IEnumerable<string> navigationPropertyPaths)
            where T : class
        {
            return navigationPropertyPaths.Aggregate(source, (query, path) => query.Include(path));
        }

        public static IEnumerable<string> GetIncludePaths(this DbContext context, Type clrEntityType)
        {
            var entityType = context.Model.FindEntityType(clrEntityType);
            var includedNavigations = new HashSet<INavigation>();
            var stack = new Stack<IEnumerator<INavigation>>();
            while (true)
            {
                var entityNavigations = new List<INavigation>();
                foreach (var navigation in entityType.GetNavigations())
                {
                    if (includedNavigations.Add(navigation))
                        entityNavigations.Add(navigation);
                }
                if (entityNavigations.Count == 0)
                {
                    if (stack.Count > 0)
                        yield return string.Join(".", stack.Reverse().Select(e => e.Current.Name));
                }
                else
                {
                    foreach (var navigation in entityNavigations)
                    {
                        var inverseNavigation = navigation.FindInverse();
                        if (inverseNavigation != null)
                            includedNavigations.Add(inverseNavigation);
                    }
                    stack.Push(entityNavigations.GetEnumerator());
                }
                while (stack.Count > 0 && !stack.Peek().MoveNext())
                    stack.Pop();
                if (stack.Count == 0) break;
                entityType = stack.Peek().Current.GetTargetType();
            }
        }

    }
}

第一种方法是应用多个字符串基础Include

第二个是使用EF Core提供的元数据收集类型的所有Include路径的实际工作。它基本上是指向循环图处理,从传递的实体类型开始,不包括所包含路径的反向导航,只发送到“叶子”节点的路径。

您的示例中的用法可能是这样的:

public virtual async Task<IEnumerable<T>> GetAllAsync(Expression<Func<T, bool>> predicate = null)
{
    var query = Context.Set<T>()
        .Include(Context.GetIncludePaths(typeof(T));
    if (predicate != null)
        query = query.Where(predicate);
    return await query.ToListAsync();
}

答案 1 :(得分:1)

伊万的答案太棒了。我稍作修改(使用Chistoph的代码here的帮助),以便可以将扩展方法与DbContext本身联系起来,以防其他人觉得更方便。例如,在我的代码库中,我可以编写:

_commissionsContext.CommissionRulesetScopes.IncludeAll().ToListAsync();

这将急切地为每个CommissionRulesetScope加载实体的整个子图:

SELECT [c].[CommissionPlanId], [c].[StartPeriod], [c].[CommissionRulesetId], [c0].[Id], [c0].[Name], [c1].[Id], [c1].[CsiScoreRuleId], [c1].[DealerOptionCommissionRuleId], [c1].[EmailCaptureRuleId], [c1].[ProductCommissionRuleId], [c1].[ProductConsistencyRuleId], [c1].[UnitCommissionRulesetId], [c2].[Id], [c2].[ConsecutiveFailurePenalty], [c2].[CurrentMonthPenalty], [c2].[Enabled], [c2].[Target], [d].[Id], [e].[Id], [e].[Enabled], [e].[Penalty], [e].[Target], [p].[Id], [p0].[Id], [p0].[CommissionBonus], [p0].[Enabled], [p0].[ProductTarget], [p0].[UnitTarget], [u].[Id], [u].[AverageCsiScoreRuleId], [u].[FinancePenetrationRuleId], [u].[GuaranteePeriodCommissionLevel], [u].[MinimumRequiredCsiReturnRate], [u].[MonthlyExpectationAttainmentRuleId], [u].[UnitCommissionTable], [a].[Id], [f].[Id], [m].[Id], [d0].[DealerOptionCommissionRuleId], [d0].[MinimumValue], [d0].[Commission], [t].[ProductCommissionRuleId], [t].[ProductTypeId], [t].[Commission], [t].[Id], [t].[Description], [t].[Key], [t0].[ProductConsistencyRuleId], [t0].[ProductMinMixRangeId], [t0].[Id], [t0].[ProductTypeId], [t0].[Id0], [t0].[Description], [t0].[Key], [t0].[ProductMinMixRangeId0], [t0].[MinimumUnitsTarget], [t0].[Target], [a0].[RuleId], [a0].[Target], [a0].[Points], [f0].[RuleId], [f0].[Target], [f0].[Points], [m0].[RuleId], [m0].[Target], [m0].[Points]
FROM [CommissionRulesetScope] AS [c]
INNER JOIN [CommissionPlan] AS [c0] ON [c].[CommissionPlanId] = [c0].[Id]
INNER JOIN [CommissionRuleset] AS [c1] ON [c].[CommissionRulesetId] = [c1].[Id]
LEFT JOIN [CsiScoreRule] AS [c2] ON [c1].[CsiScoreRuleId] = [c2].[Id]
LEFT JOIN [DealerOptionCommissionRule] AS [d] ON [c1].[DealerOptionCommissionRuleId] = [d].[Id]
LEFT JOIN [EmailCaptureRule] AS [e] ON [c1].[EmailCaptureRuleId] = [e].[Id]
LEFT JOIN [ProductCommissionRule] AS [p] ON [c1].[ProductCommissionRuleId] = [p].[Id]
LEFT JOIN [ProductConsistencyRule] AS [p0] ON [c1].[ProductConsistencyRuleId] = [p0].[Id]
LEFT JOIN [UnitCommissionRuleset] AS [u] ON [c1].[UnitCommissionRulesetId] = [u].[Id]
LEFT JOIN [AverageCsiScoreRule] AS [a] ON [u].[AverageCsiScoreRuleId] = [a].[Id]
LEFT JOIN [FinancePenetrationRule] AS [f] ON [u].[FinancePenetrationRuleId] = [f].[Id]
LEFT JOIN [MonthlyExpectationAttainmentRule] AS [m] ON [u].[MonthlyExpectationAttainmentRuleId] = [m].[Id]
LEFT JOIN [DealerOptionCommission] AS [d0] ON [d].[Id] = [d0].[DealerOptionCommissionRuleId]
LEFT JOIN (
    SELECT [p1].[ProductCommissionRuleId], [p1].[ProductTypeId], [p1].[Commission], [p2].[Id], [p2].[Description], [p2].[Key]
    FROM [ProductCommission] AS [p1]
    LEFT JOIN [ProductType] AS [p2] ON [p1].[ProductTypeId] = [p2].[Id]
) AS [t] ON [p].[Id] = [t].[ProductCommissionRuleId]
LEFT JOIN (
    SELECT [p3].[ProductConsistencyRuleId], [p3].[ProductMinMixRangeId], [p4].[Id], [p4].[ProductTypeId], [p5].[Id] AS [Id0], [p5].[Description], [p5].[Key], [p6].[ProductMinMixRangeId] AS [ProductMinMixRangeId0], [p6].[MinimumUnitsTarget], [p6].[Target]
    FROM [ProductMinMixRangeAssociation] AS [p3]
    INNER JOIN [ProductMinMixRange] AS [p4] ON [p3].[ProductMinMixRangeId] = [p4].[Id]
    INNER JOIN [ProductType] AS [p5] ON [p4].[ProductTypeId] = [p5].[Id]
    LEFT JOIN [ProductMinMixTarget] AS [p6] ON [p4].[Id] = [p6].[ProductMinMixRangeId]
) AS [t0] ON [p0].[Id] = [t0].[ProductConsistencyRuleId]
LEFT JOIN [AverageCsiScoreThreshold] AS [a0] ON [a].[Id] = [a0].[RuleId]
LEFT JOIN [FinancePenetrationThreshold] AS [f0] ON [f].[Id] = [f0].[RuleId]
LEFT JOIN [MonthlyExpectationAttainmentThreshold] AS [m0] ON [m].[Id] = [m0].[RuleId]
ORDER BY [c].[CommissionPlanId], [c].[StartPeriod], [c0].[Id], [c1].[Id], [d0].[DealerOptionCommissionRuleId], [d0].[MinimumValue], [t].[ProductCommissionRuleId], [t].[ProductTypeId], [t0].[ProductConsistencyRuleId], [t0].[ProductMinMixRangeId], [t0].[Id], [t0].[Id0], [t0].[ProductMinMixRangeId0], [t0].[MinimumUnitsTarget], [a0].[RuleId], [a0].[Target], [f0].[RuleId], [f0].[Target], [m0].[RuleId], [m0].[Target]

这是适应:

public static class DbSetExtensions
{
    /// <summary>
    /// Ensures that all navigation properties (up to a certain depth) are eagerly loaded when entities are resolved from this
    /// DbSet.
    /// </summary>
    /// <returns>The queryable representation of this DbSet</returns>
    public static IQueryable<TEntity> IncludeAll<TEntity>(
        this DbSet<TEntity> dbSet,
        int maxDepth = int.MaxValue) where TEntity : class
    {
        IQueryable<TEntity> result = dbSet;
        var context = dbSet.GetService<ICurrentDbContext>().Context;
        var includePaths = GetIncludePaths<TEntity>(context, maxDepth);

        foreach (var includePath in includePaths)
        {
            result = result.Include(includePath);
        }

        return result;
    }

    /// <remarks>
    /// Adapted from https://stackoverflow.com/a/49597502/1636276
    /// </remarks>
    private static IEnumerable<string> GetIncludePaths<T>(DbContext context, int maxDepth = int.MaxValue)
    {
        if (maxDepth < 0)
            throw new ArgumentOutOfRangeException(nameof(maxDepth));

        var entityType = context.Model.FindEntityType(typeof(T));
        var includedNavigations = new HashSet<INavigation>();
        var stack = new Stack<IEnumerator<INavigation>>();

        while (true)
        {
            var entityNavigations = new List<INavigation>();

            if (stack.Count <= maxDepth)
            {
                foreach (var navigation in entityType.GetNavigations())
                {
                    if (includedNavigations.Add(navigation))
                        entityNavigations.Add(navigation);
                }
            }

            if (entityNavigations.Count == 0)
            {
                if (stack.Count > 0)
                    yield return string.Join(".", stack.Reverse().Select(e => e.Current!.Name));
            }
            else
            {
                foreach (var navigation in entityNavigations)
                {
                    var inverseNavigation = navigation.FindInverse();
                    if (inverseNavigation != null)
                        includedNavigations.Add(inverseNavigation);
                }

                stack.Push(entityNavigations.GetEnumerator());
            }

            while (stack.Count > 0 && !stack.Peek().MoveNext())
                stack.Pop();

            if (stack.Count == 0)
                break;

            entityType = stack.Peek().Current!.GetTargetType();
        }
    }
}

答案 2 :(得分:0)

使用.Include("Order.Customer.Address");

支持:肯定是.NET Core 3.1.8+,但我不知道是否也早于

相关问题