无私分享:ASP.NET CORE 项目实战(第四章)Code First 创建数据库和数据表

在前几章中,我们搭建了项目的基本结构,配置了依赖注入,并初步了解了 MVC 模式。现在,我们将进入项目开发的核心环节之一:数据访问。本文将详细介绍如何在 ASP.NET Core 项目中使用 Entity Framework (EF) Core 的 Code First 模式来创建数据库和数据表。

什么是 Code First? Code First 是一种开发模式,它允许我们专注于业务逻辑,首先在代码中定义领域模型(实体类),然后通过 EF Core 的强大功能,根据这些模型自动生成数据库架构(表、列、关系等)。这种方式让开发人员可以完全通过 C# 代码来管理数据库,实现了非常优雅的 ORM(对象关系映射)。

目录#

  1. 环境准备与项目结构
  2. 创建实体模型(Model)
  3. 创建数据库上下文(DbContext)
  4. 配置连接字符串
  5. 生成数据库迁移(Migration)
  6. 将迁移应用到数据库
  7. 常见操作与最佳实践
  8. 总结
  9. 参考

环境准备与项目结构#

我们假设你已经有了一个基础的 ASP.NET Core MVC 项目。我们将继续使用这个项目,并为其添加数据访问层。

所需 NuGet 包: 请确保你的项目(通常是主项目和数据层项目)已安装以下包:

  • Microsoft.EntityFrameworkCore: EF Core 的核心功能。
  • Microsoft.EntityFrameworkCore.SqlServer: 用于连接 SQL Server 数据库的提供程序。如果你使用其他数据库(如 MySQL, PostgreSQL, SQLite),请安装对应的提供程序包(例如 Pomelo.EntityFrameworkCore.MySql)。
  • Microsoft.EntityFrameworkCore.Tools: 包含用于生成迁移和更新数据库的命令行工具。这个包需要安装在主项目(启动项目)中,并且其安装方式应设置为 PackageReference,并在 .csproj 文件中包含 PrivateAssets="all"

.csproj 文件示例:

<ItemGroup>
  <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
  <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.6" />
  <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6" PrivateAssets="all" />
</ItemGroup>

创建实体模型(Model)#

实体模型是数据库表的映射,通常是一个简单的 C# 类(POCO)。每个属性通常对应数据库表中的一个字段。

让我们创建两个简单的实体:Blog(博客)和 Post(文章),它们之间是一对多的关系。

Models/Blog.cs

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
 
namespace MyProject.Models
{
    public class Blog
    {
        // 主键,EF Core 默认会将名为 Id 或 <类名>Id 的属性识别为主键
        public int BlogId { get; set; }
 
        // 使用 DataAnnotations 进行数据验证和映射配置
        [Required] // 非空约束
        [MaxLength(100)] // 最大长度
        public string Title { get; set; }
 
        [MaxLength(500)]
        public string Description { get; set; }
 
        // 导航属性:代表一个 Blog 拥有多个 Post
        // 这是建立一对多关系的核心
        public virtual ICollection<Post> Posts { get; set; }
 
        // 可选:添加一些审计字段
        public DateTime CreatedTime { get; set; } = DateTime.Now;
        public DateTime? UpdatedTime { get; set; } // 可空,因为创建时可能还未更新
    }
}

Models/Post.cs

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
 
namespace MyProject.Models
{
    public class Post
    {
        public int PostId { get; set; }
 
        [Required]
        [MaxLength(200)]
        public string Title { get; set; }
 
        // 使用此注解可以将字段类型设置为 TEXT 或 NVARCHAR(MAX)
        // 具体类型取决于数据库提供程序
        [Column(TypeName = "ntext")]
        public string Content { get; set; }
 
        // 外键属性
        // 命名规则 <导航属性名>Id 或 <主表主键名>,EF Core 会自动识别
        public int BlogId { get; set; }
 
        // 导航属性:代表一篇文章属于一个博客
        // virtual 关键字支持延迟加载(需要安装 Microsoft.EntityFrameworkCore.Proxies 包)
        public virtual Blog Blog { get; set; }
 
        public DateTime CreatedTime { get; set; } = DateTime.Now;
    }
}

最佳实践:

  • 使用 DataAnnotations(如 [Required], [MaxLength])不仅可以配置数据库,还能在模型绑定阶段进行验证。
  • 为外键属性(如 BlogId)和对应的导航属性(如 Blog)使用约定的命名,EF Core 会自动建立关系。你也可以使用 [ForeignKey] 注解显式指定。
  • 考虑添加审计字段(如 CreatedTime, UpdatedTime),这在业务系统中非常有用。

创建数据库上下文(DbContext)#

DbContext 类是 EF Core 的核心,它代表与数据库的一个会话,用于查询和保存数据。我们需要创建一个继承自 DbContext 的类。

Data/ApplicationDbContext.cs

using Microsoft.EntityFrameworkCore;
using MyProject.Models;
 
namespace MyProject.Data
{
    public class ApplicationDbContext : DbContext
    {
        // 通过构造函数将配置选项(如连接字符串)传递给基类
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }
 
        // 使用 DbSet<T> 属性来表示数据库中的表
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }
 
        // 可选:重写此方法,可以进一步配置模型或种子数据
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
 
            // 示例:为 Blog 实体配置表名和索引
            modelBuilder.Entity<Blog>(entity =>
            {
                entity.ToTable("Blogs"); // 显式指定表名(可选)
                entity.HasIndex(e => e.Title).IsUnique(); // 为 Title 创建唯一索引
            });
 
            // 示例:配置一对多关系(通常 EF Core 可以根据导航属性自动配置,这是显式配置方式)
            modelBuilder.Entity<Post>()
                .HasOne(p => p.Blog)        // Post 有一个 Blog
                .WithMany(b => b.Posts)     // Blog 有很多 Posts
                .HasForeignKey(p => p.BlogId) // 使用 BlogId 作为外键
                .OnDelete(DeleteBehavior.Cascade); // 配置级联删除:当博客被删除时,其所有文章也被删除
        }
    }
}

配置连接字符串#

连接字符串包含了连接数据库所需的信息(服务器地址、数据库名、认证信息等)。我们通常将其放在 appsettings.json 文件中。

appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyProjectDb;Trusted_Connection=true;MultipleActiveResultSets=true;TrustServerCertificate=True;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
  • (localdb)\\mssqllocaldb: 连接本地 SQL Server Express LocalDB 实例。
  • MyProjectDb: 要创建的数据库名称。
  • Trusted_Connection=true: 使用 Windows 身份验证。
  • MultipleActiveResultSets=true: 允许在同一连接上执行多个命令。
  • TrustServerCertificate=True: 用于本地开发,信任证书(尤其是在 SQL Server 2022 及更高版本中)。

接下来,我们需要在 Program.cs 中注册 ApplicationDbContext 服务,并指定使用哪个连接字符串。

Program.cs

using Microsoft.EntityFrameworkCore;
using MyProject.Data;
 
var builder = WebApplication.CreateBuilder(args);
 
// 从 appsettings.json 中读取连接字符串
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
 
// 将 ApplicationDbContext 注册为服务,使用 SQL Server 提供程序
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
 
// 添加其他服务(MVC 等)
builder.Services.AddControllersWithViews();
 
var app = builder.Build();
 
// ... 配置 HTTP 请求管道等

生成数据库迁移(Migration)#

迁移是 EF Core 用于将模型更改同步到数据库的核心机制。它是一组 C# 代码文件,描述了如何从当前数据库状态升级到新状态(或如何回滚)。

步骤:

  1. 打开 程序包管理器控制台(Package Manager Console)(工具 -> NuGet 包管理器 -> 程序包管理器控制台)。
  2. 确保默认项目选择的是安装了 Microsoft.EntityFrameworkCore.Tools 的主项目。
  3. 执行以下命令:
Add-Migration InitialCreate
  • Add-Migration: 创建新迁移的命令。
  • InitialCreate: 迁移的名称,应具有描述性,例如 AddBlogsTable, AddEmailToUser 等。

执行成功后,你会在项目中看到一个新创建的 Migrations 文件夹,里面包含类似以下文件:

  • 20240521080000_InitialCreate.cs: 主迁移文件,包含 Up 方法(应用更改)和 Down 方法(回滚更改)。
  • ApplicationDbContextModelSnapshot.cs: 当前模型的快照,EF Core 用它来检测后续的模型变化。

最佳实践:

  • 每次对实体模型进行重大修改后,都应创建一个新的迁移。
  • 迁移名称应清晰描述其目的。
  • 检查生成的迁移代码,确保它符合你的预期(尤其是在进行复杂修改时)。

将迁移应用到数据库#

生成了迁移文件后,它还没有应用到数据库。我们需要执行一个命令来更新数据库架构。

有两种主要方式:

1. 使用程序包管理器控制台:

Update-Database

此命令会应用所有尚未应用到数据库的迁移。

2. 在代码中应用迁移(适用于生产环境或 CI/CD):Program.cs 中,我们可以在应用启动时自动确保数据库已创建并应用所有迁移。

// ... 上面的配置代码
 
var app = builder.Build();
 
// 创建作用域以获取 DbContext 实例
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    try
    {
        var context = services.GetRequiredService<ApplicationDbContext>();
        // 这将应用所有挂起的迁移,如果数据库不存在则创建它。
        context.Database.Migrate();
        // 注意:Migrate() 在生产环境中是安全的,它只会应用新的迁移。
        // 对于初始化种子数据,可以在这里调用 SeedData.Initialize(context);
    }
    catch (Exception ex)
    {
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(ex, "An error occurred while migrating the database.");
    }
}
 
// ... 配置 HTTP 请求管道

验证结果: 运行应用程序后,打开 SQL Server 对象资源管理器(SSOX)SQL Server Management Studio(SSMS),你应该能看到一个名为 MyProjectDb 的数据库,其中包含 BlogsPosts 表,并且表结构、主键、外键关系都已正确创建。

常见操作与最佳实践#

1. 更新模型与添加新迁移#

如果你修改了模型(例如,在 Blog 类中添加了一个 string Author { get; set; } 属性),只需执行:

Add-Migration AddAuthorToBlog
Update-Database

2. 回滚迁移#

如果你发现最新的迁移有问题,可以回滚到上一个版本。

Update-Database PreviousMigrationName
# 例如: Update-Database InitialCreate

或者,要完全移除最后一次迁移(删除迁移文件):

Remove-Migration

注意:此命令仅在迁移尚未应用到数据库时有效。如果已经应用,请先使用 Update-Database 回滚。

3. 生成 SQL 脚本#

在生产环境中,直接运行 Update-Database 可能不安全。更好的做法是生成一个 SQL 脚本,由 DBA 审核后执行。

Script-Migration -From PreviousMigration -To NewMigration -Output migration.sql
# 例如,生成从空数据库到最新的脚本:
Script-Migration -From 0 -To LatestMigration -Output full_database_script.sql

4. 最佳实践总结#

  • 领域模型优先: 专注于设计清晰、反映业务的实体类。
  • 频繁迁移: 小步快跑,每次修改都生成一个迁移,便于管理和排查问题。
  • 版本控制迁移文件: 务必将 Migrations 文件夹纳入版本控制(如 Git)。
  • 谨慎使用自动迁移context.Database.EnsureCreated() 可以快速创建数据库,但它不支持迁移,仅适用于原型设计或简单场景。Migrate() 是更专业的选择。
  • 分离 DbContext: 对于大型项目,考虑将 DbContext 和实体模型放在单独的程序集中(类库)。

总结#

通过本章的学习,我们成功地使用 EF Core 的 Code First 模式,从零开始创建了实体模型、数据库上下文,并通过迁移功能生成了实际的数据库和数据表。这种开发流程极大地提高了开发效率,让我们能够用熟悉的 C# 语言来管理数据架构。

你现在已经掌握了 ASP.NET Core 项目中进行数据持久化的基础。在下一章中,我们将学习如何通过 Repository 模式和依赖注入来使用这个 DbContext,实现对数据的增删改查(CRUD)操作。


参考#

  1. 微软官方文档:EF Core 文档
  2. .NET API 浏览器
  3. 本文示例代码仓库(如果提供)

希望这篇详细的指南能帮助你顺利掌握 ASP.NET Core 中的 Code First 开发!如有任何问题,欢迎留言讨论。