【EF Core】实体状态与变更追踪
<p>好长时间没有水文章了,请容老周解释一下。因为最近老周进了两个厂,第一个厂子呆了八天左右,第二个厂子还在调试。管理很严格,带的电子设备都要登记、办手续。当初觉得雷神笔记本的屏幕大,在车间调试代码方便,所以登记了这个型号。但这个游戏本功耗大,而且充电只能充到 83% 就充不进去了。只能白天在车间调试时用,其他时间玩手机。手机是那个 23800 mAH 的坦克3,所以电量多得是,充一次随便玩。在厂里很无聊,老周还另带了一台某宝买的开源掌机……扯远了。</p><p>第二个厂子的项目很诡异,老周甚至怀疑有人故意捣乱。他们工人自己测试的时候,总是报莫名其妙的错;但是,只要老周过去和他们一起测,就一切正常。反正现在是测不出到底啥问题。从日志中记录的异常看,都是 Modbus TCP 连接超时。把 time out 改为 50 分钟,也照样在无限连接中。老周觉得是人为拨了网线的可能性更大。反正只要老周在现场就没问题,所以耗了近一个月也没结果。所以老周就请了四天假玩玩,不管他们同不同意,四天后老周准时回去报到。</p>
<p>--------------------------------------------------------------------------------------------------------------------------------------------------------------------</p>
<p>记得上一篇水文中,老周说了把一个实体映射到多个表的话题。注意,一实体一数据表的原则是不变的,这种特殊情况可以用在你这几个表可以组成一个整体,并且经常一起使用的,这样你在查询时就不用联合了,一般是一对一关系的。</p>
<p>熟悉老周的人都知道,老周分享的都是纯知识和纯技术的东西。至于实际开发中怎么用,那是你的事。实际应用是没办法写教程的,你得看具体情况,灵活运用,不存在一个教程包万能的道理。做项目我从小周做成了老周,虽然没做过什么大项目,但小 Case 是不少的(吹吹牛皮)。你别小看那些杂七杂八的项目,哪个不是要六边形战士,哪个不是软硬结合,哪个不是既485又CAN又PLC又单片机的。别看它小,WinForms、Web、STM32(珠海极海的 APM32 也遇到过)、串口、Esp8266 全用上都是常见的事。这年头,不学点 C 语言连小项目都搞不起,哪像那些互联网巨头那么爽,天天盯着 HTML + CSS 玩。</p>
<p>老周一直觉得,经验其实不重要的,跟一两周的项目你都有经验了,关键还得是基础扎实、技术过硬,这样才能来什么活接什么活。至于说基础问题,干活的家伙,实战更重要,理论的东西其实知道是啥就好,咱们又不用写论文评职称。你理论知识说得一套一套的,真用的时候不会用,那有啥用?不要排斥实用主义,实用主义其实是正确的,技术学了就是拿来用的,不用就没意义了。学习是分两种的:一种是内修——比如琴棋书画,这是文化底蕴,个人气质。这种你不必学了就要用(但也可以用),更重要的是养心养神,自我调整;另一种就是工作干活用的,叫技能,属于外修。从小老师都教我们要内外兼修。</p>
<p>好了,不扯了。在开始今天的主题前,咱们补一个内容:既然实体能分布到多个表中,那反过来呢?能把多个实体映射到一个表中吗?当然可以了,官方称作“表拆分”。同样的道理,一般也是一对一的关系。</p>
<p>光说不练,惨过失恋。咱们直接用实例来说明。假设下面有两个实体。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Person
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> PsID { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Name { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } = <span style="color: rgba(0, 0, 255, 1)">null</span>!<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Age {<span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 导航属性</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> PersonInfo OtherInfo { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } = <span style="color: rgba(0, 0, 255, 1)">null</span>!<span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> PersonInfo
{
</span><strong><span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">int</span> InfoID {<span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; }</span></strong> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 既做主键也做外键</span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 体重
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">float</span> Weight { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 身高
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">float</span> Height {<span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 民族
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? Ethnicity {<span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}</span></pre>
</div>
<p>待会儿咱们要做的是把这两个实体映射到一个表中,所以为了安全,你可以让 PersonInfo 实体的 InfoID 属性变成私有成员,这可以防止三只手的人意外修改主键值。因为这个实体的 ID 值必须始终与 Person 的 ID 一致。</p>
<p>下面代码是数据库上下文类。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> DemoDbContext : DbContext
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> DbSet<Person> People { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> DbSet<PersonInfo> PersonInfos { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">data source=恭喜发财.db</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
.LogTo(
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 输出日志的委托</span>
action: msg =><span style="color: rgba(0, 0, 0, 1)"> Console.WriteLine(msg),
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 过滤器,只显示即将执行的命令日志,可以看到SQL语句</span>
filter: (eventId, _) => eventId.Id ==<span style="color: rgba(0, 0, 0, 1)"> RelationalEventId.CommandExecuting
);
}
</span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnModelCreating(ModelBuilder modelBuilder)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 配置实体</span>
modelBuilder.Entity<Person>(pse =><span style="color: rgba(0, 0, 0, 1)">
{
pse.<strong><span style="background-color: rgba(255, 255, 0, 1)">Property(e </span></strong></span><strong><span style="background-color: rgba(255, 255, 0, 1)">=> e.PsID).HasColumnName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">person_id</span><span style="color: rgba(128, 0, 0, 1)">"</span></span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">)</span></strong>;
pse.Property(b </span>=> b.Name).HasMaxLength(<span style="color: rgba(128, 0, 128, 1)">16</span>).IsRequired().HasColumnName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">person_name</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
pse.Property(d </span>=> d.Age).HasColumnName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">person_age</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 主键</span>
pse.HasKey(w => w.PsID).<strong><span style="background-color: rgba(255, 255, 0, 1)">HasName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">PK_Person</span><span style="color: rgba(128, 0, 0, 1)">"</span></span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">)</span></strong>;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 表名</span>
pse.<strong><span style="background-color: rgba(255, 255, 0, 1)">ToTable(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">tb_people</span><span style="color: rgba(128, 0, 0, 1)">"</span></span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">)</span></strong>;
});
modelBuilder.Entity</span><PersonInfo>(pie =><span style="color: rgba(0, 0, 0, 1)">
{
pie.<strong><span style="background-color: rgba(255, 255, 0, 1)">Property(</span></strong></span><strong><span style="background-color: rgba(255, 255, 0, 1)"><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">InfoID</span><span style="color: rgba(128, 0, 0, 1)">"</span>).HasColumnName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">person_id</span><span style="color: rgba(128, 0, 0, 1)">"</span></span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">).ValueGeneratedNever()</span></strong>;
pie.Property(r </span>=> r.Height).HasColumnName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">info_height</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
pie.Property(i </span>=> i.Weight).HasColumnName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">info_weight</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
pie.Property(k </span>=> k.Ethnicity).HasMaxLength(<span style="color: rgba(128, 0, 128, 1)">10</span>).HasColumnName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">info_ethnic</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 主键</span>
pie.HasKey(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">InfoID</span><span style="color: rgba(128, 0, 0, 1)">"</span>).<strong><span style="background-color: rgba(255, 255, 0, 1)">HasName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">PK_Person</span><span style="color: rgba(128, 0, 0, 1)">"</span></span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">)</span></strong>;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 同一个表名</span>
pie.<strong><span style="background-color: rgba(255, 255, 0, 1)">ToTable(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">tb_people</span><span style="color: rgba(128, 0, 0, 1)">"</span></span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="background-color: rgba(255, 255, 0, 1)">)</span></strong>;
});
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 两实体的关系</span>
modelBuilder.Entity<Person>().HasOne(n =><span style="color: rgba(0, 0, 0, 1)"> n.OtherInfo)
.WithOne()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> info --> person</span>
.HasForeignKey<PersonInfo>(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">InfoID</span><span style="color: rgba(128, 0, 0, 1)">"</span>).HasConstraintName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">FK_PersonInfo</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> person --> info</span>
.HasPrincipalKey<Person>(p =><span style="color: rgba(0, 0, 0, 1)"> p.PsID);
}
}</span></pre>
</div>
<p>基本代码相信各位能看懂的。和配置一般实体区别不大,但要注意几点:</p>
<p>1、两个实体所映射的表名要相同。这是F话了,都说映射到同一个表了,表名能不一样的?</p>
<p>2、两个实体中作为主键的属性名可以不同,但类型要相同(可以减少翻车事故);更重要的是:<span style="text-decoration: underline; font-size: 16px"><span style="color: rgba(255, 0, 0, 1); text-decoration: underline"><strong>一定要映射到同一个列名</strong></span></span>。因为映射后,两个实体作为主键的属性会合并;再者,<span style="text-decoration: underline"><span style="color: rgba(255, 0, 0, 1)"><strong><span style="font-size: 16px">主键的约束名称也要相同</span></strong></span></span>,不解释了,一样的道理。</p>
<div class="cnblogs_code">
<pre>modelBuilder.Entity<Person>(pse =><span style="color: rgba(0, 0, 0, 1)">
{
pse.Property(e </span>=> e.PsID).HasColumnName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1); background-color: rgba(255, 255, 0, 1)">person_id</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
……
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 主键</span>
pse.HasKey(w => w.PsID).HasName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1); background-color: rgba(255, 255, 0, 1)">PK_Person</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 表名</span>
pse.ToTable(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1); background-color: rgba(255, 255, 0, 1)">tb_people</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
});
modelBuilder.Entity</span><PersonInfo>(pie =><span style="color: rgba(0, 0, 0, 1)">
{
pie.Property(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">InfoID</span><span style="color: rgba(128, 0, 0, 1)">"</span>).HasColumnName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1); background-color: rgba(255, 255, 0, 1)">person_id</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">).ValueGeneratedNever();
……
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 主键</span>
pie.HasKey(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">InfoID</span><span style="color: rgba(128, 0, 0, 1)">"</span>).HasName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1); background-color: rgba(255, 255, 0, 1)">PK_Person</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 同一个表名</span>
pie.ToTable(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1); background-color: rgba(255, 255, 0, 1)">tb_people</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
});</span></pre>
</div>
<p>对于第二个实体,ValueGeneratedNever 方法可以不调用,EF 会自动感知到不需要自动生成列值。</p>
<p>3、两个实体配置为一对一关系,这个和常规实体操作一样。</p>
<p>然后在 Main 方法中测试一下。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> <span style="color: rgba(0, 0, 255, 1)">var</span> context = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> DemoDbContext();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 打印数据库模型</span>
<span style="color: rgba(0, 0, 0, 1)"> Console.WriteLine(context.Model.ToDebugString());
}</span></pre>
</div>
<p>运行结果:</p>
<div class="cnblogs_code">
<pre> Executing DbCommand , CommandType=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Text</span><span style="color: rgba(128, 0, 0, 1)">'</span>, CommandTimeout=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">30</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]
CREATE TABLE </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">tb_people</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">person_id</span><span style="color: rgba(128, 0, 0, 1)">"</span> INTEGER NOT NULL CONSTRAINT <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">PK_Person</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> PRIMARY KEY AUTOINCREMENT,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">person_name</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> TEXT NOT NULL,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">person_age</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> INTEGER NOT NULL,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">info_weight</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> REAL NOT NULL,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">info_height</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> REAL NOT NULL,
</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">info_ethnic</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> TEXT NULL
);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 以下是数据库模型</span>
<span style="color: rgba(0, 0, 0, 1)">Model:
EntityType: Person
Properties:
PsID (</span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">) Required PK AfterSave:Throw ValueGenerated.OnAdd
Age (</span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">) Required
Name (</span><span style="color: rgba(0, 0, 255, 1)">string</span>) Required MaxLength(<span style="color: rgba(128, 0, 128, 1)">16</span><span style="color: rgba(0, 0, 0, 1)">)
Navigations:
OtherInfo (PersonInfo) Required ToDependent PersonInfo
Keys:
PsID PK
EntityType: PersonInfo
Properties:
InfoID (</span><span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)">) Required PK FK AfterSave:Throw
Ethnicity (</span><span style="color: rgba(0, 0, 255, 1)">string</span>) MaxLength(<span style="color: rgba(128, 0, 128, 1)">10</span><span style="color: rgba(0, 0, 0, 1)">)
Height (</span><span style="color: rgba(0, 0, 255, 1)">float</span><span style="color: rgba(0, 0, 0, 1)">) Required
Weight (</span><span style="color: rgba(0, 0, 255, 1)">float</span><span style="color: rgba(0, 0, 0, 1)">) Required
Keys:
InfoID PK
Foreign keys:
PersonInfo {</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">InfoID</span><span style="color: rgba(128, 0, 0, 1)">'</span>} -> Person {<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">PsID</span><span style="color: rgba(128, 0, 0, 1)">'</span>} Unique Required RequiredDependent Cascade ToDependent: OtherInfo</pre>
</div>
<p>--------------------------------------------------------------------------------------------------------------------------------------</p>
<p>下面正片开始。今天咱们说说 EF Core 中几大主要功能模块之一——追踪(叫跟踪也行)。正常情况下,EF Core 从实体被查询出来的时候开始跟踪。跟踪前会为实体的各个属性/字段的值创建一个快照(就备份一下,不是拷贝对象,而是用一个字典来存放)。然后在特定条件下,会触发比较,即比较实体引用当前各属性的值与当初快照中的值,从而确定实体的状态。</p>
<p>为了方便访问,DbContext 类会公开 ChangeTracker 属性,通过它你能访问到由 EF Core 创建的 ChangeTracker 实例(在Microsoft.EntityFrameworkCore.ChangeTracking 命名空间)。该类包含与实体追踪有关的信息。调用 DetectChanges 方法会触发实体的追踪扫描,方法只负责触发状态检查,不返回任何结果,调用后实体的状态自动更新。实体的状态由 EntityState 枚举表示。</p>
<p>1、Unchanged:实体从数据库中查询出来后就是这个状态,前提是这个实体是从数据库中查出来的,也就是说它已经在数据库中了。</p>
<p>2、Added:当你用 DbContext.Add 或 DbSet.Add 方法添加新实体后,实体就处在这个状态。实体只存在 EF Core 中,还没保存到数据库。提交时生成 INSERT 语句。</p>
<p>3、Modified:已修改。实体自从数据库中查询出来到目前为止,它的某些属性或全部属性被修改过。提交时生成 UPDATE 语句。</p>
<p>4、Deleted:已删除。实体已从 DbSet 中删除(还在数据库中)就是这个状态,提交后生成 DELETE 语句。</p>
<p>5、Detached:失踪人口,EF Core 未追踪其状态。</p>
<p>EF Core 内部有个名为 IStateManager 的服务接口,默认实现类是 StateManager。该类可以修改实体的状态,也可以控制开始/停止追踪实体的状态。咱们在写代码时不需要直接访问它,DbContext 以及 DbContext.ChangeTracker、DbSet 已经封装了相关访问入口。</p>
<p>对 DbSet 对象来说,你调用 Add、Remove、Update 等方法只是更改了实体的状态,并没有真正更新到数据库,除非你调用 SaveChanges 方法。SaveChanges 方法内部会先调用 DetectChanges 方法触发状态变更扫描,然后再根据实体的最新状态生成相应的 SQL 语句,再发送到数据库中执行。</p>
<p>下面以插入新实体为例,演示一下。本示例在插入新实体前、后,以及提交到数据库后都打印一次实体的状态。</p>
<p>先定义实体类。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Pet
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">int</span> Id { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span> Name { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span>; } = <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">.Empty;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? Description { <span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">string</span>? Category {<span style="color: rgba(0, 0, 255, 1)">get</span>; <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">; }
}</span></pre>
</div>
<p>正规流程,写数据库上下文类。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> TestDbContext : DbContext
{
</span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">data source=天宫赐福.db</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity</span><Pet>(et =><span style="color: rgba(0, 0, 0, 1)">
{
et.ToTable(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">tb_pets</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
et.Property(g </span>=> g.Name).HasMaxLength(<span style="color: rgba(128, 0, 128, 1)">20</span><span style="color: rgba(0, 0, 0, 1)">);
et.Property(k </span>=> k.Description).HasMaxLength(<span style="color: rgba(128, 0, 128, 1)">200</span><span style="color: rgba(0, 0, 0, 1)">);
et.Property(q </span>=> q.Category).HasMaxLength(<span style="color: rgba(128, 0, 128, 1)">15</span><span style="color: rgba(0, 0, 0, 1)">);
et.HasKey(m </span>=> m.Id).HasName(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">PK_PetID</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
});
}
}</span></pre>
</div>
<p>好,现在进入测试环节。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> <span style="color: rgba(0, 0, 255, 1)">var</span> context = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TestDbContext();
context.Database.EnsureCreated();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 添加一个实体</span>
Pet p = <span style="color: rgba(0, 0, 255, 1)">new</span>() { Name = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Jack</span><span style="color: rgba(128, 0, 0, 1)">"</span>, Description = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">不会游泳的巴西龟</span><span style="color: rgba(128, 0, 0, 1)">"</span>, Category = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">爬行动物</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> };
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 打印一下状态</span>
Console.WriteLine(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">----------- 添加前 -------------</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
context.Add(p);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 再打印一下状态</span>
Console.WriteLine(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n---------- 添加后 ------------</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 提交</span>
<span style="color: rgba(0, 0, 0, 1)"> context.SaveChanges();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 再打印状态</span>
Console.WriteLine(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n---------- 提交后 ------------</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
}</span></pre>
</div>
<p>和 Model 类似,ChangeTracker 对象也有个 DebugView,用于获取调试用的信息。这个能打印出实体以及它的各个属性的状态。</p>
<p>运行一遍,结果如下:</p>
<div class="cnblogs_code">
<pre>----------- 添加前 -------------
---------- 添加后 ------------<span style="color: rgba(0, 0, 0, 1)">
Pet {Id: </span>-<span style="color: rgba(128, 0, 128, 1)">2147482647</span><span style="color: rgba(0, 0, 0, 1)">} Added
Id: </span>-<span style="color: rgba(128, 0, 128, 1)">2147482647</span><span style="color: rgba(0, 0, 0, 1)"> PK Temporary
Category: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">爬行动物</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
Description: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">不会游泳的巴西龟</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
Name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Jack</span><span style="color: rgba(128, 0, 0, 1)">'</span>
---------- 提交后 ------------<span style="color: rgba(0, 0, 0, 1)">
Pet {Id: </span><span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">} Unchanged
Id: </span><span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)"> PK
Category: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">爬行动物</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
Description: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">不会游泳的巴西龟</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
Name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Jack</span><span style="color: rgba(128, 0, 0, 1)">'</span></pre>
</div>
<p>新实体被 Add 之前,它是没有被追踪的,所以打印状态信息空白。调用 Add 方法后,它的状态就变成 Added 了。此时,你不需要调用 DetectChanges 方法,因为 Add 方法本身就会修改实体的状态。新实体还未存入数据库,所以主键 ID 赋了个负值,且是临时的。当调用 SaveChanges 方法后,提交数据库保存,并取回数据库生成的ID值,故此时 ID 的值是 1。而且,实体的状态被改回 Unchanged。这是合理的,现在新的实体已经在数据库了,而且自从插入后没有修改过,状态应当是 Unchaged。</p>
<p>如果你有其他想法,希望在 SaveChanges 之后实体的状态不变回 Unchaged,可以这样调用 SaveChanges 方法。</p>
<div class="cnblogs_code">
<pre>context.SaveChanges(acceptAllChangesOnSuccess: <span style="color: rgba(0, 0, 255, 1)">false</span>);</pre>
</div>
<p>acceptAllChangesOnSuccess 参数设置为 false 后,数据库执行成功后不会改变实体的当前状态。于是,数据库中插入新记录后,实体状态还是 Added。</p>
<div class="cnblogs_code">
<pre>---------- 添加后 ------------<span style="color: rgba(0, 0, 0, 1)">
Pet {Id: </span>-<span style="color: rgba(128, 0, 128, 1)">2147482647</span><span style="color: rgba(0, 0, 0, 1)">} Added
Id: </span>-<span style="color: rgba(128, 0, 128, 1)">2147482647</span><span style="color: rgba(0, 0, 0, 1)"> PK Temporary
Category: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">爬行动物</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
Description: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">不会游泳的巴西龟</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
Name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Jack</span><span style="color: rgba(128, 0, 0, 1)">'</span>
---------- 提交后 ------------<span style="color: rgba(0, 0, 0, 1)">
Pet {Id: </span><span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">} <strong><span style="background-color: rgba(255, 255, 0, 1)">Added</span></strong>
Id: </span><span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)"> PK
Category: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">爬行动物</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
Description: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">不会游泳的巴西龟</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
Name: </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Jack</span><span style="color: rgba(128, 0, 0, 1)">'</span></pre>
</div>
<p>这样做可能会导致逻辑错误,除非你有特殊用途,比如这样用。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span> <span style="color: rgba(0, 0, 255, 1)">var</span> context = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TestDbContext();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 处理事件</span>
context.ChangeTracker.Tracked += (_, e) =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> backupcolor =<span style="color: rgba(0, 0, 0, 1)"> Console.ForegroundColor;
Console.ForegroundColor </span>=<span style="color: rgba(0, 0, 0, 1)"> ConsoleColor.Green;
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">实体被追踪:\n{e.Entry.DebugView.LongView}\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
Console.ForegroundColor </span>=<span style="color: rgba(0, 0, 0, 1)"> backupcolor;
};
context.ChangeTracker.StateChanged </span>+= (_, e) =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> bkColor =<span style="color: rgba(0, 0, 0, 1)"> Console.ForegroundColor;
Console.ForegroundColor </span>=<span style="color: rgba(0, 0, 0, 1)"> ConsoleColor.Blue;
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">实体(ID={e.Entry.Property(nameof(Pet.Id)).CurrentValue})状态改变:{e.OldState} --> {e.NewState}\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
Console.ForegroundColor </span>=<span style="color: rgba(0, 0, 0, 1)"> bkColor;
};
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 新实体</span>
Pet p = <span style="color: rgba(0, 0, 255, 1)">new</span> Pet { Name = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Tom</span><span style="color: rgba(128, 0, 0, 1)">"</span>, Description = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">会游泳的鸟</span><span style="color: rgba(128, 0, 0, 1)">"</span>, Category = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">猛禽</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> };
context.Add(p);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 保存,但状态不改变</span>
<span style="background-color: rgba(255, 255, 0, 1)">context.SaveChanges(<span style="color: rgba(0, 0, 255, 1)">false</span></span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 255, 0, 1)">)</span>;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 因为是 Added 状态,所以还可以继续insert</span>
p.Name = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Simum</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
p.Description </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">三手青蛙</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
p.Category </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">两栖动物</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 保存,状态改变</span>
<span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 0, 1)">context.SaveChanges();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 把它们查询出来看看</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> <span style="color: rgba(0, 0, 255, 1)">set</span> = context.Set<Pet><span style="color: rgba(0, 0, 0, 1)">();
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n数据库中的记录:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span>(<span style="color: rgba(0, 0, 255, 1)">var</span> pp <span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">)
{
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{pp.Id}{pp.Name}{pp.Description}{pp.Category}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p>上面代码中,侦听了两个事件:Tracked——当 EF Core 开始跟踪某个实体时发生;当有实体的状态改变之后发生。其实还有一个 StateChanging 事件,是在实体状态即将改变时发生。总结来说就是:状态改变之前发生 StateChanging 事件,改变之后发生 StateChanged 事件。要注意,StateChanged 和 StateChanging 事件在 EF Core 首次追踪实体时不会引发。比如,刚开始追踪时状态为 Unchanged,不会引发事件,而之后状态变为 Added,就会引发事件(最开始那个状态不会触发事件)。</p>
<p>上面代码处理 Tracked 事件,当开始追踪某实体时,打印一下调试信息,记录某状态;处理 StateChanged 事件,在开始追踪状态后,状态发生改变之后打印变化前后的状态。</p>
<p>代码运行结果如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/367389/202601/367389-20260125181401722-693660532.png" alt="image" width="288" height="266" loading="lazy"></p>
<p>首先,new 了一个 Pet 对象,赋值,再调用 Add 方法添加到数据集合中,此时状态会被改为 Added。Tracked 事件输出第一块绿色字体,表示实体开始追踪的状态为 Added,ID 值是随机分配的负值,并说明是临时主键值。</p>
<p>然后调用 SaveChanges 方法并传递 false 给acceptAllChangesOnSuccess 参数,表明 INSERT 进数据库后,状态不改变,还是 Added。</p>
<p>然后,还是用那个实体实例,改变一下属性值,由于它的状态依旧是 Added,调用 SaveChanges() 方法时未传参数,它会调用 SaveChanges(acceptAllChangesOnSuccess: true),结果是这次实体的状态变成了 Unchanged。就是输出结果中蓝色字体那一行。此时实体的 ID=2,记住这个值,待会儿用到。</p>
<p>再往后,咱们 foreach 语句给 DbSet 会触发 EF Core 去查询数据库,于是,我们看到,控制台在“数据库中的记录:”一行之后又发生了 Tracked 事件,有一个 ID=1 的实体被追踪了,它刚从数据库中查询出来,就是第二块绿色字体那里。</p>
<p>这时候你是不是迷乎了?不是从数据库查出两条记录吗,为什么只有 ID=1 的被追踪了,ID=2 呢?其实,ID = 2 已经被追踪了。忘了吗?它前面不是从 Added 状态变为 Unchanged 状态吗。这是因为咱们这一连串操作都在同一个 DbContext 实例的生命周期进行的,EF Core 对实体的追踪不会断开。</p>
<p>如果你把上面的代码改成这样,那就明白了。</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> context = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TestDbContext())
{
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 处理事件</span>
<span style="background-color: rgba(255, 255, 0, 1)">context.ChangeTracker.Tracked +=</span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 255, 0, 1)"> OnTracked;</span>
<span style="background-color: rgba(255, 255, 0, 1)">context.ChangeTracker.StateChanged </span></span><span style="background-color: rgba(255, 255, 0, 1)">+=<span style="color: rgba(0, 0, 0, 1)"> OnStateChanged;
</span></span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 新实体</span>
Pet p = <span style="color: rgba(0, 0, 255, 1)">new</span> Pet { Name = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Tom</span><span style="color: rgba(128, 0, 0, 1)">"</span>, Description = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">会游泳的鸟</span><span style="color: rgba(128, 0, 0, 1)">"</span>, Category = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">猛禽</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> };
context.Add(p);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 保存,但状态不改变</span>
context.SaveChanges(<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 因为是 Added 状态,所以还可以继续insert</span>
p.Name = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Simum</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
p.Description </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">三手青蛙</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
p.Category </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">两栖动物</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 保存,状态改变</span>
<span style="color: rgba(0, 0, 0, 1)"> context.SaveChanges();
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 把它们查询出来看看</span>
<span style="color: rgba(0, 0, 255, 1)">using</span>(<span style="color: rgba(0, 0, 255, 1)">var</span> context2 = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TestDbContext())
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 依旧要处理事件</span>
<span style="background-color: rgba(255, 255, 0, 1)">context2.ChangeTracker.Tracked +=</span><span style="color: rgba(0, 0, 0, 1)"><span style="background-color: rgba(255, 255, 0, 1)"> OnTracked;</span>
<span style="background-color: rgba(255, 255, 0, 1)">context2.ChangeTracker.StateChanged </span></span><span style="background-color: rgba(255, 255, 0, 1)">+=<span style="color: rgba(0, 0, 0, 1)"> OnStateChanged;
</span></span> <span style="color: rgba(0, 0, 255, 1)">var</span> <span style="color: rgba(0, 0, 255, 1)">set</span> = context2.Set<Pet><span style="color: rgba(0, 0, 0, 1)">();
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n数据库中的记录:</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> pp <span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)">)
{
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">{pp.Id}{pp.Name}{pp.Description}{pp.Category}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 下面两个方法处理事件</span>
<span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> OnTracked(<span style="color: rgba(0, 0, 255, 1)">object</span>?<span style="color: rgba(0, 0, 0, 1)"> _,EntityTrackedEventArgs e)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> backupcolor =<span style="color: rgba(0, 0, 0, 1)"> Console.ForegroundColor;
Console.ForegroundColor </span>=<span style="color: rgba(0, 0, 0, 1)"> ConsoleColor.Green;
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">实体被追踪:\n{e.Entry.DebugView.LongView}\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
Console.ForegroundColor </span>=<span style="color: rgba(0, 0, 0, 1)"> backupcolor;
}
</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span> OnStateChanged(<span style="color: rgba(0, 0, 255, 1)">object</span>?<span style="color: rgba(0, 0, 0, 1)"> _,EntityStateChangedEventArgs e)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> bkColor =<span style="color: rgba(0, 0, 0, 1)"> Console.ForegroundColor;
Console.ForegroundColor </span>=<span style="color: rgba(0, 0, 0, 1)"> ConsoleColor.Blue;
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">实体(ID={e.Entry.Property(nameof(Pet.Id)).CurrentValue})状态改变:{e.OldState} --> {e.NewState}\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
Console.ForegroundColor </span>=<span style="color: rgba(0, 0, 0, 1)"> bkColor;
}</span></pre>
</div>
<p>现在再次运行,看看结果是不是符合你当初的期望。</p>
<p><img src="https://img2024.cnblogs.com/blog/367389/202601/367389-20260125183707220-446191187.png" alt="image" width="373" height="378" loading="lazy"></p>
<p>现在的情况是:向数据库插入记录是第一个 DbContext 实例,完事后就释放了,实体追踪器自然就挂了;随后创建了第二个 DbContext 实例,这时候从数据库中查询出两条记录都是没有被追踪的,所以要启动追踪,自然就能引发两次 Tracked 事件了。</p>
<p>好了,各位,今天咱们就粗浅地聊到这里。后面老周还会继续讨论实体追踪的话题,本文主要是让大伙伴们了解一下实体的状态变化。</p>
<p> </p><br><br>
来源:https://www.cnblogs.com/tcjiaan/p/19528796
頁:
[1]