跳到主要内容

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 并发控制。