【问题标题】:EF Core Include is Still Lazy Loading?EF Core Include 仍然是延迟加载?
【发布时间】:2019-08-05 07:10:17
【问题描述】:

在一个带有 EF 核心的 Asp.Net Core 2.2 项目中(最新的一切,今天运行了所有 NuGet 更新)我有这个操作:

return Ok(_db.GlobalRoles
             .Include(gr => gr.GlobalRoleFeatures)
                 .ThenInclude(grf => grf.Feature)
             .Include(gr => gr.GlobalRoleCompanyGroupRoles)
                 .ThenInclude(grcgr => grcgr.CompanyGroupRole)
                     .ThenInclude(cgr => cgr.CompanyGroupRoleFeatures)
                         .ThenInclude(cgrf => cgrf.Feature)
             .ToList());

在大多数情况下,细节并不重要,只要说它是我想要预先加载的实体树就足够了。当我分析数据库时,这最终会导致 4 个查询。起初我发现这出乎意料,但不以为然,因为这可能只是 EF 如何优化获取这些结果。没什么大不了。并且得到的数据是正确的。

但是当我把它包裹在IMemoryCache:

return Ok(_cache.GetOrCreate(nameof(GlobalRole), entry =>
{
    entry.SlidingExpiration = TimeSpan.FromMinutes(_appSettings.DataCacherExpiryMinutes);
    return _db.GlobalRoles
              .Include(gr => gr.GlobalRoleFeatures)
                  .ThenInclude(grf => grf.Feature)
              .Include(gr => gr.GlobalRoleCompanyGroupRoles)
                  .ThenInclude(grcgr => grcgr.CompanyGroupRole)
                      .ThenInclude(cgr => cgr.CompanyGroupRoleFeatures)
                          .ThenInclude(cgrf => cgrf.Feature)
              .ToList();
}));

虽然此数据的第一次提取按预期工作,但随后从缓存中提取会导致异常:

Newtonsoft.Json.JsonSerializationException:从“Castle.Proxies.GlobalRoleProxy”上的“GlobalRoleCompanyGroupRoles”获取值时出错。 ---> System.InvalidOperationException:为警告“Microsoft.EntityFrameworkCore.Infrastructure.LazyLoadOnDisposedContextWarning”生成错误:在关联的 DbContext 被处置后,尝试在实体类型“GlobalRoleProxy”上延迟加载导航属性“GlobalRoleCompanyGroupRoles”。通过将事件 ID 'CoreEventId.LazyLoadOnDisposedContextWarning' 传递给 'DbContext.OnConfiguring' 或 'AddDbContext' 中的 'ConfigureWarnings' 方法,可以抑制或记录此异常。

似乎在序列化对象时,预先加载的包含实体列表不存在。 (或者也许它们是,但它仍在尝试再次加载它们?或者以某种方式查询上下文?)当然,上下文实例早就被释放了,应该缓存完全物化的列表。

当我调试时,顶级列表确实是从缓存中返回的。但检查后,其中任何对象的GlobalRoleFeaturesGlobalRoleCompanyGroupRoles 属性都会导致上述相同的异常。

注意:在查询中使用 .ToListAsync()async 一直到 .GetOrCreateAsync() 和控制器操作的行为相同。

我是否忽略了什么?有没有办法将不再依赖于 DB 上下文的完全物化列表放入内存缓存中?

【问题讨论】:

    标签: asp.net-core-webapi asp.net-core-2.2 ef-core-2.2


    【解决方案1】:

    问题在于使用IMemoryCache。实际上,您并没有将项目序列化到缓存中。对象直接缓存在内存中,这意味着它们与DbContext 之类的关系仍然存在,即使DbContext 不存在。

    具体来说,延迟加载的工作方式是 EF 实际上创建实体类的动态代理,并使用检查 EF 对象缓存的客户 getter 覆盖(因此需要 virtual 关键字)引用或集合属性对于这些项目,如果找不到,则进行查询以获取它们。因为您直接在内存中缓存,所以您正在缓存这些代理类实例,它们仍然具有此逻辑。

    无论如何,使用IMemoryCache 是个坏主意。相反,您应该始终使用IDistributedCache。如果您仍想实际缓存在内存中,则有一个 MemoryDistributedCache 提供程序(实际上是默认值),但使用 IDistributedCache 为您做了两件事:

    1. 它比IMemoryCache 更通用,因此您以后可以在任何 缓存提供程序(Redis、SQL Server 等)中进行子化,而无需更改您的应用代码。

    2. 特别是针对您的问题,它会强制您实际序列化缓存值,即使使用内存缓存提供程序,这意味着您不会遇到同样的非显而易见的问题.

    这确实意味着需要做更多的工作。您需要使用 JsonConvert 之类的东西对缓存进行序列化和反序列化,但您可以将扩展名添加到 IDistributedCache 来为您处理。

    【讨论】:

    • 有趣。我有一种(似乎是错误的)印象,即.ToList() 会在将所有内容放入缓存之前将其具体化。手动序列化为IDistributedCache 应该不成问题,而且灵活性对于这个项目来说确实很有用。我试试看,谢谢!
    • .ToList() 确实实现了查询。您在那里拥有完整的对象图,其中包含所有包含的实体。问题是相关项目实际上并不在 类中。正如我所说,有一个自定义 getter 可以从对象缓存中提取这些对象或进行查询以获取它们。您的包含用于填充该对象缓存,因此不需要进行更多查询。然而,在这些对象被从内存缓存中拉出之后,它仍然试图在上下文的对象缓存中找到相关的项目,但是上下文当然已经消失了。这就是问题所在。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多