一、EF Core 保存数据原理(SaveChanges())
✅ 简单理解:
DbContext.SaveChanges() 的核心作用是:
将 DbContext 中“已跟踪”的实体对象的状态变化(如新增、修改、删除)转换为对应的 SQL 语句,并提交给数据库执行。
🧠 背后的执行流程:
- 变更追踪(Change Tracking)
DbContext 会追踪你对实体对象做的操作(例如Add()、Update()、Remove()),并记录实体的状态(Added、Modified、Deleted等)。 - 生成命令(Command Generation)
调用SaveChanges()时,EF Core 会根据变更状态,构造 SQL 命令(如INSERT、UPDATE、DELETE)。 - 执行命令
EF Core 会按顺序执行这些命令,最终把更改持久化到数据库中。
二、事务操作原理
✅ 什么是事务?
事务是一组操作的集合,这组操作要么全部成功,要么全部失败。EF Core 默认就使用了事务来保护数据一致性。
🧠 EF Core 中事务的方式有两种:
✅ 自动事务(SaveChanges 内部自带)
context.SaveChanges();
-
EF Core 默认在 SaveChanges()内部自动创建一个事务(仅在多条命令时)。 -
如果所有命令都执行成功,就提交;否则就回滚。
✅ 手动控制事务
适用于:多个 DbContext.SaveChanges()、跨多个表操作、或需精细控制时。
using var transaction = context.Database.BeginTransaction();
try
{
context.Add(...);
context.SaveChanges();
context.Update(...);
context.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
}
三、原理整合:保存数据 + 事务完整图解
步骤 |
EF Core 做了什么 |
1 |
DbContext 跟踪实体的状态(Add/Update/Delete) |
2 |
调用 时,构建 SQL 命令 |
3 |
EF Core 打开数据库连接 |
4 |
自动或手动开启事务 |
5 |
顺序执行 SQL 命令,若任一失败则回滚 |
6 |
成功则提交事务 |
7 |
关闭连接 |
六、理解 SaveChanges() 默认会在多个命令之间启用事务
理解 SaveChanges() 默认会在多个命令之间启用事务的意思是,当你通过 DbContext 保存数据时,如果执行多个操作(例如插入、更新、删除多个实体),EF Core 会将这些操作放在同一个事务中进行处理。这意味着,如果这些操作中的任何一个失败了,所有操作都会回滚,确保数据库的数据一致性。
1. 为什么需要事务?
事务的作用是保证一组数据库操作要么全部成功,要么全部失败。在处理多个数据库命令时,如果不使用事务,一个命令成功后,另一个失败了,数据库就会处于不一致的状态。例如:
-
执行插入新用户记录成功,但插入日志记录失败,导致数据库只有用户数据而没有日志数据,数据不完整。
2. SaveChanges 中的事务机制
SaveChanges() 方法会自动在多个数据库操作之间启用一个隐式的事务。EF Core 会在调用 SaveChanges() 时自动开启一个事务,并在所有命令成功执行后提交该事务。如果其中任何一个命令执行失败,整个事务会被回滚。
3. 举例:多个命令和自动事务
假设你有两个实体:User 和 UserLog,我们希望在新增一个用户时,也同时插入一条操作日志。如果这些操作之间没有事务控制,可能会导致部分操作成功,部分操作失败,从而造成数据的不一致。
示例代码:
public class MyDbContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<UserLog> UserLogs { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public class UserLog
{
public int Id { get; set; }
public int UserId { get; set; }
public string Action { get; set; }
}
public void AddUserAndLog()
{
using (var context = new MyDbContext())
{
var user = new User { Name = "张三" };
context.Users.Add(user); // 第1个操作:添加用户
var log = new UserLog { UserId = user.Id, Action = "用户注册" };
context.UserLogs.Add(log); // 第2个操作:添加日志
context.SaveChanges(); // 第3个操作:保存到数据库,自动在多个操作之间启用事务
}
}
执行流程:
context.Users.Add(user)会将 user实体的状态设置为Added,表示插入操作。context.UserLogs.Add(log)会将 log实体的状态设置为Added,表示插入操作。-
调用 SaveChanges()时,EF Core 会:
- 自动开启事务
,将这两个操作放在一个事务中。 -
生成 SQL 命令:一个是 INSERT INTO Users,另一个是INSERT INTO UserLogs。 -
将这些命令按顺序执行,确保两者都成功执行,否则事务会回滚。
事务成功:
如果两个操作都成功,EF Core 会提交事务,数据会持久化到数据库中,Users 表和 UserLogs 表都会有对应的记录。
事务回滚(失败情况):
如果其中某一个操作失败(例如插入日志记录时发生错误),事务会被回滚,数据库中的任何变化都会被撤销。也就是说,Users 表中的数据也不会被保存。
4. 具体示例:失败回滚
假设在插入 User 成功后,插入 UserLog 时发生了一个错误(例如违反了数据库约束)。EF Core 会自动回滚整个事务,即使 User 的插入是成功的,它也不会被保存到数据库。
public void AddUserAndLogWithError()
{
using (var context = new MyDbContext())
{
var user = new User { Name = "李四" };
context.Users.Add(user); // 第1个操作:添加用户(成功)
var log = new UserLog { UserId = user.Id, Action = "用户注册" };
context.UserLogs.Add(log); // 第2个操作:添加日志(故意制造错误)
// 假设日志插入操作失败(例如外键约束失败)
try
{
context.SaveChanges(); // 整个事务会回滚
}
catch (Exception ex)
{
Console.WriteLine("发生错误,事务已回滚:" + ex.Message);
}
}
}
结果:
User插入成功后, UserLog插入失败。-
由于事务回滚, User的插入操作也会被撤销。
总结:
-
默认情况下, SaveChanges()会在你执行多个操作时,自动在这些操作之间启动一个事务,确保数据库的操作要么全部成功,要么全部失败。 -
这种事务控制机制避免了数据的不一致,保证了多操作的原子性。

