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。 - 增加分页参数
page、pageSize,返回总数和当前页数据。 - 写一个完成任务方法,找不到任务时返回失败结果。