.NET 8 Web开发入门(四):注入燃料——Entity Framework Core 与 Code First 实战

一、前言:从内存到持久化

在之前的文章中,我们的数据都保存在内存列表里。一旦程序重启,数据就会丢失。在企业级开发中,数据是核心资产,必须安全地存储在数据库中。

传统开发模式下,你需要手写SQL语句(CREATE TABLE...SELECT * FROM...),这不仅繁琐,而且容易出错,还需要处理数据库连接池、事务等复杂逻辑。

Entity Framework Core (EF Core) 解决了这些问题。它是一个ORM(Object-Relational Mapper,对象关系映射器)。它就像一个智能翻译官,能把你写的C#对象自动翻译成数据库里的表,把你调用的C#方法自动翻译成SQL语句。

你只需操作C#对象,EF Core帮你搞定数据库。

二、实战准备:定义数据模型

我们将构建一个经典的"待办事项管理"系统。

2.1 安装依赖包

首先,我们需要引入EF Core的核心包以及数据库驱动。本文以最常用的 SQL Server 为例(即使你没安装SQL Server,也可以使用LocalDB或SQLite,后文会讲)。

在项目根目录下,打开终端执行以下命令:

bash 复制代码
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design

2.2 定义实体类

我们定义一个 TodoItem 类。注意,这次我们使用传统的 class 而非 record,因为在EF Core中,实体通常需要属性的可变性,且 class 对导航属性(关联关系)的支持更为成熟。

创建 Models/TodoItem.cs

csharp 复制代码
namespace MyTodoApp.Models;

// 这是一个实体类,对应数据库中的一张表
public class TodoItem
{
    // 主键。EF Core约定:名为 Id 或 <类名>Id 的属性会被自动设为主键
    public int Id { get; set; }

    // 任务标题
    public string? Title { get; set; }

    // 是否完成
    public bool IsDone { get; set; }

    // 创建时间
    public DateTime CreatedAt { get; set; } = DateTime.Now;
}

三、数据库上下文:代码与数据库的桥梁

这是EF Core中最核心的概念。DbContext 负责管理实体对象与数据库的会话状态。

创建 Data/AppDbContext.cs

csharp 复制代码
using Microsoft.EntityFrameworkCore;
using MyTodoApp.Models;

namespace MyTodoApp.Data;

// 继承自 DbContext
public class AppDbContext : DbContext
{
    // 构造函数:接收 DbContextOptions,用于配置数据库连接
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }

    // DbSet 对应数据库中的表。
    // 属性名 Todos 将成为数据库表名(EF Core默认约定为复数形式)
    public DbSet<TodoItem> Todos { get; set; }

    // 高级配置:可以通过 Fluent API 在这里配置映射关系
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        
        // 示例:如果不想用默认的表名,可以在这里指定
        // modelBuilder.Entity<TodoItem>().ToTable("TaskItems");
    }
}

刚子解读AppDbContext 就像一个仓库管理员。DbSet<TodoItem> Todos 就是仓库里的一个货架。你往 Todos 里添加对象,管理员就会记下来,等到 SaveChanges 时,他会把货架上的东西搬运到真正的数据库里。

四、依赖注入与数据库连接

回到 Program.cs,我们需要告诉应用程序:使用哪个数据库?连接字符串是什么?

4.1 配置连接字符串

打开 appsettings.json,添加连接字符串:

json 复制代码
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    // 这里使用 LocalDB,它是VS自带的轻量级SQL Server,无需安装额外软件
    // 如果你有真正的SQL Server,将 Data Source 改为你的服务器地址
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyTodoDb;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

4.2 注册服务

Program.cs 中注册 DbContext:

csharp 复制代码
using Microsoft.EntityFrameworkCore;
using MyTodoApp.Data;

var builder = WebApplication.CreateBuilder(args);

// ... 其他服务注册 ...

// 注册 DbContext
builder.Services.AddDbContext<AppDbContext>(options =>
{
    // 从配置文件读取连接字符串
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});

var app = builder.Build();

// ... 中间件配置 ...

【刚子提示】 这里我们使用了 AddDbContext,默认生命周期是 Scoped 。这意味着在一次HTTP请求中,无论你注入多少次 AppDbContext,拿到的都是同一个实例。这保证了事务的一致性,非常符合Web开发的需求。

五、见证奇迹:Code First 迁移

现在是激动人心的时刻。我们还没建数据库,甚至没装数据库管理工具。我们将通过代码"变"出一个数据库。

5.1 理解迁移

迁移 是EF Core的版本控制系统。每当你修改了实体类(比如加个字段),你就创建一个迁移文件,记录这次变更。EF Core会根据这个文件更新数据库结构。

5.2 创建并执行迁移

确保已安装工具:

bash 复制代码
dotnet tool install --global dotnet-ef

在项目根目录执行:

bash 复制代码
# 1. 创建迁移:给这次变更起个名字,比如 InitialCreate
dotnet ef migrations add InitialCreate

# 2. 更新数据库:执行迁移,生成数据库表
dotnet ef database update

执行完第一条命令后,你会看到项目中多了一个 Migrations 文件夹,里面包含了自动生成的C#代码,描述了如何创建表。

执行完第二条命令,EF Core会自动连接数据库,创建 MyTodoDb 数据库和 Todos 表。

六、实战CRUD:让数据流动起来

现在数据库准备好了,我们来实现具体的业务接口。我们将使用之前学到的异步编程知识。

6.1 获取所有待办

csharp 复制代码
app.MapGet("/todos", async (AppDbContext db) =>
{
    // 直接返回数据库中的所有数据
    // EF Core 会自动将其翻译为 SELECT * FROM Todos
    return await db.Todos.ToListAsync();
});

6.2 创建新待办

csharp 复制代码
app.MapPost("/todos", async (AppDbContext db, TodoItem todo) =>
{
    // 1. 添加到上下文(此时还未写入数据库)
    db.Todos.Add(todo);
    
    // 2. 异步保存更改到数据库
    await db.SaveChangesAsync();
    
    // 3. 返回201 Created 响应
    return Results.Created($"/todos/{todo.Id}", todo);
});

6.3 更新待办状态

csharp 复制代码
app.MapPut("/todos/{id}", async (int id, AppDbContext db, TodoItem inputTodo) =>
{
    // 1. 查找记录
    var todo = await db.Todos.FindAsync(id);
    
    if (todo is null) return Results.NotFound();
    
    // 2. 更新字段
    todo.Title = inputTodo.Title;
    todo.IsDone = inputTodo.IsDone;
    
    // 3. 保存更改
    // EF Core 会自动检测哪些属性被修改了,生成最优的 UPDATE SQL
    await db.SaveChangesAsync();
    
    return Results.NoContent();
});

6.4 删除待办

csharp 复制代码
app.MapDelete("/todos/{id}", async (int id, AppDbContext db) =>
{
    var todo = await db.Todos.FindAsync(id);
    if (todo is null) return Results.NotFound();
    
    db.Todos.Remove(todo);
    await db.SaveChangesAsync();
    
    return Results.NoContent();
});

七、开发者的进阶思考:分离关注点

虽然我们把所有代码都写在了 Program.cs 里,但这只是为了教学演示。在真实的企业项目中,随着业务变复杂,Program.cs 会变得臃肿不堪。

你应该已经开始感受到,架构设计 就是合理的"拆分"。

  • Model:定义数据形状。
  • DbContext:负责数据库交互。
  • Service:负责业务逻辑(如果有的话)。
  • Minimal API:负责HTTP协议处理(接收请求、返回响应)。

在后续的高级篇章中,我会介绍如何使用仓储模式服务层 来进一步封装 EF Core 的调用,让代码更加整洁。

八、总结与下篇预告

在这篇近3000字的文章中,我们完成了一个巨大的跨越:

  1. 理解了ORM与EF Core的概念。
  2. 掌握了Code First开发流程:定义实体 -> 创建Context -> 迁移。
  3. 实现了完整的CRUD异步接口。

你的应用现在已经是一个真正的、有持久化能力的Web应用了!

下一篇预告

虽然我们的接口能跑通,但还有一个隐患:如果前端传来的数据格式不对怎么办?如果用户提交了一个空标题,或者SQL注入攻击? 在下一篇文章中,我们将引入数据验证异常处理。我会教你如何构建一个坚固的盾牌,保护你的API免受"脏数据"和"运行时崩溃"的侵害。

准备好给你的应用穿上铠甲吧!

原文链接:.NET 8 Web开发入门(四):注入燃料------Entity Framework Core 与 Code First 实战 - 码农刚子的开发笔记

相关推荐
咏方舟【长江支流】10 小时前
【开源】跨语言·跨平台·跨数据库(4) ——用宝框架 ORM 数据源适配器
数据库·开源·ai编程·orm·咏方舟-长江支流·用宝框架·用宝框架orm
咏方舟【长江支流】11 小时前
【开源】跨语言·跨平台·跨数据库(3) —— 为用宝框架增加ORM,让AI执行
数据库·人工智能·开源·ai编程·orm·咏方舟-长江支流·用宝框架
想你依然心痛2 天前
金融信创规模化改造方案:银行多业务批量迁移工程实践——分级集群、读写分离、零丢失容灾与 SpringBoot 适配源码全解
数据安全·读写分离·高可用集群·数据库迁移·成都银行·金融信创·springboot 适配
KaiwuDB3 天前
丝滑迁移!MySQL 迁移至 KaiwuDB 实战攻略
数据库·mysql·kaiwudb·数据库迁移·kwdb
hubro7 天前
.NET热门ORM真实的性能测试总结
测试·orm·性能化化
ClouGence11 天前
SQL Server CDC 能放到 Always On 备库读吗?一文讲透原理与实践
数据库·sql server
zzzzzz3101 个月前
LMCache 深度解析:LLM 推理加速的秘密武器,TTFT 降低 13 倍是怎么做到的?
pytorch·机器学习·orm
Jabes.yang1 个月前
互联网大厂Java求职面试实战解析(含技术场景与详解)
spring boot·微服务·面试·orm·技术栈·java se·jakarta ee
落木萧萧8251 个月前
MyBatisGX 批量操作:比 MyBatis-Plus 和 MyBatis-Flex 更好用、更快
mybatis·orm
喵个咪1 个月前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm