EntityFrameWork Core从零开始,(六)对象跟踪与实体状态

本文详细介绍了EFCore中的实体状态,包括Detached、Unchanged、Deleted、Modified和Added,以及如何通过DbContext的ChangeTracker进行实体状态管理。同时,解释了更改检测的概念,阐述了DetectChanges方法的作用,并探讨了实体更改后的通知机制。此外,通过示例代码展示了不同状态下实体的转换和操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言(其实是复习)

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

一.EFCore的实体状态

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

  1. Detached离线的
  2. Unchanged未改变的
  3. Deleted被删除
  4. Modified被更改的
  5. 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<StudentSec>(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<EntityEntry> 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<InternalEntityEntry>
    {
        
        public virtual PropertyValues CurrentValues { get; }
        //该是个是否已经有键值
        public virtual bool IsKeySet { get; }
        //实体类中的集合属性
        public virtual IEnumerable<CollectionEntry> Collections { get; }
        //实体类中的引用类型
        public virtual IEnumerable<ReferenceEntry> References { get; }
        //实体类的属性
        public virtual IEnumerable<PropertyEntry> Properties { get; }
        //实体类中的导航属性
        public virtual IEnumerable<NavigationEntry> Navigations { get; }
        //实体类中的成员(方法),
        public virtual IEnumerable<MemberEntry> 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(<GetDatabaseValuesAsync>d__40))]
        public virtual Task<PropertyValues> 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(<ReloadAsync>d__42))]
        public virtual Task ReloadAsync(CancellationToken cancellationToken = default);
       
        public override string ToString();
    }

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

  1. EntityEntry.State 获取并设置实体的 EntityState。
  2. EntityEntry.Entity 实体本体
  3. EntityEntry.Context 正在跟踪此实体的 DbContext。
  4. EntityEntry.Metadata 实体类型的 IEntityType 元数据。
  5. 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: [<not found>]
//在调用 ChangeTracker.DetectChanges() 之前查看更改跟踪器调试视图表明未检测到所做的更改,因此这些更改不会反映在实体状态和修改的属性数据中:
分割線---------------------強行啟動修改檢查
CourseStudentCopy (Dictionary<string, object>) {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()方法不仅可以显式的调用,也被以下方法隐式的调用

  1. DbContext.SaveChanges 和 DbContext.SaveChangesAsync
  2. ChangeTracker.Entries() 和 ChangeTracker.EntriesTEntity(),
  3. ChangeTracker.HasChanges()
  4. DbSetTEntity>.Local
  5. ChangeTracker.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<Post> Posts { get; } = new ObservableCollection<Post>();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗马苏丹默罕默德

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值