跳到主要内容

5.3 EF Core CRUD 与迁移

EF Core 是 .NET 常用 ORM。它让你用 C# 类描述数据模型,再通过迁移把模型变化同步到数据库。

学习目标

  • 能定义实体和 DbContext
  • 能创建并应用迁移。
  • 能实现新增、查询、更新和删除。
  • 能理解跟踪查询、投影查询和保存变更的基本行为。

应用场景

  • 把 API 中的内存数据替换为真实数据库。
  • 管理表结构变化,例如增加任务截止日期。
  • 为列表页提供分页、筛选和排序。
  • 在服务层中封装数据访问细节。

核心概念

概念说明
Entity映射到数据库表的业务对象
DbContext工作单元,负责查询、跟踪和保存变更
Migration模型变化到数据库结构变化的版本记录
LINQ 查询用 C# 表达 SQL 查询,由 EF Core 翻译执行

案例:任务实体与上下文

public sealed class TodoItem
{
public int Id { get; set; }
public required string Title { get; set; }
public string? Description { get; set; }
public bool IsCompleted { get; set; }
public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
}

public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
: DbContext(options)
{
public DbSet<TodoItem> Todos => Set<TodoItem>();

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TodoItem>(entity =>
{
entity.Property(todo => todo.Title).HasMaxLength(120).IsRequired();
entity.Property(todo => todo.Description).HasMaxLength(500);
entity.HasIndex(todo => todo.IsCompleted);
});
}
}

示例:注册数据库上下文

builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
{
"ConnectionStrings": {
"Default": "Server=.;Database=TodoDb;Trusted_Connection=True;TrustServerCertificate=True"
}
}

示例:CRUD 服务

public sealed class TodoService(AppDbContext dbContext)
{
public async Task<IReadOnlyList<TodoListItemDto>> GetAllAsync(CancellationToken cancellationToken)
{
return await dbContext.Todos
.AsNoTracking()
.OrderByDescending(todo => todo.CreatedAt)
.Select(todo => new TodoListItemDto(
todo.Id,
todo.Title,
todo.IsCompleted,
todo.CreatedAt))
.ToListAsync(cancellationToken);
}

public async Task<TodoListItemDto> CreateAsync(
CreateTodoRequest request,
CancellationToken cancellationToken)
{
var todo = new TodoItem
{
Title = request.Title.Trim(),
Description = request.Description,
};

dbContext.Todos.Add(todo);
await dbContext.SaveChangesAsync(cancellationToken);

return new TodoListItemDto(todo.Id, todo.Title, todo.IsCompleted, todo.CreatedAt);
}
}

public sealed record TodoListItemDto(
int Id,
string Title,
bool IsCompleted,
DateTimeOffset CreatedAt);

迁移命令

dotnet ef migrations add InitialCreate --project src/TodoApp.Infrastructure --startup-project src/TodoApp.Api
dotnet ef database update --project src/TodoApp.Infrastructure --startup-project src/TodoApp.Api

重点难点

  • DbContext 通常按请求使用 Scoped 生命周期,不要注册为 Singleton。
  • 查询列表时优先使用投影 DTO,避免把不需要的列加载出来。
  • 只读查询使用 AsNoTracking 降低跟踪开销。
  • 迁移文件要提交到版本库,不能只在本机数据库里改表。
  • 删除数据前要考虑软删除、外键约束和审计要求。

常见误区

误区推荐做法
直接返回实体给前端返回 DTO,控制字段和契约
每次发布自动无脑执行迁移生产环境先审查 SQL 和备份策略
查询后在内存中过滤大量数据尽量让过滤、排序、分页在数据库执行

练习

  • 为任务增加 DueDate 字段,创建迁移并更新列表 DTO。
  • 增加分页参数 pagepageSize,返回总数和当前页数据。
  • 写一个完成任务方法,找不到任务时返回失败结果。