栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

前言(其实是复习)

通过之前的学习,
我们了解到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(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就是一个对实体对象的大的包装类
主要有这几个属性

  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: []
//在调用 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()方法不仅可以显式的调用,也被以下方法隐式的调用

  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 Posts { get; } = new ObservableCollection();
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/644690.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号