【问题标题】:EF Core is lazy loading when I try to eager load with .Include()当我尝试使用 .Include() 进行预加载时,EF Core 会延迟加载
【发布时间】:2019-08-26 19:44:29
【问题描述】:

我使用的是 EF Core 2.2.6(数据库优先),似乎只是启用延迟加载让我无法急切加载。启用延迟加载是否会排除在任何容量下使用急切加载?

namespace Example.Models
{
    public class Lead
    {
        public int Id { get; set; }
        public LeadOrganization LeadOrganization { get; set; }

        public Lead(ExampleContext.Data.Lead dbLead)
        {
            Id = dbLead.Id;
            LeadOrganization = new LeadOrganization(dbLead.LeadOrganization);
        }

        public static Lead GetLead(int id)
        {
            using (var db = new ExampleContext())
            {
                var dbLead = db.Leads
                    .Include(l => l.LeadOrganization)
                        .ThenInclude(lo => lo.LeadOrganizationAddresses)
                            .ThenInclude(loa => loa.AddressType)
                    .FirstOrDefault(l => l.Id== id);

                return new Lead(dbLead);
            }
        }
    }
}
namespace Example.Models
{
    public class LeadOrganization
    {
        public IEnumerable<LeadOrganizationAddress> Addresses { get; set; }

        public LeadOrganization(ExampleContext.Data.LeadOrganization dbLeadOrganization)
        {
            Addresses = dbLeadOrganization.LeadOrganizationAddresses.Select(loa => new LeadOrganizationAddress(loa));
        }
    }
}
namespace Example.Models
{
    public class LeadOrganizationAddress
    {
        public AddressType AddressType { get; set; }

        public LeadOrganizationAddress(ExampleContext.Data.LeadOrganizationAddress dbLeadOrganizationAddress)
        {
            AddressType = new AddressType(dbLeadOrganizationAddress.AddressType);
        }
    }
}
namespace Example.Models
{
    public class AddressType
    {
        public short Id { get; set; }

        public AddressType(ExampleContext.Data.AddressType dbAddressType)
        {
            Id = dbAddressType.Id;
        }
    }
}

ExampleContext.Data 命名空间包含数据库中 EF 生成的部分类。 LeadLeadOrganizationLeadOrganizationAddressAddressType 是在属性方面基本上是 1:1 的类,但添加了静态方法(是的,这很奇怪,但这是我必须使用的)。

Lead 有一个 LeadOrganization,而 LeadOrganization 又具有至少一个 LeadOrganizationAddress,而 LeadOrganizationAddress 又具有 AddressType。

GetLead 调用Lead 构造函数时,查询中的数据尚未加载,即使它应该是预先加载的。这会导致嵌套对象出现问题。当它最终到达LeadOrganizationAddress 构造函数时,DbContext 已被释放,因此无法延迟加载关联的AddressType

我是否误解了急切加载的全部意义?我认为它会在初始查询时检索所有数据,然后让我将其传递给构造函数而不会出现问题。我不需要继续返回数据库并延迟加载任何内容。

如果您启用了延迟加载,您是否可以不急切加载?是否有其他解决方法,例如强制它加载任何代理实体?

【问题讨论】:

  • "lead.LeadOrganization 是一个代理,因为它实际上还没有检索到数据" 我认为这种状态不存在。该属性要么是null(未加载),要么是已加载的实例(代理与否无关紧要)。 EF Core 不会创建假实例,并且代理类可能用于将相关数据延迟加载到您的示例中未显示的LeadOrganization 实体。此外,不清楚您所说的“按预期”是什么意思,以及您在谈论嵌套实体的构造函数有什么问题。我认为最好提供minimal reproducible example
  • 我已经更新了我的帖子,以便更清楚地了解我正在尝试做什么以及我所看到的。

标签: c# entity-framework-core ef-core-2.2


【解决方案1】:

好的,经过调查,EF Core 2.x 通过代理实现延迟加载存在问题。相关跟踪问题是

问题在于导航属性 急切加载,但 LazyLoader 不知道何时释放 - 无法安全访问上下文更改跟踪器,只是抛出异常。相关代码可见here,在第一行:

if (_disposed)
{
    Logger.LazyLoadOnDisposedContextWarning(Context, entity, navigationName);
}

当我阅读它时,它应该在 EF Core 3.0 中通过以下“重大更改”-Lazy-loading proxies no longer assume navigation properties are fully loaded 发布时得到修复。它也部分解释了当前的问题:

旧行为

在 EF Core 3.0 之前,一旦处理了 DbContext,就无法知道从该上下文获取的实体上的给定导航属性是否已完全加载。

很遗憾,这并不能帮助您解决当前的问题。我看到的选项是:

  1. 等待 EF Core 3.0 发布
  2. 不要通过代理使用延迟加载
  3. 关闭已处理上下文警告的延迟加载 - 默认为Throw,将其更改为LogIgnore,例如:

    optionsBuilder.ConfigureWarnings(warnings => warnings
        .Log(CoreEventId.LazyLoadOnDisposedContextWarning)
    );
    

【讨论】:

    【解决方案2】:

    我假设您使用UseLazyLoadingProxies(),但希望禁用查询中特定包含的延迟加载。这还没有实现:

    https://github.com/aspnet/EntityFrameworkCore/issues/10787

    你现在唯一能做的事:

    1.) 禁用延迟加载代理(“所有属性的默认延迟加载”)

    2.) 然后对特定属性使用(手动实现)延迟加载,例如在您的一种情况下:

    public class LeadOrganization
    {
        private ILazyLoader _lazyLoader { get; set; }
    
        private IEnumerable<LeadOrganizationAddress> _addresses;
    
        public LeadOrganization(ILazyLoader lazyLoader)
        {
            _lazyLoader = lazyLoader;
        }
    
        public IEnumerable<LeadOrganizationAddress> Addresses
        {
            get => _addresses;
            set => _addresses = value;
        }
    
        public IEnumerable<LeadOrganizationAddress> AddressesLazy
        {
            get
            {
                _lazyLoader?.Load(this, ref _addresses);
            }
            set => this._addresses = value;
        }
    }
    

    所以对于急切加载使用.Include(lo=&gt;lo.Addresses),对于延迟加载使用.Include(lo=&gt;lo.AddressesLazy)


    编辑 1

    不应默认为所有属性启用 IMO 延迟加载 - 这可能会影响整个实现的性能。因此,在延迟加载为您带来优势的情况下,上述解决方案是一种替代方案。我也想在每个包含中都有这个选项,比如.Include(o=&gt;o.Addresses, LoadingBehaviour.Eager) - 也许将来会存在。

    【讨论】:

    • 以上代码显然应该在您的实体类中使用(阅读您的评论后)
    • 我同意默认情况下不启用延迟加载,当我们将项目从 EF 移动到 EF Core 时,我启用它作为现有代码的全部功能,因为如果您不急于加载属性并尝试使用它,它只是空的。因此,即使您尝试进行预加载,EF Core 也会尝试使用延迟加载(如果对所有内容都启用)?
    • 是的@Gabe - “现在”在 db-context 上启用延迟加载代理将为所有导航属性启用它...
    • 感谢这个临时解决方案,同时 EF 核心中没有其他选项。
    【解决方案3】:

    延迟加载并不是阻止属性实例化的原因,因为它们缺少适当的构造函数。

    除了 EF Core,这些类型很奇怪,例如LeadOrganization 需要在它们的构造函数中传递给它们自己的实例。这是一个先有鸡还是先有蛋的问题——如何创建第一个问题?

    public class LeadOrganization
    {
        public IEnumerable<LeadOrganizationAddress> Addresses { get; set; }
    
        public LeadOrganization(ExampleContext.Data.LeadOrganization dbLeadOrganization)
        {
            Addresses = dbLeadOrganization.LeadOrganizationAddresses.Select(loa => new LeadOrganizationAddress(loa));
        }
    }
    

    无论如何,EF Core only supports simple constructors with parameters based on convention(基本上是参数到属性的 1-1 映射),因此它不知道如何实例化和水合那些嵌套类,无论是渴望的还是惰性的。

    我建议使这些构造函数无参数,或者如果您希望 EF 通过属性来水合对象,至少向您的类添加一个无参数构造函数。

    【讨论】:

    • 对不起,这个例子有点混乱。我展示的类不是 EF 实体类。它们在属性方面是 1:1,但添加了方法,因此构造函数接受一个实体类并将其转换为我们正在使用的模型。我已经更新了命名空间以帮助澄清。你会看到AddressTypeExample.Models 中,但是构造函数接受ExampleContext.Data.AddressType 作为参数。
    猜你喜欢
    • 2019-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多