EF Core Include 仍然是延迟加载?

EF Core Include is Still Lazy Loading?

在带有 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: Error getting value from 'GlobalRoleCompanyGroupRoles' on 'Castle.Proxies.GlobalRoleProxy'. ---> System.InvalidOperationException: Error generated for warning 'Microsoft.EntityFrameworkCore.Infrastructure.LazyLoadOnDisposedContextWarning: An attempt was made to lazy-load navigation property 'GlobalRoleCompanyGroupRoles' on entity type 'GlobalRoleProxy' after the associated DbContext was disposed.'. This exception can be suppressed or logged by passing event ID 'CoreEventId.LazyLoadOnDisposedContextWarning' to the 'ConfigureWarnings' method in 'DbContext.OnConfiguring' or 'AddDbContext'.

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

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

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

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

问题是使用 IMemoryCache。事实上,您并没有将项目序列化到您的缓存中。对象直接缓存在内存中,这意味着它们与 DbContext 之类的东西的联系会持续存在,即使 DbContext 不会。

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

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

  1. 它比IMemoryCache更通用,所以你可以稍后在任何缓存提供者(Redis,SQL服务器等) .) 无需更改您的应用程序代码。

  2. 特别针对您的问题,它将强制您实际序列化缓存值,即使使用内存缓存提供程序,这意味着您不会有同样的不明显问题。

这确实意味着需要多做一些工作。您需要使用 JsonConvert 之类的东西来序列化和反序列化 to/from 缓存,但您可以向 IDistributedCache 添加扩展来为您处理。