我之前编写的一些代码使用Find()
方法通过主键检索单个实体:
return myContext.Products.Find(id)
之所以奏效,是因为我将这段代码归入了一个通用类,并且每个实体都有一个不同的字段名称作为其主键。
但是我不得不替换代码,因为我注意到它正在返回缓存的数据,并且我需要它在每次调用时从数据库返回数据。 Microsoft的文档确认这是Find()
的行为。
因此,我将代码更改为使用SingleOrDefault
或FirstOrDefault
。我没有在文档中找到任何说明这些方法返回缓存数据的内容。
现在我正在执行以下步骤:
SingleOrDefault
将实体检索到新的实体变量中
或FirstOrDefault
。返回的实体在Description
字段中仍具有旧值。
我已经运行了SQL跟踪,并验证了在步骤3中是否正在查询数据。这让我感到困惑-如果EF正在往返数据库,为什么它返回缓存的数据?
我已经在线搜索了,大多数答案都适用于Find()
方法。此外,他们提出了一些仅作为解决方法的解决方案(部署DbContext
并实例化一个新解决方案)或对我不起作用的解决方案(使用AsNoTracking()
方法)。
如何从数据库中检索实体并绕过EF缓存?
答案 0 :(得分:1)
Microsoft How Queries Work文章第3点中描述了您看到的行为:
- 对于结果集中的每个项目
a。如果这是一个跟踪查询,EF将检查数据是否代表上下文实例的变更跟踪器中已有的实体
- 如果是,则返回现有实体
在this blog post中有更好的描述:
事实证明,实体框架使用Identity Map模式。这意味着一旦将具有给定键的实体加载到上下文的缓存中,只要存在该上下文,就永远不会再次加载。因此,当我们第二次访问数据库以获取客户时,它从数据库中检索了更新的
851
记录,但是由于客户851
已被加载到上下文中,因此它会从数据库中忽略更新的记录。数据库(more details)。
所有这些都说明,如果您进行查询,它将首先检查主键以查看其是否已在缓存中。如果是这样,它将使用缓存中的内容。
如何避免呢?首先是要确保您的DbContext
对象没有存活太久。 DbContext
对象仅设计用于一个工作单元。如果将其放置太久会发生坏事,例如过多的内存消耗。
DbContext
以获取数据并丢弃该DbContext
。DbContext
,更新记录并丢弃该DbContext
。这就是为什么在ASP.NET Core中使用EF Core with dependency injection时,它的创建期限为scoped,所以任何DbContext
对象只能生存一个生命HTTP请求。
在极少数情况下,您确实确实确实需要获取已有对象的记录的新数据,可以像这样使用EntityEntry.Reload() / EntityEntry.ReloadAsync:
myContext.Entry(myProduct).Reload();
如果您只知道ID,那对您没有帮助。
如果您真的真的需要重新加载仅拥有ID的实体,则可以执行以下操作:
private Product GetProductById(int id) {
//check if it's in the cache already
var cachedEntity = myContext.ChangeTracker.Entries<Product>()
.FirstOrDefault(p => p.Entity.Id == id);
if (cachedEntity == null) {
//not in cache - get it from the database
return myContext.Products.Find(id);
} else {
//we already have it - reload it
cachedEntity.Reload();
return cachedEntity.Entity;
}
}
但是,再次,这仅应在有限的情况下使用,因为您已经解决了任何长期存在的DbContext
对象的情况,因为不必要的缓存并不是唯一的结果。
答案 1 :(得分:0)
好的,我也遇到了同样的问题,终于找到答案了,
你做的一切都是正确的,这就是 EF 的工作方式。
您可以将 .AsNoTracking()
用于您的目的:
return myContext.Products.AsNoTracking().Find(id)
确保您在顶部添加了using Microsoft.EntityFrameworkCore;
。
它就像一个魔法