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

你好,我是Eita。
过去,我曾总结过 EF Core 中关系是如何建立的。
我之前使用过 Laravel (Eloquent),所以觉得 EF Core 的“即使没有明确指定也能推断关系”的机制有点奇怪。
在上一篇文章中,我以这种不安感为出发点,组织了 EF Core 如何解释实体之间的关系并将其视为内部模型。
[C#] Entity Framework Core - 关系建立条件 -
考虑到这一点,让我们来阐明EF Core 如何管理实体变更,特别是通过其ChangeTracker 。
在工作中,我遇到了即将发生意外提交的情况。
- EF Core 会记住哪些更改?
DbContext和ChangeTracker的管理单元是什么- 实施过程中需要注意的时机
我把这个留在这里作为个人备忘录。
我希望这对某些人的组织工作有所帮助。
ChangeTracker是什么?
首先,ChangeTracker 是DbContext 当前跟踪的实体状态的机制。
在 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如何在您自己的代码中注册,请参阅以下文件中的注册部分等。
Startup.csProgram.cs
实施过程中需要注意的时机
ChangeTracker 是一个方便的工具,但如果您不清楚自己正在跟踪什么,则可能会导致意外的更新。
我个人认为,在以下情况下,您应该格外小心。
| 定时 | 出现的问题 |
|---|---|
| 回滚/当发生异常时 | 即使数据库端的事务被撤销, ChangeTracker也不会自动恢复到回滚之前的状态,这可能导致数据库中的状态与DbContext 记住的状态之间存在差异。 |
| 长时间批量处理等。 | 先前处理的数据会继续在跟踪列表中累积,这可能会导致内存浪费和意外地参与更新。 |
| 只读查询 | 如果即使仅用于在屏幕上显示信息而启用跟踪功能,也可能导致不必要的内存消耗,并可能导致意外更新。 |
此外,我认为关于这些事项应该牢记以下几点。
- 使用后:请丢弃物品,不要不必要地重复使用。注意物品的使用寿命。
- 如果无法将它们分开,
Clear():在回滚后立即手动重置跟踪状态,例如,在同一上下文中处理错误时。 - 如果您不打算更新
请使用 AsNoTracking():将此添加到不保存数据的地方,例如列表显示,以避免不必要的跟踪。
ChangeTracker究竟有哪些实用之处?
到目前为止,我们已经重点关注并整理了有关ChangeTracker 的状态管理和注意事项的信息。
因此,ChangeTracker 的优点
1. 同一个实体可以被视为同一个实例。
当在同一个DbContext中由ChangeTracker管理时,如果DbContext中存在多个具有相同主键的实体,则很难确定哪个实例的状态应该保存为“正确”。
为了避免这种情况,EF Core 将同一DbContext中具有相同主键的实体视为单个实例(身份解析)。
身份解析ChangeTracker 的必要机制,
同一个 DbContext 中多次处理相同的数据,也可以将其视为同一个实例,这非常方便
(当然,您仍然需要知道正在跟踪哪个实体。)
2. 它根据实体的状态和关系自动确定必要的 SQL。
如上所述,INSERT 、 UPDATE 和 DELETE 所需的 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记住了什么?
我意识到了解这两种观点都很重要。
就是这样!!
