第 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 条评论

  • 头像
    沈星 投稿者

    收藏了,感谢分享

    无记录
    回复