实体框架中的缓存如何工作?

Res*_*aos 1 caching ncache entity-framework-core asp.net-core ef-core-6.0

我看到大量关于人们努力让 EF 不发送缓存数据的帖子。我坐在这里想知道他们如何让它发送缓存数据......

以下是详细信息:使用 Entity Framework Core 6.0.6 的 .NET 6.0 上的 ASP.NET Core 最新版本。通过 Swagger 站点页面测试控制器方法。DbContext 具有作用域并注入依赖项。未使用 AsNoTracking。

该方法非常简单_DbContext.Set.ToListAsync()。

  1. 我称该方法为。
  2. 直接更新数据库中先前从该方法返回的记录之一的值(通过 Sql Management Studio,而不是同一服务)。
  3. 再次调用该方法。

我希望第二次调用具有“陈旧”数据,即与第一次调用相同的数据。但是,它有我从数据库中更新的数据!?第二次调用要快得多,我认为这是因为 EF 缓存了我的查询和元数据,但它似乎没有缓存查询的实际结果。

根据我的测试,它看起来只会缓存同一请求中的结果。一旦请求完成,DbContext 就会被释放,然后结果缓存就会消失。正确的?因此,如果我想在不同的 Web 请求之间保留结果缓存,那么我必须通过 NCache、其他一些包或自己编写代码来实现自己的缓存?

Gur*_*ron 7

实体框架依赖于跟踪机制(请参阅)。它本身不是缓存,但它可以产生类似的效果,尽管主要目标之一是跟踪更改,以便将它们传播到数据库。

更改跟踪“绑定”到上下文的具体实例(即不在不同实例/全局之间共享)。变更跟踪的重要功能之一是身份解析

由于跟踪查询使用更改跟踪器,因此 EF Core 将在跟踪查询中进行身份解析。当具体化实体时,EF Core 将从更改跟踪器返回相同的实体实例(如果已被跟踪)。如果结果多次包含相同的实体,则每次出现都会返回相同的实例。无跟踪查询不使用更改跟踪器,也不进行身份解析。因此,即使结果中多次包含同一实体,您也会返回实体的新实例。

如果数据库在同一上下文中的请求之间“外部”更新,这可能会导致数据过时:

using (var ctx1 = new AppContext())
{
    var entity = await ctx1.SomeEntity
        .Where(e => e.Id == 1)
        .FirstOrDefaultAsync();

    using (var ctx2 = new AppContext())
    {
        var theSameEntity = await ctx2.SomeEntity
            .Where(e => e.Id == 1)
            .FirstOrDefaultAsync();
        theSameEntity.SomeTextField = "Updated"; // assuming it had another value
        await ctx2.SaveChangesAsync();
    }

    var entity1 = await ctx1.SomeEntity
        .Where(e => e.Id == 1)
        .FirstOrDefaultAsync()
    var referenceEquals = object.ReferenceEquals(entity, entity1); // True
    var field = entity1.SomeTextField; // field will have the original value, not "Updated"
}
Run Code Online (Sandbox Code Playgroud)

第二次调用要快得多,我相信这是因为 EF 缓存了我的查询和元数据

其潜在原因有多种:

  • 如果第一个查询是在此应用程序运行中第一次执行此查询,则 EF Core 可以缓存查询翻译结果,以便在后续查询(跨上下文实例)中重用。此外,如果实体已被跟踪,则 EF 的当前迭代将不会重新创建和重新映射获取的数据。不确定我是否记得正确,但如果已请求已跟踪的实体,EF/EF Core 的先前迭代可能会完全跳过查询数据库,但我在这里可能是错的。

还有几个因素影响“启动时间”,即在应用程序中第一次使用DbContext该类型时执行第一个操作的时间。DbContext

但它似乎没有缓存查询的实际结果。

它同时是部分正确和部分错误的。如前所示 - 如果数据实际上已更改,EF 将不会更新跟踪的实体。但如果数据被删除,EF 会发现:

using (var ctx1 = new AppContext())
{
    var entity = await ctx1.SomeEntity
        .Where(e => e.Id == 1)
        .FirstOrDefaultAsync();

    using (var ctx2 = new AppContext())
    {
        var theSameEntity = await ctx2.SomeEntity
            .Where(e => e.Id == 1)
            .FirstOrDefaultAsync();
        ctx2.DeliveryComment.Remove(theSameEntity); // remove
        await ctx2.SaveChangesAsync();
    }

    var entity1 = await ctx1.SomeEntity
        .Where(e => e.Id == 1)
        .FirstOrDefaultAsync()
    var isNull = entity1 == null; // True
}
Run Code Online (Sandbox Code Playgroud)