当我尝试使用 .Include() 进行预加载时,EF Core 是延迟加载
EF Core is lazy loading when I try to eager load with .Include()
我正在使用 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 从数据库生成的部分 类。 Lead
、LeadOrganization
、LeadOrganizationAddress
和 AddressType
是 类 基本上 1:1 具有属性方面的部分,但具有静态方法添加(是的,这很奇怪,但这是我必须处理的)。
Lead 有一个 LeadOrganization,LeadOrganization 又至少有一个 LeadOrganizationAddress,而 LeadOrganizationAddress 又有一个 AddressType。
当 GetLead
调用 Lead
构造函数时,查询中的数据尚未加载,尽管它应该预先加载。这会导致嵌套对象的问题。当它最终到达 LeadOrganizationAddress
构造函数时,DbContext 已被释放,因此无法延迟加载关联的 AddressType
.
我是不是误解了预加载的全部意义?我认为它会在初始查询时检索所有数据,然后让我毫无问题地将其传递给构造函数。我不需要继续返回数据库并延迟加载任何东西。
如果启用了延迟加载,是否可以简单地不进行预加载?是否有其他解决方法,例如强制它加载任何代理实体?
延迟加载并不是阻止属性实例化的原因,而是缺少适当的构造函数。
除了 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 通过属性来混合对象,至少向您的 类.
添加一个无参数的构造函数
我假设您使用 UseLazyLoadingProxies()
但想禁用查询中特定包含的延迟加载。这还没有实现:
https://github.com/aspnet/EntityFrameworkCore/issues/10787
你现在唯一能做的事:
1.) 禁用延迟加载代理 ("default lazy loading for all properties")
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=>lo.Addresses)
,对于延迟加载使用 .Include(lo=>lo.AddressesLazy)
编辑 1
不应为所有属性默认启用 IMO 延迟加载 - 这可能会影响整个实现的性能。因此,在延迟加载为您带来优势的情况下,上述解决方案是一种替代方案。我也想在每个包含中都有这个选项,比如 .Include(o=>o.Addresses, LoadingBehaviour.Eager)
- 也许将来会存在。
好的,在调查问题后,EF Core 2.x 通过代理实现延迟加载存在问题。相关的跟踪问题是
- #15170: Eager loading include with using UseLazyLoadingProxies
- #12780: Store IsLoaded flags in proxy for better lazy-loading experience
问题是导航属性 是 急切加载的,但是 LazyLoader
不知道在处理时 - 无法安全地访问上下文更改跟踪器,只是抛出异常。相关代码可见here,第一行:
if (_disposed)
{
Logger.LazyLoadOnDisposedContextWarning(Context, entity, navigationName);
}
正如我所读,它应该在 EF Core 3.0 中被修复,当它与以下 "breaking change" - Lazy-loading proxies no longer assume navigation properties are fully loaded 一起发布时。它还部分解释了当前的问题:
Old behavior
Before EF Core 3.0, once a DbContext
was disposed there was no way of knowing if a given navigation property on an entity obtained from that context was fully loaded or not.
很遗憾,这不能帮助您解决当前的问题。我看到的选项是:
- 等待 EF Core 3.0 发布
- 不要通过代理使用延迟加载
关闭 lazy loading on disposed context warning - 默认为 Throw
,将其更改为 Log
或 Ignore
,例如:
optionsBuilder.ConfigureWarnings(warnings => warnings
.Log(CoreEventId.LazyLoadOnDisposedContextWarning)
);
我正在使用 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 从数据库生成的部分 类。 Lead
、LeadOrganization
、LeadOrganizationAddress
和 AddressType
是 类 基本上 1:1 具有属性方面的部分,但具有静态方法添加(是的,这很奇怪,但这是我必须处理的)。
Lead 有一个 LeadOrganization,LeadOrganization 又至少有一个 LeadOrganizationAddress,而 LeadOrganizationAddress 又有一个 AddressType。
当 GetLead
调用 Lead
构造函数时,查询中的数据尚未加载,尽管它应该预先加载。这会导致嵌套对象的问题。当它最终到达 LeadOrganizationAddress
构造函数时,DbContext 已被释放,因此无法延迟加载关联的 AddressType
.
我是不是误解了预加载的全部意义?我认为它会在初始查询时检索所有数据,然后让我毫无问题地将其传递给构造函数。我不需要继续返回数据库并延迟加载任何东西。
如果启用了延迟加载,是否可以简单地不进行预加载?是否有其他解决方法,例如强制它加载任何代理实体?
延迟加载并不是阻止属性实例化的原因,而是缺少适当的构造函数。
除了 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 通过属性来混合对象,至少向您的 类.
添加一个无参数的构造函数我假设您使用 UseLazyLoadingProxies()
但想禁用查询中特定包含的延迟加载。这还没有实现:
https://github.com/aspnet/EntityFrameworkCore/issues/10787
你现在唯一能做的事:
1.) 禁用延迟加载代理 ("default lazy loading for all properties")
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=>lo.Addresses)
,对于延迟加载使用 .Include(lo=>lo.AddressesLazy)
编辑 1
不应为所有属性默认启用 IMO 延迟加载 - 这可能会影响整个实现的性能。因此,在延迟加载为您带来优势的情况下,上述解决方案是一种替代方案。我也想在每个包含中都有这个选项,比如 .Include(o=>o.Addresses, LoadingBehaviour.Eager)
- 也许将来会存在。
好的,在调查问题后,EF Core 2.x 通过代理实现延迟加载存在问题。相关的跟踪问题是
- #15170: Eager loading include with using UseLazyLoadingProxies
- #12780: Store IsLoaded flags in proxy for better lazy-loading experience
问题是导航属性 是 急切加载的,但是 LazyLoader
不知道在处理时 - 无法安全地访问上下文更改跟踪器,只是抛出异常。相关代码可见here,第一行:
if (_disposed)
{
Logger.LazyLoadOnDisposedContextWarning(Context, entity, navigationName);
}
正如我所读,它应该在 EF Core 3.0 中被修复,当它与以下 "breaking change" - Lazy-loading proxies no longer assume navigation properties are fully loaded 一起发布时。它还部分解释了当前的问题:
Old behavior
Before EF Core 3.0, once a
DbContext
was disposed there was no way of knowing if a given navigation property on an entity obtained from that context was fully loaded or not.
很遗憾,这不能帮助您解决当前的问题。我看到的选项是:
- 等待 EF Core 3.0 发布
- 不要通过代理使用延迟加载
关闭 lazy loading on disposed context warning - 默认为
Throw
,将其更改为Log
或Ignore
,例如:optionsBuilder.ConfigureWarnings(warnings => warnings .Log(CoreEventId.LazyLoadOnDisposedContextWarning) );