首页 > 其他分享 >EF Core 中的更改跟踪

EF Core 中的更改跟踪

时间:2024-03-31 09:55:50浏览次数:23  
标签:Core 实体 更改 数据库 EF SaveChanges Blog Id

本文内容
如何跟踪实体
实体状态
从查询跟踪
简单查询和更新
查询,然后插入、更新和删除
每个 DbContext 实例跟踪对实体所做的更改。 在调用 SaveChanges 时,这些跟踪的实体会相应地驱动对数据库的更改。

本文档概述了 Entity Framework Core (EF Core) 更改跟踪,以及它如何与查询和更新相关。

提示

通过从 GitHub 下载示例代码,你可运行并调试到本文档中的所有代码。

提示

为了简单起见,本文档使用和引用同步方法(如 SaveChanges),而不是它们的异步等效方法(如 SaveChangesAsync)。 除非另有说明,否则可以替换调用并等待异步方法。

如何跟踪实体
实体实例在以下情况下会被跟踪:

从针对数据库执行的查询返回
通过 Add、Attach、Update 或类似方法显示附加到 DbContext
检测为连接到现有跟踪实体的新实体
实体实例在以下情况下不再被跟踪:

DbContext 已释放
清除更改跟踪器
显式拆离实体
DbContext 旨在表示短期工作单元,如 DbContext 初始化和配置中所述。 这意味着释放 DbContext 是停止跟踪实体的正常方式。 换句话说,DbContext 的生存期应为:

创建 DbContext 实例
跟踪某些实体
对实体进行一些更改
调用 SaveChanges 以更新数据库
释放 DbContext 实例
提示

采用此方法时,无需清除更改跟踪器或显式拆离实体实例。 但是,如果确实需要拆离实体,则调用 ChangeTracker.Clear 比逐个拆离实体更有效。

实体状态
每个实体都与给定 EntityState 相关联:

Detached 实体未被 DbContext 跟踪。
Added 实体是新实体,并且尚未插入到数据库中。 这意味着它们将在调用 SaveChanges 时插入。
Unchanged 实体自从数据库中查询以来尚未进行更改。 从查询返回的所有实体最初都处于此状态。
Modified 实体自从数据库中查询以来已进行更改。 这意味着它们将在调用 SaveChanges 时更新。
Deleted 实体存在于数据库中,但标记为在调用 SaveChanges 时删除。
EF Core 跟踪属性级别的更改。 例如,如果只修改单个属性值,则数据库更新将仅更改该值。 但是,当实体本身处于“已修改”状态时,只能将属性标记为已修改。 (或者,从另一角度来看,“已修改”状态意味着至少有一个属性值已标记为已修改。)

下表汇总了不同的状态:


展开表
实体状态 由 DbContext 跟踪 存在于数据库中 属性已修改 SaveChanges 上的操作
Detached 否 - - -
Added 是 否 - 插入
Unchanged 是 是 No -
Modified 是 是 是 更新
Deleted 是 是 - 删除
备注

为清楚起见,此文本使用了关系数据库术语。 NoSQL 数据库通常支持类似操作,但可能具有不同的名称。 有关详细信息,请查阅数据库提供程序文档。

从查询跟踪
当同一个 DbContext 实例同时用于查询实体并通过调用 SaveChanges 更新它们时,EF Core 更改跟踪的效果最佳。 这是因为 EF Core 会自动跟踪已查询实体的状态,然后在调用 SaveChanges 时检测对这些实体所做的任何更改。

与显式跟踪实体实例相比,此方法具有多个优点:

该实例为简单类型。 实体状态极少需要显式操作,EF Core 负责处理状态更改。
更新仅限于那些实际已更改的值。
卷影属性的值将保留,并根据需要使用。 当外键以卷影状态存储时,这一点尤其重要。
自动保留属性的原始值,并将其用于有效更新。
简单查询和更新
例如,请考虑一个简单的博客/帖子模型:

C#

复制
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }

public IList<Post> Posts { get; } = new List<Post>();
}

public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }

public int? BlogId { get; set; }
public Blog Blog { get; set; }
}
我们可以使用此模型来查询博客和帖子,然后对数据库进行一些更新:

C#

复制
using var context = new BlogsContext();

var blog = context.Blogs.Include(e => e.Posts).First(e => e.Name == ".NET Blog");

blog.Name = ".NET Blog (Updated!)";

foreach (var post in blog.Posts.Where(e => !e.Title.Contains("5.0")))
{
post.Title = post.Title.Replace("5", "5.0");
}

context.SaveChanges();
使用 SQLite 作为示例数据库,调用 SaveChanges 会生成以下数据库更新:

SQL

复制
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog (Updated!)' (Size = 20)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p1='2' (DbType = String), @p0='Announcing F# 5.0' (Size = 17)], CommandType='Text', CommandTimeout='30']
UPDATE "Posts" SET "Title" = @p0
WHERE "Id" = @p1;
SELECT changes();
更改跟踪器调试视图是一种可以直观看到正在被跟踪的实体及其状态的极佳方式。 例如,在调用 SaveChanges 之前,将以下代码插入到上面的示例中:

C#

复制
context.ChangeTracker.DetectChanges();
Console.WriteLine(context.ChangeTracker.DebugView.LongView);
生成以下输出:

输出

复制
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, {Id: 3}]
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Modified
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5.0' Modified Originally 'Announcing F# 5'
Blog: {Id: 1}
具体而言,请注意:

Blog.Name 属性标记为已修改 (Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'),这会导致博客处于 Modified 状态。
帖子 2 的 Post.Title 属性标记为已修改 (Title: 'Announcing F# 5.0' Modified Originally 'Announcing F# 5'),这会导致此帖子处于 Modified 状态。
帖子 2 的其他属性值尚未更改,因此未标记为已修改。 这就是这些值不包含在数据库更新中的原因。
不会以任何方式修改另一个帖子。 这就是它仍处于 Unchanged 状态且不包括在数据库更新中的原因。
查询,然后插入、更新和删除
与上一示例中的更新类似,可以将更新与插入和删除合并在同一工作单元中。 例如:

C#

复制
using var context = new BlogsContext();

var blog = context.Blogs.Include(e => e.Posts).First(e => e.Name == ".NET Blog");

// Modify property values
blog.Name = ".NET Blog (Updated!)";

// Insert a new Post
blog.Posts.Add(
new Post
{
Title = "What’s next for System.Text.Json?", Content = ".NET 5.0 was released recently and has come with many..."
});

// Mark an existing Post as Deleted
var postToDelete = blog.Posts.Single(e => e.Title == "Announcing F# 5");
context.Remove(postToDelete);

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

context.SaveChanges();
在此示例中:

从数据库查询博客和相关帖子并进行跟踪
Blog.Name 属性已更改
新帖子将添加到博客的现有帖子集合中
通过调用 DbContext.Remove 将现有帖子标记为要删除
在调用 SaveChanges 之前,再次查看更改跟踪器调试视图,会显示 EF Core 跟踪这些更改的方式:

输出

复制
Blog {Id: 1} Modified
Id: 1 PK
Name: '.NET Blog (Updated!)' Modified Originally '.NET Blog'
Posts: [{Id: 1}, {Id: 2}, {Id: 3}, {Id: -2147482638}]
Post {Id: -2147482638} Added
Id: -2147482638 PK Temporary
BlogId: 1 FK
Content: '.NET 5.0 was released recently and has come with many...'
Title: 'What's next for System.Text.Json?'
Blog: {Id: 1}
Post {Id: 1} Unchanged
Id: 1 PK
BlogId: 1 FK
Content: 'Announcing the release of EF Core 5.0, a full featured cross...'
Title: 'Announcing the Release of EF Core 5.0'
Blog: {Id: 1}
Post {Id: 2} Deleted
Id: 2 PK
BlogId: 1 FK
Content: 'F# 5 is the latest version of F#, the functional programming...'
Title: 'Announcing F# 5'
Blog: {Id: 1}
请注意:

博客标记为 Modified。 这将生成一个数据库更新。
帖子 2 标记为 Deleted。 这将生成一个数据库删除。
具有临时 ID 的新帖子与博客 1 相关联,并标记为 Added。 这将生成一个数据库插入。
在调用 SaveChanges 时,这会生成以下数据库命令(使用 SQLite):

SQL

复制
-- Executed DbCommand (0ms) [Parameters=[@p1='1' (DbType = String), @p0='.NET Blog (Updated!)' (Size = 20)], CommandType='Text', CommandTimeout='30']
UPDATE "Blogs" SET "Name" = @p0
WHERE "Id" = @p1;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p0='2' (DbType = String)], CommandType='Text', CommandTimeout='30']
DELETE FROM "Posts"
WHERE "Id" = @p0;
SELECT changes();

-- Executed DbCommand (0ms) [Parameters=[@p0='1' (DbType = String), @p1='.NET 5.0 was released recently and has come with many...' (Size = 56), @p2='What's next for System.Text.Json?' (Size = 33)], CommandType='Text', CommandTimeout='30']
INSERT INTO "Posts" ("BlogId", "Content", "Title")
VALUES (@p0, @p1, @p2);
SELECT "Id"
FROM "Posts"
WHERE changes() = 1 AND "rowid" = last_insert_rowid();
有关插入和删除实体的详细信息,请参阅显式跟踪实体。 有关 EF Core 如何自动检测此类更改的详细信息,请参阅更改检测和通知。

标签:Core,实体,更改,数据库,EF,SaveChanges,Blog,Id
From: https://www.cnblogs.com/zy8899/p/18106415

相关文章

  • Eclipse中更改包名为相同名称的小写形式报错
    1.问题我想将包名中的QuotationManage改为quotationmanage,发生报错!'RenamePackageReason:Aresourceexistswithadifferentcase:'/src/main/java/com/xxx/QuotationManage'.2.解决参考链接:重命名包名发生错误这里由于windows是不区分大小写的,所以会导致这种重命名......
  • 使用 wsl+makefile+clangd编辑stm32代码环境的搭建
    使用wsl+makefile+clangd编辑stm32代码环境的搭建安装wsl环境可以看看下面的文章安装与换源都提及,相信大家可以安装成功的https://www.cnblogs.com/banmei-brandy/p/16218660.html安装make、bear、clangd、arm-none-eabi-gcc、最新的构建库sudoaptinstallmakebearclang......
  • codeforces div4 Double Strings
    #include<iostream>#include<algorithm>#include<cstring>#include<map>usingnamespacestd;intT,n;strings[900005];map<string,int>mm;//存放每一个字符串是否出现过intmain(){ cin>>T; while(T--){ mm.clear();//每次清空mm里面的数......
  • 关于.NET Core
    摘要:.NETCore3.1版本后,.NETCore概念被弱化,统称.NET,截止至2024.3,最新为.NET8.01.什么是.NETCore?.NET是微软推出的开发平台,是.NETFramework、.NETCore、Xamarin/Mono等的统称.NETFramework是Windows平台下开发技术,近20年历史.NETCore是免费、跨平台、开源的开发技......
  • 适合汽车应用的MAX96705AGTJ/V、DS90UB913ATRTVJQ1小尺寸串行器,以及TEF8105EN/N1汽车
    1、MAX96705AGTJ/V为小尺寸串行器,具有特别适合于汽车摄像应用的特性。其功能和引脚与MAX9271兼容。高带宽模式下,对于12位线性或组合HDR数据类型,并行时钟最大为116MHz。应用汽车摄像应用规格功能:串行器数据速率:1.6Gbps输入类型:CML输出类型:CMOS,LVCMOS输入数:1输出数:16电压-供电:1......
  • EFCore 中的工作单元
    保存数据本文内容方法1:更改跟踪和SaveChanges方法2:ExecuteUpdate和ExecuteDelete(“批量更新”)总结虽然查询允许从数据库中读取数据,但保存数据意味着向数据库添加新实体、删除实体或以某种方式修改现有实体的属性。EntityFrameworkCore(EFCore)支持将数据保存到数据库......
  • EF Core 中使用事务
    使用事务项目2023/10/0514个参与者反馈本文内容默认事务行为控制事务保存点跨上下文事务使用外部DbTransactions(仅限关系数据库)使用System.Transactions显示较少选项事务允许以原子方式处理多个数据库操作。如果已提交事务,则所有操作都会成功应用到数据库。如果已回滚事务,则......
  • EFCore中ExecuteUpdate 和 ExecuteDelete
    ExecuteUpdate和ExecuteDelete项目2023/05/114个参与者反馈本文内容ExecuteDeleteExecuteUpdateChangetracking事务显示另外3个备注EFCore7.0中已引入此功能。ExecuteUpdate和ExecuteDelete是一种将数据保存到数据库的方法,无需使用EF的传统更改跟踪和SaveChang......
  • NET Core使用Grpc通信(一):一元请求
    gRPC是一个现代的开源高性能远程过程调用(RPC)框架,它可以高效地连接数据中心内和跨数据中心的服务,支持负载平衡、跟踪、运行状况检查和身份验证。gRPC通过使用ProtocolBuffers作为数据传输格式,实现了在不同平台上的通信,并支持双向流和流式传输。RPC是远程过程调用的缩写,实现......
  • 题解 CF70E【Information Reform】
    题解CF70E【InformationReform】题目描述\(n\)个点的树,边权为\(1\)。可以花费常数\(k\),在一个点上建基站。每个点\(i\)需要找到离他最近的基站\(a_i\),花费\(d[dis(i,a_i)]\)。一种方案的总花费是建基站的花费加上每个点的花费之和。最小化总花费。输出方案\(a_i\)。......