Core EF 实体跟踪
最近一段时间在学习Core EF,我根据网上教程,和经过相关测试,总结了一些有关于Core EF变更跟踪器ChangeTracker的状态特性,主要目的是做记录,避免遗忘一些细节,仅供参考。
先贴上大佬有关ChangeTracker比较简短的回答
Core EF 实体的5种状态。
Detached
实体未被 DbContext 跟踪。Added
实体是新实体,并且尚未插入到数据库中。 这意味着它们将在调用 SaveChanges 时插入。Unchanged
实体自从数据库中查询以来尚未进行更改。 从查询返回的所有实体最初都处于此状态。Modified
实体自从数据库中查询以来已进行更改。 这意味着它们将在调用 SaveChanges 时更新。Deleted
实体存在于数据库中,但标记为在调用 SaveChanges 时删除。
DbContext 方法
Add 方法添加新实体,将实体状态设置为:Added。
- 导航内现有的实体:
- 注意只有主实体未被跟踪(Detached状态)才会变更导航实体状态
- 原状态为Detached时,修改外键,变更状态为 Added。
- 原状态为Unchanged时,修改外键,变更状态为 Modified。
- 原状态为Modified时,修改外键,状态不变。
- 原状态为Deleted时,修改外键,状态不变。
- 原状态为Added时,修改外键,状态不变。
Remove方法删除实体,将实体状态设置为:Deleted。
- 导航内现有的实体:
- 注意只有主实体未被跟踪(Detached状态)才会变更导航实体状态,其中如果主实体状态是Added,调用该方法会直接将其状态变更为Detached(取消跟踪)
- 设置了联级删除,Cascade,ClientCascade ,变更状态为 Deleted。
- 设置了联级 SetNull,ClientSetNull,修改外键为null,设置状态为:Modified。
- 不管导航是否是跟踪状态,是否存在从属关系,只要主键在数据库中实际存在,都对其标记为删除或修改外键的状态。
Attach方法附加实体,将实体状态设置为:Unchanged。(所有属性IsModified=false)
· Attach方法会将跟踪器中的OriginalValue 原始值重置为修改值,这正好与直接设置EntityEntry.State = EntityState.Unchanged 时会将修改值重置为原始值的特性相反。
· Attach方法对于包含生成的键的实体类型,如果实体设置了其主键值,则会在状态中跟踪该实体Unchanged。 如果未设置主键值,则会在状态中跟踪Added。(导航内实体也符合该特性)
- 导航内现有的实体:
- 注意只有主实体未被跟踪(Detached状态)才会变更导航实体状态
- 原状态为Detached时,修改外键,变更状态为 Unchanged(有主键)。
- 原状态为Unchanged时,修改外键,变更状态为 Modified。
- 原状态为Modified时,修改外键,状态不变。
- 原状态为Deleted时,修改外键,状态不变。
- 原状态为Added时,修改外键,状态不变。
Update方法更新实体,将实体状态设置为:Modified。(所有属性IsModified=true)
· 对于包含生成的键的实体类型,如果实体设置了其主键值,则会在状态中跟踪该实体Modified。 如果未设置主键值,则会在状态中跟踪Added。
- 导航内现有的实体:
- 注意只有主实体未被跟踪(Detached状态)才会变更导航实体状态
- 原状态为Detached时,修改外键,变更状态为 Modified(所有属性IsModified=true)
- 原状态为Unchanged时,修改外键,变更状态为 Modified(其他属性IsModified不变)
- 原状态为Modified时,修改外键,状态不变。(其他属性IsModified不变)
- 原状态为Deleted时,修改外键,状态不变。
- 原状态为Added时,修改外键,状态不变。
· 注意,导航内现有的实体时指:在Attach或Update之前,导航属性中就已经存在的实体,而Attach后由于EF跟踪机制,自动关联的已加载实体不属于现有的。
自动检测更改的方法
- 在EF中,修改属性值后,最终需要通过调用DetectChanges(),比对OriginalValue来检测值是否更改过,从而设置对应的 EntityState 状态以及属性的 IsModified
在一些方法中会自动调用上下文中的ChangeTracker.DetectChanges
- DbContext.SaveChanges 和 DbContext.SaveChangesAsync ,以确保在更新数据库之前检测到所有更改。
- ChangeTracker.Entries() 和 ChangeTracker.Entries<TEntity>() ,以确保实体状态和修改的属性是最新的。
- ChangeTracker.HasChanges(),以确保结果准确无误。
- ChangeTracker.CascadeChanges(),用于在级联之前确保主体/父实体的实体状态正确。
- DbSet<TEntity>.Local,以确保跟踪的关系图是最新的。
在某些位置,只会对单个实体实例进行更改检测,而不是在整个所跟踪实体关系图上发生更 改。 这些位置包括:
- 使用时 DbContext.Entry ,确保实体的状态和修改的属性是最新的。
- 使用 EntityEntry 、以确保调用
Property
Collection
Reference
Member
等方法时,属性的修改、当前值等是最新的。 - 当要删除依赖/子实体时,因为所需的关系已被断开。 这会检测不应删除实体的时间,因为它已重新获得父级。
补充:DbContext.Entry 时会调用单EntityEntry实例中DetectChanges,调用 Property,Collection,Reference,Member 等方法则不会。
可以通过设置 ChangeTracker.AutoDetectChangesEnabled 来关闭DetectChanges的自动调用。
指定属性为更新状态
EntityEntry实例中通过调用Property方法.IsModified 来获取属性的修改状态。
也可以设置该属性,将其标识为已更新。我们前面提到过:EntityEntry.State = EntityState.Unchanged 会将修改值重置为原始值,而将IsModified设置为false,也会有一样的效果。
当我们将任意属性的 IsModified 设置为true,被跟踪的实体状态将会被变更为 Modified。我们将所有属性的IsModified 设置为false,被跟踪的实体状态将会被变更为 Unchanged。
直接简单的设置EntityState
通过直接设置 EntityEntry.EntityState 状态,来影响SaveChanges时生成的SQL。与Add,Remove,Attach,Update 等方法相比,直接设置EntityState不会对导航属性内现有的实体的状态进行变更。
DetectChanges 方法应是检测实体:状态为Unchanged 的所有属性的更改情况,和状态为Modified,属性IsModified为false的更改情况。
DetectChanges 针对实体内的导航实体状态为Detached:
- 自动生成主键的实体,有主键状态变更为:Modified(关系修改)
- 自动生成主键的实体,无主键状态变更为:Added(关系绑定)
- 常规主键的实体,有主键状态变更为:Added(关系绑定)
- 常规主键的实体,无主键抛出异常
DetectChanges 针对实体内的导航实体是被踪的状态的。仅检测关系变更(外键)。
但是导航实体作为被跟踪的实体,也会被DetectChanges 单独检测。
随笔移植,原文地址:https://zhuanlan.zhihu.com/p/419496293