前言(其实是复习)

通过之前的学习, 我们了解到DbContext实际上是一个"半手工"的Session 学过Hibernate的都知道, 它的难度很大程度上在于了解 Session对对象状态的跟踪(或者说是管理)会始终与数据库中表的记录同步, 这其中的原理则是通过Hibernate中对象的三种状态实现的 1.瞬时态 2.持久态 3.离线态 先不说Session是如何改变对象的状态,以及其复杂的内部结构(其实是快忘了)

一.EFCore的实体状态

EFCore的实体状态有五种,分别是

Detached离线的Unchanged未改变的Deleted被删除Modified被更改的Added别添加的 在EntityState枚举中能找到它们

// 摘要:

// The state in which an entity is being tracked by a context.

public enum EntityState

{

Detached = 0,

Unchanged = 1,

Deleted = 2,

Modified = 3,

Added = 4

}

二.实体状态对应的场景

1.Detached没有设置主键,且没有和当前数据库上下文建立关联

static void Main(string[] args)

{

SGDbContext context = new SGDbContext();

//一个新建的对象,没有设置主键,没有放入DbContext对象中

var detachedObject = new StudentSec();

detachedObject.StudentSecName = "張三";

Console.WriteLine(context.Entry(detachedObject).State);

}

结果

Detached

2.Unchanged从数据库中查询出的没有做任何变更的对象,

static void Main(string[] args)

{

//此处的DbcONTEXT用例可以是任何一个DbContext

PPDbContext context = new PPDbContext();

Pet pet = context.pets.First();

Person p1 = context.persons.First();

DisplayEntityState(context.ChangeTracker.Entries());

}

//查询实体状态的方法

private static void DisplayEntityState(IEnumerable entities)

{

foreach(var entry in entities)

{

//循环输出DbContext类实体中对象的状态

Console.WriteLine($"Entity{entry.Entity.GetType().Name},EntityState:{entry.State}");

}

}

结果:

EntityPet,EntityState:Unchanged

EntityPerson,EntityState:Unchanged

3.Added状态,被托管给DbContext(由EFCore)生成主键,但没有savechanges()

static void Main(string[] args)

{

PPDbContext context = new PPDbContext();

Pet pet = new Pet();

pet.name = "kksk";

pet.owner = null;

context.Add(pet);

DisplayEntityState(context.ChangeTracker.Entries());

}

结果:

EntityPet,EntityState:Added

4.Deleted状态,当(已存在的)实体被纳入数据库上下文的Remove方法后

static void Main(string[] args)

{

//此处的DbcONTEXT用例可以是任何一个DbContext

PPDbContext context = new PPDbContext();

Pet pet = context.pets.First();

context.Remove(pet);

DisplayEntityState(context.ChangeTracker.Entries());

}

结果:

EntityPet,EntityState:Deleted

5.Modified存在的实体属性被修改后的状态

static void Main(string[] args)

{

//此处的DbcONTEXT用例可以是任何一个DbContext

PPDbContext context = new PPDbContext();

Pet pet = context.pets.First();

pet.name = "55kai";

DisplayEntityState(context.ChangeTracker.Entries());

}

结果:

EntityPet,EntityState:Modified

三.访问跟踪的实体(官方文档写是访问,这不明摆着的是查询吗)

DbContext.Entry为给定的实体实例返回 EntityEntry< TEntity > 实例。ChangeTracker.Entries为所有跟踪的实体或某种指定类型的所有跟踪的实体返回 EntityEntry< TEntity > 实例。DbContext.Find、DbContext.FindAsync、DbSetTEntity.Find 和 DbSetTEntity.FindAsync按主键查找单个实体,首先查找跟踪的实体,然后根据需要查询数据库。DbSetTEntity>.Local就是DbContext的集合

PS:后面两种方式就是纯粹的查询方式了,不知道官网文档为什么把它放到访问跟踪的实体中,这样恐怕会造成文档的内容重叠,不方便读者阅读(机翻很多已经不好阅读了)

这里只需要介绍一下这个EntityEntry即可, 在上面的代码中,我们就通过了ChangeTrackers.Entities获取了所有的EntityEntry< TEntity > 进行判断 下面是EntityEntry的的源码(不需要仔细看,快速的浏览即可),

[DebuggerDisplay("{InternalEntry,nq}")]

public class EntityEntry : IInfrastructure

{

public virtual PropertyValues CurrentValues { get; }

//该是个是否已经有键值

public virtual bool IsKeySet { get; }

//实体类中的集合属性

public virtual IEnumerable Collections { get; }

//实体类中的引用类型

public virtual IEnumerable References { get; }

//实体类的属性

public virtual IEnumerable Properties { get; }

//实体类中的导航属性

public virtual IEnumerable Navigations { get; }

//实体类中的成员(方法),

public virtual IEnumerable Members { get; }

//实体类型的 IEntityType 元数据。

public virtual IEntityType Metadata { get; }

//当前的DbContext数据库上下文

public virtual DbContext Context { get; }

//当前实体的状态

public virtual EntityState State { get; set; }

//当前实体的本体(其实整个类就像一个Wrapper)

public virtual object Entity { get; }

public virtual PropertyValues OriginalValues { get; }

public virtual CollectionEntry Collection([NotNullAttribute] string propertyName);

//仅强制检测此实体的更改

public virtual void DetectChanges();

[EditorBrowsable(EditorBrowsableState.Never)]

public override bool Equals(object obj);

public virtual PropertyValues GetDatabaseValues();

[AsyncStateMachine(typeof(d__40))]

public virtual Task GetDatabaseValuesAsync(CancellationToken cancellationToken = default);

[EditorBrowsable(EditorBrowsableState.Never)]

public override int GetHashCode();

//再获取MemberEntry

public virtual MemberEntry Member([NotNullAttribute] string propertyName);

//再获取NavigatEntry

public virtual NavigationEntry Navigation([NotNullAttribute] string propertyName);

//再获取属性Entry

public virtual PropertyEntry Property([NotNullAttribute] string propertyName);

//再获取引用Entry

public virtual ReferenceEntry Reference([NotNullAttribute] string propertyName);

[AsyncStateMachine(typeof(d__42))]

public virtual Task ReloadAsync(CancellationToken cancellationToken = default);

public override string ToString();

}

其实这个EntityEntry就是一个对实体对象的大的包装类 主要有这几个属性

EntityEntry.State 获取并设置实体的 EntityState。EntityEntry.Entity 实体本体EntityEntry.Context 正在跟踪此实体的 DbContext。EntityEntry.Metadata 实体类型的 IEntityType 元数据。EntityEntry.DetectChanges() 仅强制检测此实体的更改

四.快照更改跟踪(这里源文档说的很晦涩,我仅以我的理解判断一下)

原文文档链接: https://docs.microsoft.com/zh-cn/ef/core/change-tracking/change-detection 默认情况下,DbContext 实例首次跟踪每个实体时,EF Core 会创建这些实体的属性值的快照。 然后将此快照中存储的值与实体的当前值进行比较,以确定哪些属性值已更改。

在调用 SaveChanges 时,会进行更改检测,以确保在将更新发送到数据库之前检测到所有更改的值。 但是,更改检测也会发生在其他时间,以确保应用程序使用最新的跟踪信息。 可以通过调用 ChangeTracker.DetectChanges() 随时强制执行更改检测。 (简而言之,就是EFCore会创建数据库上下文跟踪对象的快照,在你调用SaveChange()之时,检查实体的更改(包括实体的状态,属性值等等。。),发送SQL使得跟踪的实体和数据库的表的数据和关系一致),可以使用ChangeTracker.DetectChanges()提前检查上述的更改

这里以上两天文章的多对多的关系举例

static void Main(string[] args)

{

MMDbContext context = new MMDbContext();

var stuOne = context.studentCopies.First();

stuOne.courses.Add(new Course() { course_name = "張三說刑法",teacher_name = "羅翔" });

stuOne.age = 999;

//第一次打印跟改跟踪器视图,此时还没有检查实体关系的改变

Console.WriteLine(context.ChangeTracker.DebugView.LongView);

Console.WriteLine("分割線---------------------強行啟動修改檢查");

//这里我们启动强制关系检查,更新跟改

context.ChangeTracker.DetectChanges();

//再次打印跟改跟踪器视图,查看此时的变化情况

Console.WriteLine(context.ChangeTracker.DebugView.LongView);

}

结果:

StudentCopy {id: 1} Unchanged

id: 1 PK

age: 999 Originally 21

name: '羅翔'

courses: []

//在调用 ChangeTracker.DetectChanges() 之前查看更改跟踪器调试视图表明未检测到所做的更改,因此这些更改不会反映在实体状态和修改的属性数据中:

分割線---------------------強行啟動修改檢查

CourseStudentCopy (Dictionary) {coursesid: -2147482647, studentsid: 1} Added

coursesid: -2147482647 PK FK Temporary

studentsid: 1 PK FK

Course {id: -2147482647} Added //检查到了新加的关联对象

id: -2147482647 PK Temporary

course_name: '張三說刑法'

teacher_name: '羅翔'

students: [{id: 1}]

StudentCopy {id: 1} Modified //修改Unchanged状态为Modified

id: 1 PK

age: 999 Modified Originally 21

name: '羅翔'

courses: [{id: -2147482647}]

DetectChanges()方法不仅可以显式的调用,也被以下方法隐式的调用

DbContext.SaveChanges 和 DbContext.SaveChangesAsyncChangeTracker.Entries() 和 ChangeTracker.EntriesTEntity(),ChangeTracker.HasChanges()DbSetTEntity>.LocalChangeTracker.CascadeChanges(), 在某些情况下,更改检测仅发生在单个实体实例上,而不是整个跟踪的实体图上。 具体情况如下:

1.使用 DbContext.Entry 时,以确保实体的状态和修改的属性处于最新状态。 2.使用 EntityEntry 方法(如 、Collection、Reference 或 Member)时,以确保属性修改、当前值等处于最新状态。 3.由于已提供所需的关系,将要删除依赖/子实体时。 这会检测何时不应删除实体,因为它已重新成为父级。 可以通过调用 EntityEntry.DetectChanges() 来显式触发单个实体的本地更改检测。 (PS:可以调用ChangeTracker.AutoDetectChangesEnabled来关闭自动的跟改检测,这在批量处理实体时可以会有一点性能上的提升,但不检查数据的一致性)

五.(实体更改后)实体的通知

https://docs.microsoft.com/zh-cn/ef/core/change-tracking/change-detection 通知实体是不使用实体快照的一种检测一致性的方式,(不常用) 需要实现 INotifyPropertyChanging 和 INotifyPropertyChanged 接口,这些接口是 .NET 基类库 (BCL) 的一部分。 这些接口定义在更改属性值之前和之后必须触发的事件。 例如:

public class Blog : INotifyPropertyChanging, INotifyPropertyChanged

{

public event PropertyChangingEventHandler PropertyChanging;

public event PropertyChangedEventHandler PropertyChanged;

private int _id;

public int Id

{

get => _id;

set

{

PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Id)));

_id = value;

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Id)));

}

}

private string _name;

public string Name

{

get => _name;

set

{

PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Name)));

_name = value;

PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));

}

}

public IList Posts { get; } = new ObservableCollection();

}

精彩链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。