在具有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'方法,可以抑制或记录该异常。
似乎在序列化对象时,不存在渴望加载的包含实体的列表。 (或者也许是,但是它仍在尝试再次加载它们?还是以某种方式查询上下文?)很自然,上下文实例早已被处理掉了,只应缓存完全实现的列表。
当我调试时,顶层列表确实是从缓存中返回的。但是在检查后,其中任何对象的GlobalRoleFeatures
和GlobalRoleCompanyGroupRoles
属性都会导致上述相同的异常。
注意:在查询上使用.ToListAsync()
并在async
到控制器操作之间一直使用.GetOrCreateAsync()
,行为相同。
我在俯视什么吗?有没有办法将不再依赖于数据库上下文的完全实例化列表放入内存缓存中?
答案 0 :(得分:1)
问题出在使用IMemoryCache
。实际上,您不是将项目序列化到缓存中。对象直接缓存在内存中,这意味着它们与DbContext
之类的事物之间的联系会持久,即使DbContext
并非如此。
具体来说,延迟加载的工作方式是EF实际上创建了您的实体类的动态代理,并用检查EF的客户getter覆盖(因此,需要virtual
关键字)reference或collection属性。物品的对象缓存,如果找不到,则进行查询以获取它们。因为您直接在内存中进行缓存,所以您正在缓存这些代理类实例,这些实例上仍然具有这种逻辑。
无论如何都使用IMemoryCache
是一个坏主意。相反,您应该始终使用IDistributedCache
。如果您仍想实际缓存在内存中,则有一个MemoryDistributedCache
提供程序(实际上是默认设置),但是使用IDistributedCache
可以为您做两件事:
它比IMemoryCache
更通用,因此您以后可以在任何缓存提供程序(Redis,SQL Server等)中使用子程序,而无需更改应用程序代码。
专门针对您的问题,即使您使用内存缓存提供程序,它也会强制实际序列化缓存值,这意味着您不会遇到同样的非显而易见的问题
这确实意味着还有更多工作要做。您需要使用类似JsonConvert
的东西来对缓存进行序列化和反序列化,但是您可以在IDistributedCache
上添加扩展名,以便为您处理。