[C#] Entity Framework Core -变更跟踪器-

变更跟踪器

你好,我是Eita。

过去,我曾总结过 EF Core 中关系是如何建立的。

我之前使用过 Laravel (Eloquent),所以觉得 EF Core 的“即使没有明确指定也能推断关系”的机制有点奇怪。

在上一篇文章中,我以这种不安感为出发点,组织了 EF Core 如何解释实体之间的关系并将其视为内部模型。

[C#] Entity Framework Core - 关系建立条件 -

[C#] Entity Framework Core - 关系建立条件 -

考虑到这一点,让我们来阐明EF Core 如何管理实体变更,特别是通过其ChangeTracker

在工作中,我遇到了即将发生意外提交的情况。

  • EF Core 会记住哪些更改?
  • DbContextChangeTracker 的管理单元是什么
  • 实施过程中需要注意的时机

我把这个留在这里作为个人备忘录。

我希望这对某些人的组织工作有所帮助。

ChangeTracker是什么?

首先,ChangeTrackerDbContext 当前跟踪的实体状态的机制。

在 EF Core 中, DbContext会记住从数据库中检索到的实体,以及已添加更新删除的实体。

然后,当调用SaveChanges()时,将根据ChangeTracker管理的状态执行INSERT / UPDATE / DELETE操作。

简而言之,二者关系如下:

  • 数据库上下文:数据库操作单元
  • ChangeTracker:用于跟踪给定工作单元内发生的变化的工具。
  • SaveChanges():反映数据库中已记住的更改。

换句话说,EF Core 不会每次都手动编写 SQL;相反,它会根据 DbContext 的当前状态确定必要的 SQL

实体状态

ChangeTracker 管理处于多种状态的实体。

典型情况如下:

情况 意义 执行 SaveChanges 时会发生什么?
分离 DbContext 未跟踪 什么都不做
未改变 获取数据库后,未做任何更改。 什么都不做
额外 新增 插入
修改的 已更改 英语:
已删除 已安排删除 删除

例如,如果您更改从数据库中检索到的实体的值并调用SaveChanges() ,EF Core 将检测到更改并执行UPDATE

// 从数据库检索:未更改的 var user = context.Users.First(); // 更新属性:已修改的 user.Name = "更改后"; // 调用 SaveChanges() 时检测到更改并执行 UPDATE 操作 context.SaveChanges();

关键在于SaveChanges() 后,更改才会反映到数据库中

ChangeTracker 只是 记住 DbContext 中的“计划更改”

ChangeTracker 的状态何时发生变化?

接下来,我们来整理一下实体状态发生变化的时刻。

定时 例子 情况
当使用常规查询从数据库中检索时 Context.Users.FirstAsync() 未改变
当你添加一个新的 context.Users.Add(user) 额外
new` 创建的对象 Context.Users.Attach(user) 未改变
当您更改受跟踪实体的属性时 user.Name = "更改后" 修改的
当您想要强制更新一个分离的对象时。 Context.Users.Update(user) 修改的
当您选择删除时 Context.Users.Remove(user) 已删除

通常情况下,EF Core 中的常规查询 会被跟踪

因此,按如下方式检索的实体 DbContext 中进行跟踪

var users = context.Users.ToList();

另一方面,AsNoTracking() 不会发生跟踪

var users = context.Users.AsNoTracking().ToList();

在这种情况下,即使您更改了检索到的用户ChangeTracker也不会记住该更改。

因此,如果数据只是被引用而不被更新,则可以使用AsNoTracking()来避免不必要的跟踪。

我何时会被从变更跟踪器中移除?

ChangeTracker 跟踪的实体

跟踪功能将在以下时间段主要停止运行:

定时 内容
DbContext释放 正常跟踪结束时间
调用ChangeTracker.Clear() 移除DbContext当前跟踪的所有实体。
Entry(entity).State = EntityState.Detached 将特定实体排除在跟踪范围之外。
SaveChanges() 成功删除后 已删除的实体变为分离状态。

SaveChanges() 之后的状态

SaveChanges() 大致如下:

保存更改 保存更改后
额外 未改变
修改的 未改变
已删除 分离

换句话说,即使更改已反映在数据库中,已添加或更新的实体仍将继续被跟踪为“未更改”

另一方面,已删除的实体被视为已从数据库中消失,因此被归类为分离实体

状态转换列表

根据我们目前所讨论的内容,让我们总结一下基本状态转换。

手术 状态转换 影响/补充
新实体 分离 DbContext 尚不清楚该实例的存在。
添加(实体) 分离 → 添加 插入到SaveChanges ( )
从数据库中获取数据? 分离 → 未改变 数据采集​​后的状态,没有任何变化。
更改已获取数据的值。 未更改 → 已修改 由于数值已更改,它将自动更新。
移除(实体) 未更改 → 已删除 我计划从数据库中删除数据
SaveChanges() 成功(添加/更新) 新增/修改 → 未更改 保存已完成,与数据库相比没有任何变化。
SaveChanges() 成功(删除) 已删除 → 已分离 它将从数据库中消失,并从托管列表中移除。
更新(实体) 分离 → 修改 使用`new`创建的数据将作为`UPDATE`的目标,而无需使用 `SELECT`。

注意: DbContext无法感知的状态称为分离状态。例如,您使用`new`创建的对象,或者使用`AsNoTracking()`获取的实体,不会被跟踪,并且处于DbContext无法感知的状态。

使用 ChangeTracker 时您应该了解的事项

不包含在追踪范围内(因为没有追踪信息)

如前所述,如果数据只是被引用而没有被更新,则可以使用AsNoTracking()将其从ChangeTracker跟踪中排除。

var users = context.Users.AsNoTracking().ToList();

只读处理避免了不必要的跟踪,从而防止了内存浪费和意外更新。

基于 DbContext 进行管理

ChangeTracker不是在整个应用程序中共享的;它存在于每个DbContext实例中。

因此,即使它们是同一个实体,但以下事物却是不同的。

  • A 的 DbContext 中跟踪的状态
  • B 的 DbContext 中正在跟踪的状态

如果您不了解这个单位,

  • 能记住多少次变化?
  • 为什么这一变化也反映在了SaveChanges()中?

这将使理解变得困难。

注意:DbContext 生命周期

DbContext 由 DI 注册期间配置的设置决定。

当您在 ASP.NET Core Web 应用程序中使用AddDbContext注册 DbContext 时,默认情况下DbContext注册为Scoped

因此,通常在单个请求中使用同一个DbContext实例。

官方文档指出, AddDbContext默认注册为 Scoped,并且在许多 ASP.NET Core 应用程序中,每个请求都将具有不同的作用域,即不同的DbContext

DbContext 生命周期

要检查DbContext如何在您自己的代码中注册,请参阅以下文件中的注册部分等。

  • Startup.cs
  • Program.cs

实施过程中需要注意的时机

ChangeTracker 是一个方便的工具,但如果您不清楚自己正在跟踪什么,则可能会导致意外的更新。

我个人认为,在以下情况下,您应该格外小心。

定时 出现的问题
回滚/当发生异常时 即使数据库端的事务被撤销, ChangeTracker也不会自动恢复到回滚之前的状态,这可能导致数据库中的状态与DbContext 记住的状态之间存在差异。
长时间批量处理等。 先前处理的数据会继续在跟踪列表中累积,这可能会导致内存浪费和意外地参与更新。
只读查询 如果即使仅用于在屏幕上显示信息而启用跟踪功能,也可能导致不必要的内存消耗,并可能导致意外更新。

此外,我认为关于这些事项应该牢记以下几点。

  • 使用后:请丢弃物品,不要不必要地重复使用。注意物品的使用寿命。
  • 如果无法将它们分开, Clear():在回滚后立即手动重置跟踪状态,例如,在同一上下文中处理错误时。
  • 如果您不打算更新 请使用 AsNoTracking():将此添加到不保存数据的地方,例如列表显示,以避免不必要的跟踪。

ChangeTracker究竟有哪些实用之处?

到目前为止,我们已经重点关注并整理了有关ChangeTracker 的状态管理和注意事项的信息。

因此,ChangeTracker 的优点

1. 同一个实体可以被视为同一个实例。

当在同一个DbContext中由ChangeTracker管理时,如果DbContext中存在多个具有相同主键的实体,则很难确定哪个实例的状态应该保存为“正确”。

为了避免这种情况,EF Core 将同一DbContext中具有相同主键的实体视为单个实例(身份解析)。

身份解析ChangeTracker 的必要机制,

同一个 DbContext 中多次处理相同的数据,也可以将其视为同一个实例,这非常方便
(当然,您仍然需要知道正在跟踪哪个实体。)

2. 它根据实体的状态和关系自动确定必要的 SQL。

如上所述,INSERTUPDATEDELETE 所需的 SQL 语句,

此外,如果实体之间的关系已正确建立,EF Core 将处理必要的 SQL 执行顺序和外键绑定,包括链接到父实体的子实体。

var user = new User { Name = "山田太郎", Orders = new List<Order> { new Order { ProductName = "产品 A" }, new Order { ProductName = "产品 B" } } }; context.Users.Add(user); context.SaveChanges();

在这个例子中,用户订单 之间的关系 IModel 中正确构建

因此,在注册用户后,系统会将分配的用户 ID 作为订单数据的外部密钥进行关联,然后注册订单数据。

换句话说,ChangeTracker 不仅管理单个实体的状态,还管理相关实体的状态。

然后, SaveChanges() ,EF Core 会使用该状态信息和模型信息来确定并执行必要的 SQL 及其执行顺序。

让我们简要总结一下 ChangeTracker 的工作流程。

最后,ChangeTracker 如何记住更改并将其反映到数据库中的过程

使用 WorkTracker 进行变更管理工作流程

通过 DbContext 检索实体 ↓ ChangeTracker 跟踪实体(默认状态为 Unchanged) ↓ 执行属性更改/添加/删除等操作 ↓ ChangeTracker 中的状态发生变化(已修改/已添加/已删除) ↓ 调用 SaveChanges() ↓ EF Core 根据 ChangeTracker 保存的状态执行 UPDATE/INSERT/DELETE 操作 ↓ 状态更新为已保存的状态(已修改/已添加变为 Unchanged,已删除变为 Detached)

概括

到目前为止, ChangeTracker 的作用

ChangeTracker 管理实体状态的调用 SaveChanges() 会将必要的更改反映到数据库中

它会自动检测检索到的实体的更改,并生成必要的 SQL 查询,这样您就可以在数据库中反映对象更改,而无需显式编写详细的更新代码。

另一方面,如果您不清楚正在跟踪的内容,SaveChanges() 时可能会在数据库中反映出意外的更改

因此,在使用 EF Core 时,

  • 数据库中有什么内容?
  • 当前的 DbContext 记住了什么?

我意识到了解这两种观点都很重要。

就是这样!!

如果您觉得这篇文章对您有帮助,请点个“赞”!
1
1
7
X Facebook Hatena书签 口袋

这篇文章的作者

关于作者

瑛塔

一名网页开发工程师,
只想出去闯闯。