SingleOrDefault和FirstOrDefault返回缓存的数据

时间:2019-07-16 16:10:40

标签: c# entity-framework-core

我之前编写的一些代码使用Find()方法通过主键检索单个实体:

return myContext.Products.Find(id)

之所以奏效,是因为我将这段代码归入了一个通用类,并且每个实体都有一个不同的字段名称作为其主键。

但是我不得不替换代码,因为我注意到它正在返回缓存的数据,并且我需要它在每次调用时从数据库返回数据。 Microsoft的文档确认这是Find()的行为。

因此,我将代码更改为使用SingleOrDefaultFirstOrDefault。我没有在文档中找到任何说明这些方法返回缓存数据的内容。

现在我正在执行以下步骤:

  1. 通过EF保存实体。
  2. 在SSMS中执行UPDATE语句以更新最近保存的 记录的“描述”字段。
  3. 使用SingleOrDefault将实体检索到新的实体变量中 或FirstOrDefault

返回的实体在Description字段中仍具有旧值。

我已经运行了SQL跟踪,并验证了在步骤3中是否正在查询数据。这让我感到困惑-如果EF正在往返数据库,为什么它返回缓存的数据?

我已经在线搜索了,大多数答案都适用于Find()方法。此外,他们提出了一些仅作为解决方法的解决方案(部署DbContext并实例化一个新解决方案)或对我不起作用的解决方案(使用AsNoTracking()方法)。

如何从数据库中检索实体并绕过EF缓存?

2 个答案:

答案 0 :(得分:1)

Microsoft How Queries Work文章第3点中描述了您看到的行为:

  
      
  1. 对于结果集中的每个项目
  2.   
     
    

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;

它就像一个魔法

相关问题