5.5 关系映射、事务与并发
业务数据不是孤立的。任务属于用户、任务有标签、多个用户可能同时更新数据。关系、事务和并发控制决定数据是否一致。
学习目标
- 能配置一对多和多对多关系。
- 能判断何时需要显式事务。
- 能理解乐观并发控制。
- 能处理更新冲突和重复提交。
应用场景
- 用户拥有多条任务。
- 任务和标签是多对多关系。
- 创建任务时同时写入任务、标签和审计日志。
- 两个浏览器窗口同时编辑同一条任务。
关系映射示例
public sealed class User
{
public int Id { get; set; }
public required string Email { get; set; }
public List<TodoItem> Todos { get; } = [];
}
public sealed class TodoItem
{
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; } = null!;
public required string Title { get; set; }
public List<Tag> Tags { get; } = [];
}
public sealed class Tag
{
public int Id { get; set; }
public required string Name { get; set; }
public List<TodoItem> Todos { get; } = [];
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(user => user.Todos)
.WithOne(todo => todo.User)
.HasForeignKey(todo => todo.UserId);
modelBuilder.Entity<TodoItem>()
.HasMany(todo => todo.Tags)
.WithMany(tag => tag.Todos);
}
事务示例
await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken);
var todo = new TodoItem {UserId = userId, Title = request.Title};
dbContext.Todos.Add(todo);
dbContext.AuditLogs.Add(AuditLog.CreatedTodo(userId, request.Title));
await dbContext.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
如果所有操作都在同一个 DbContext.SaveChangesAsync 内完成,EF Core 通常会自动使用事务;跨多次保存或多资源协作时才更常见显式事务。
并发令牌示例
public sealed class TodoItem
{
public int Id { get; set; }
public required string Title { get; set; }
public byte[] RowVersion { get; set; } = [];
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TodoItem>()
.Property(todo => todo.RowVersion)
.IsRowVersion();
}
重点难点
- 删除关系数据前要明确级联删除、限制删除或软删除策略。
- 显式事务不是越多越好,范围越大锁竞争越明显。
- 乐观并发适合“冲突少,但不能静默覆盖”的业务。
- 并发异常要转换为用户可理解的提示,例如“数据已被其他人修改”。
常见误区
| 误区 | 推荐做法 |
|---|---|
所有关联都直接 Include | 按用例选择投影或显式加载 |
| 每个方法都手动开事务 | 先判断是否跨多个一致性操作 |
| 最后写入者覆盖一切 | 对关键数据启用并发令牌 |
练习
- 给任务和标签配置多对多关系。
- 创建任务时同时写审计日志,并说明是否需要显式事务。
- 为任务标题编辑增加并发冲突处理。
延伸阅读
- EF Core 关系映射。
- EF Core 事务。
- EF Core 并发控制。