【问题标题】:AsNoTracking() doesn't return unsaved changesAsNoTracking() 不返回未保存的更改
【发布时间】:2020-06-20 14:58:05
【问题描述】:

Entity Framework Core 中 AsNoTracking() 的文档说,在保存数据库上下文时,对它的任何编辑都不会被持久化。 我注意到使用 AsNoTracking() 时的另一个区别,即如果数据库上下文有未保存的编辑并且您使用 AsNoTracking() 查询它,则不会返回这些更改。 该文档听起来好像只有对 AsNoTracking() 查询所做的编辑不会在保存时被跟踪和持久化,但似乎返回的内容也会有所不同。

如果这确实是预期的行为,我不确定最佳设计模式。 我在所有只读查询中都使用了 AsNoTracking(),但这意味着我有一个错误,因为我的设计是这样的:

修改数据的控制器端点:

  1. 在服务中调用某些可能会或可能不会改变数据库的东西
  2. 调用服务中的其他内容,使用 AsNoTracking() 进行只读查询
  3. 控制器保存数据库上下文

其目的是任何控制器端点都可以调用任意数量的服务方法,这些服务方法可能会或可能不会改变数据库,数据库上下文是限定范围的,因此它们在调用之间共享,最终控制器会持久保存更改。 问题是上面的#2 不会返回在#1 中完成的更改。这应该如何解决?这些服务可以调用其他服务,这些服务可能会从已经修改过的地方获取一些数据,所以我不能只是到处传递模型。 我应该从任何地方删除 AsNoTracking() 并收工吗?或者我应该在每次写入后添加一个保存调用?或者还有什么我可以做的吗?

TLDR:我希望在只读查询中使用 AsNoTracking() 以提高速度,但它不会返回任何未保存的更改。我应该删除 AsNoTracking(),每次编辑后保存,还是有更好的方法?

编辑: 这是我的意思的sn-p;任何带有 AsNoTracking() 的查询都会忽略在保存之前对上下文所做的任何编辑,这让我想知道 AsNoTracking() 到底有什么用处:

var userSessionEntry = await this.mainContext.Sessions
    .Where(t => t.AccountId == session.AccountId).FirstAsync();

userSessionEntry.AccountId = Guid.Empty;

var userSessionEntry2 = await this.mainContext.Sessions
    .Where(t => t.AccountId == session.AccountId).AsNoTracking().FirstAsync();

Console.WriteLine(userSessionEntry2.AccountId); // prints original AccountId and not an empty id

编辑 2: 我正在使用 Entity Framework Core 的最新预览版; 5.0.0-preview.5.20278.

谢谢。

【问题讨论】:

  • edit您的问题包含您拥有的源代码,该代码显示您如何使用AsNoTracking()读取数据,您如何在上下文中未保存的更改,您如何尝试从上下文以及您如何知道数据不正确。如何提供代码请见minimal reproducible example
  • 嗨@Progman,感谢您的回复。我不确定你的意思是什么,例如“你怎么知道数据不正确”?你在帖子中挑战这个主张吗?我添加了“证明”,即在保存更改之前,在后续查询中使用 AsNoTracking() 不会将修改后的条目返回到问题中。我没有代码问题所以无法提供样品,这是设计问题。
  • 你有哪个版本的EF核心?
  • 嗨@GuruStron!我正在使用最新的预览版 5.0.0-preview.5.20278。

标签: c# asp.net-core entity-framework-core


【解决方案1】:

AsNoTracking 的工作方式是它始终会绕过DbContext 自己的缓存(更改跟踪)实体,并直接在数据库上执行查询。这就是定义的含义。缓存的数据可能与基础数据库数据不同,假设其他人对您使用的相同实体进行了更改。

但是,根据您的设计,如果您的控制器中的所有服务都使用完全相同的 DbContext 实例,那么您会没事的。有一些方法可以通过将数据库上下文的作用域依赖注入到您拥有的任何服务来做到这一点。这样,您的服务请求的所有部分都应该使用相同的实例。

如果您一直需要最新的数据,那么您需要使用AsNoTracking 进行所有查询,以便您始终访问数据库以获取最新数据。

您仍然可以对不再跟踪更改的实体进行编辑,但需要一些额外的代码:

var manager = await DbContext.Set()
    .AsNoTracking()
    .Where(x => x.IsManager)
    .ToListAsync();

foreach(经理中的 var 经理)
{
    manager.Salary += 10000;

    var dbEntry = DbContext.DbEntry(manager);
    dbEntry.Property(x => x.Salary).IsModified = true;
}

等待 DbContext.SaveChangesAsync();

您可以使用上述策略来确保始终使用最新数据。如果您有 1000 名用户在积极使用您的服务,这实际上会对您的数据库造成很大影响,因此需要一些缓存策略。

【讨论】:

  • 非常感谢您的澄清,我真的很感激。 docs.microsoft.com/en-us/ef/core/querying/tracking 说“当结果用于只读场景时,没有跟踪查询很有用。它们执行得更快,因为不需要设置更改跟踪信息。”,所以我在中使用了 AsNoTracking()所有只读查询。这些查询驻留在最常直接从控制器调用的服务中,为客户端提供返回值。但即使在某些可能改变了数据的代码中,我也会重用相同的方法。
  • 我想知道我是否应该在任何地方删除 AsNoTracking(),或者我是否应该在 getter 方法中添加一个标志,以确定是否使用 AsNoTracking() 并在直接从控制器调用时将其打开(到在不涉及数据设置的控制器调用的只读场景中保持更快的执行?您对此有最佳模式的建议吗?再次感谢您的回复!
  • 抱歉,我没有“最佳模式”,但我可以谈谈我们对项目所做的工作。在处理实体时,我们通常会实施“断开连接”的方法,这意味着对于几乎所有事情,我们不会过多使用更改跟踪器(如果有的话)。大多数情况下,我们手动管理更改跟踪器。所以...AsNoTracking 无处不在。 :D
  • 使用 change-tracker 的自动模式对于非常小的应用程序可能是有益的,但是一旦它增长到一定的大小,您将比任何东西都更需要性能。在这种情况下,AsNoTracking 所有的事情都将是那个答案,同时仍然从 EF 获得帮助以实际为您的数据库命令生成最佳 SQL。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-03-18
  • 1970-01-01
  • 2017-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多