第 7 章 Entity Framework Core 入门

7.1 ORM 概念与 EF Core 架构

ORM(Object-Relational Mapping):将对象模型与关系数据库映射的技术,核心优势:

  • 用面向对象语法操作数据库(无需手写 SQL);
  • 屏蔽不同数据库差异(如 SQL Server/MySQL,代码无需修改);
  • 简化数据访问代码,提升开发效率。

EF Core(Entity Framework Core):.NET 跨平台的 ORM 框架,支持.NET 5+特点:

  • 轻量级、高性能;
  • 支持代码优先(Code-First)、数据库优先(Database-First)两种开发模式;
  • 可扩展(支持自定义插件,如批量操作、日志)。

EF Core 核心组件

  • DbContext:数据库上下文,管理实体与数据库的交互(如跟踪实体状态、执行查询、保存更改);
  • DbSet:表明数据库中的表,用于 CRUD 操作(如DbSet<User>对应 Users 表);
  • 实体(Entity):映射到数据库表的类(如 User 类对应 Users 表,类的属性对应表的字段);
  • 模型构建器(ModelBuilder):用于配置实体与表的映射关系(如主键、外键、字段类型);
  • 数据库提供程序:适配不同数据库(如Microsoft.EntityFrameworkCore.SqlServer、Pomelo.EntityFrameworkCore.MySql)。

7.2 数据模型设计与实体配置

实体设计原则

1)每个实体对应一张表,类名提议与表名一致(或通过配置指定);

2)主键属性提议命名为Id或[实体名]Id(如UserId),EF Core 默认识别;

3)属性类型与数据库字段类型匹配(如string对应nvarchar,int对应int)。

示例:基础实体类

// 用户实体(对应Users表)
public class User
{
    public int Id { get; set; } // 主键(EF Core默认识别)
    public string Name { get; set; } // 对应Name字段(nvarchar)
    public int Age { get; set; } // 对应Age字段(int)
    public decimal Balance { get; set; } // 对应Balance字段(decimal)
    public DateTime CreateTime { get; set; } // 对应CreateTime字段(datetime2)

    // 导航属性:表明与Order的一对多关系(一个用户有多个订单)
    public List<Order> Orders { get; set; } = new List<Order>();
}
// 订单实体(对应Orders表)
public class Order
{
    public int Id { get; set; }
    public string OrderNo { get; set; } // 订单编号
    public decimal TotalAmount { get; set; } // 订单总金额
    public int UserId { get; set; } // 外键(关联User.Id)

    // 导航属性:表明与User的多对一关系(一个订单属于一个用户)
    public User User { get; set; }
}

实体配置方式数据注解(Data Annotations):直接在实体属性上添加特性,简单直观,示例:

public class User
{
    [Key] // 指定为主键(若命名不符合默认规则时使用)
    public int UserId { get; set; }

    [Required] // 非空(对应数据库字段NOT NULL)
    [MaxLength(50)] // 最大长度50(对应nvarchar(50))
    public string Name { get; set; }

    [Column("UserAge")] // 指定数据库字段名为UserAge(而非Age)
    public int Age { get; set; }

    [NotMapped] // 不映射到数据库字段(仅内存中使用)
    public string FullName => $"用户-{Name}";
}

Fluent API:在DbContext的OnModelCreating方法中配置,支持更复杂的映射(如多对多关系、索引),示例:

public class AppDbContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Order> Orders { get; set; }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 配置User实体
        modelBuilder.Entity<User>(entity =>
                                  {
                                      entity.ToTable("Users"); // 指定表名(若类名与表名不同)
                                      entity.HasKey(u => u.Id); // 指定主键

                                      // 配置Name字段:非空、最大长度50、索引
                                      entity.Property(u => u.Name)
                                          .IsRequired()
                                          .HasMaxLength(50)
                                          .HasColumnType("nvarchar(50)");

                                      // 配置CreateTime:默认值为当前时间
                                      entity.Property(u => u.CreateTime)
                                          .HasDefaultValueSql("GETDATE()");

                                      // 配置与Order的一对多关系(一个User对应多个Order)
                                      entity.HasMany(u => u.Orders)
                                          .WithOne(o => o.User)
                                          .HasForeignKey(o => o.UserId) // 指定外键
                                          .OnDelete(DeleteBehavior.Cascade); // 级联删除(删除User时删除关联的Order)
                                  });
        // 配置Order实体
        modelBuilder.Entity<Order>(entity =>
                                   {
                                       entity.Property(o => o.OrderNo)
                                           .IsRequired()
                                           .HasMaxLength(20);

                                       // 为OrderNo创建唯一索引(避免重复订单号)
                                       entity.HasIndex(o => o.OrderNo).IsUnique();
                                   });
    }
}

7.3 DbContext 生命周期与配置

DbContext 生命周期:创建→使用→释放,需遵循 “短生命周期” 原则(避免长时间占用数据库连接),推荐用using语句管理。

DbContext 核心作用

  • 跟踪实体状态:实体有 4 种状态:Added(新增)、Modified(修改)、Deleted(删除)、Unchanged(未变更);
  • 执行查询:通过DbSet<T>或Set<T>()发起查询(最终转换为 SQL);
  • 保存更改:调用SaveChanges()/SaveChangesAsync(),将实体状态变更同步到数据库。

DbContext 配置(连接字符串)

方式 1:重写 OnConfiguring 方法(简单场景):

public class AppDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        // 配置SQL Server连接字符串
        string connectionString = "Server=localhost;Database=MyDB;User ID=sa;Password=123456;TrustServerCertificate=True;";
        optionsBuilder.UseSqlServer(connectionString)
            .LogTo(Console.WriteLine); // 日志输出到控制台(便于调试SQL)
    }
    public DbSet<User> Users { get; set; }
    public DbSet<Order> Orders { get; set; }
}

方式 2:依赖注入配置(ASP.NET Core/Web API 场景,推荐):

  • 第一步:安装 NuGet 包Microsoft.EntityFrameworkCore.SqlServer;
  • 第二步:在Program.cs中注册 DbContext:
var builder = WebApplication.CreateBuilder(args);
// 注册AppDbContext,从配置文件读取连接字符串
builder.Services.AddDbContext<AppDbContext> (options =>
{
    string connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
    options.UseSqlServer(connectionString);
});
var app = builder.Build();

第三步:在appsettings.json中配置连接字符串:

{
    "ConnectionStrings": {
        "DefaultConnection": "Server=localhost;Database=MyDB;User ID=sa;Password=123456;TrustServerCertificate=True;"
    }
}

DbContext 使用示例(ASP.NET Core 控制器)

[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
    private readonly AppDbContext _dbContext;
    // 构造函数注入AppDbContext(依赖注入自动管理生命周期)
    public UserController(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }
    // 查询所有用户
    [HttpGet]
    public async Task<ActionResult<List<User>>> GetAllUsers()
    {
        var users = await _dbContext.Users.ToListAsync();
        return Ok(users);
    }
    // 新增用户
    [HttpPost]
    public async Task<ActionResult<User>> AddUser(User user)
    {
        user.CreateTime = DateTime.Now;
        _dbContext.Users.Add(user); // 标记实体为Added状态
        await _dbContext.SaveChangesAsync(); // 同步到数据库,生成INSERT语句
        return CreatedAtAction(nameof(GetAllUsers), new { id = user.Id }, user);
    }
}

7.4 数据库迁移与版本控制

数据库迁移(Migration):EF Core 通过迁移自动生成 / 更新数据库结构(基于实体模型),解决 “实体变更后手动修改数据库” 的问题,支持版本控制(可回滚到历史版本)。

迁移核心命令(通过 Package Manager Console 或.NET CLI)

命令(.NET CLI)

作用

dotnet ef migrations add 迁移名

创建新迁移(对比当前模型与历史模型)

dotnet ef database update

应用所有未应用的迁移到数据库

dotnet ef database update 迁移名

回滚到指定迁移版本

dotnet ef migrations remove

删除最近创建的未应用迁移

dotnet ef migrations list

列出所有迁移及其状态(已应用 / 未应用)

迁移操作步骤

  • 初始化迁移
    • 安装 EF Core 工具:dotnet tool install –global dotnet-ef;
    • 创建第一个迁移:dotnet ef migrations add InitialCreate(生成迁移文件,包含创建表的 SQL 逻辑);
    • 应用迁移到数据库:dotnet ef database update(执行迁移文件,创建 Users、Orders 表)。
  • 实体变更后更新迁移
    • 例如:给 User 类添加Email属性:public string Email { get; set; };
    • 创建新迁移:dotnet ef migrations add AddEmailToUser;
    • 应用迁移:dotnet ef database update(自动生成 ALTER TABLE 语句,添加 Email 字段)。
  • 回滚迁移
    • 回滚到 InitialCreate 版本:dotnet ef database update InitialCreate(删除 AddEmailToUser 迁移的变更,即删除 Email 字段);
    • (可选)删除回滚后的迁移文件:dotnet ef migrations remove。

迁移文件解析

  • 每个迁移包含两个文件:
    • [迁移ID]_迁移名.cs(Up 方法:应用迁移的逻辑;Down 方法:回滚迁移的逻辑);
    • [迁移ID]_迁移名.Designer.cs(迁移元数据)。
  • 示例:AddEmailToUser 迁移的 Up 方法:
protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.AddColumn<string>(
        name: "Email",
        table: "Users",
        type: "nvarchar(max)",
        nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
    migrationBuilder.DropColumn(
        name: "Email",
        table: "Users");
}
  • 注意事项

1)迁移文件需纳入版本控制(如 Git),确保团队成员使用一样的数据库结构;

2)生产环境迁移需谨慎,提议先在测试环境验证;
3)若手动修改了数据库结构,需确保与迁移文件一致,否则会导致迁移失败。

© 版权声明

相关文章

1 条评论

  • 头像
    沈星 投稿者

    收藏了,感谢分享

    无记录
    回复