当前位置: 首页 > 知识库问答 >
问题:

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

伏默
2023-03-14

我有一个简单的问题,但似乎找不到解决的办法。我使用的是EntityFramework核心版本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; }
}

但当我加载订单实体时,相关实体Customer及其内部地址为null

我所尝试的:

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

这只是一个例子,我有多个嵌套级别的实体,我想在通用存储库中加载嵌套的相关数据,因此不能使用Include和Include,因为加载时我不知道实际的实体类型。

示例:

    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个答案

耿锦
2023-03-14

使用.Include(“Order.Customer.Address”)

支持:.NETCore3.1.8,但我不知道是否更早

汪文光
2023-03-14

伊万的回答太棒了。我稍微修改了它(在这里使用Chistoph代码的帮助),这样扩展方法就可以与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();
        }
    }
}
井疏珂
2023-03-14

目前官方不存在此类功能(EF Core 2.0.2和即将推出的2.1)。它是在“急切加载所有导航属性”(4851(已关闭)中请求的,目前由基于规则的“急切加载”(include)2953跟踪,并允许在模型中声明聚合(例如,定义包含的属性或通过其他方式)#1985(都在积压中,即没有具体的时间表)。

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

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, int maxDepth = int.MaxValue)
        {
            if (maxDepth < 0) throw new ArgumentOutOfRangeException(nameof(maxDepth));
            var entityType = context.Model.FindEntityType(clrEntityType);
            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();
            }
        }

    }
}

第一种方法只是应用多个字符串baseInclude的一种方便方法。

第二个任务是使用EF核心提供的元数据收集类型的所有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();
}
 类似资料:
  • 我正在使用EF Core(v1.1.3)和C#。 在我的单元测试中,我使用代码优先db模型创建了一个sqlite内存数据库,然后用一些测试数据填充它。但是,当从dB加载实体列表时,相关实体会被填充,尽管没有使用“。包含”。 如果我使用相同的模型来创建一个实际的磁盘数据库,并进行相同的加载,那么只有在使用<代码>时,才会加载相关的实体。包括。 这是sqlite内存实现(v1.1.0)中的错误,还是我

  • 我想使用即时加载在实体框架核心中获得多个嵌套级别的子表。我认为延迟加载还没有实现。 我找到了EF6的答案。 我的问题是,EF Core中无法识别选择 错误CS1061“雇员”不包含“选择”的定义,并且找不到接受“雇员”类型的第一个参数的扩展方法“选择”(您是否缺少使用指令或程序集引用?) 我包含的命名空间: EF Core中的选择选项是什么。

  • 我已经找到了几个这样的帖子,但不能确定我在这里做错了什么。 我试着在两张桌子上各放一个项目。第二项包含对第一项的查找。我可以在没有问题的情况下为父级添加种子,但无法找到正确的语法来为子级添加种子。 下面是我用来种子父级的代码 下面是种子子的代码--这将失败: 根据文档的建议,我使用了一个匿名对象,而不是类本身。我也尝试过同时使用navigation属性、外键和nav道具。ID=1的组肯定已经在db

  • 当将实体模型转换为DTO时,我对Entity Framework Core(v2.0.1)有一个问题。基本上,通过该短语的任何其他版本,当我不想加载时,它是延迟加载的。这是一个简单的。NET Core Console应用程序(带有Microsoft. EntityFrameworkCore. SqlServer(2.0.1)包)。 现在,当执行方法来检索数据时,它正在执行以下SQL 如您所见,它没

  • 实体框架5.0首先使用现有数据库编写代码。使用电动工具对类进行逆向工程。一切都很好。数据库有两个表。一个父母和一个孩子的外键返回到父母ID。ID都是带有自动增量的int。我添加了许多父母记录,现在想将孩子记录添加到特定的父母。我能看到的唯一方法是通过在父母表中搜索名称或其他属性并返回ID来找到适当的父母ID。然后在添加孩子时在外键属性中使用该ID。我不想设置新父母,所以这是将孩子添加到现有父母的唯

  • 我正在尝试编写一个Updatestatus方法,该方法仅在我保存对数据库的更改时更新实体的状态字段。如果实体中的任何其他字段发生了变化,我不想将这些更改保存到数据库中。对于实体自己的字段来说,这很简单,使用: 但是,我发现,如果相关实体没有键值集,则将插入通过我设置状态的实体的导航属性可访问的任何相关实体。因此,如果将新的子实体添加到实体中。子项,并且子实体ChildId属性为0,则该子项将插入到