无私分享:ASP.NET CORE 项目实战(第四章)Code First 创建数据库和数据表
在前几章中,我们搭建了项目的基本结构,配置了依赖注入,并初步了解了 MVC 模式。现在,我们将进入项目开发的核心环节之一:数据访问。本文将详细介绍如何在 ASP.NET Core 项目中使用 Entity Framework (EF) Core 的 Code First 模式来创建数据库和数据表。
什么是 Code First? Code First 是一种开发模式,它允许我们专注于业务逻辑,首先在代码中定义领域模型(实体类),然后通过 EF Core 的强大功能,根据这些模型自动生成数据库架构(表、列、关系等)。这种方式让开发人员可以完全通过 C# 代码来管理数据库,实现了非常优雅的 ORM(对象关系映射)。
目录#
环境准备与项目结构#
我们假设你已经有了一个基础的 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# 代码文件,描述了如何从当前数据库状态升级到新状态(或如何回滚)。
步骤:
- 打开 程序包管理器控制台(Package Manager Console)(工具 -> NuGet 包管理器 -> 程序包管理器控制台)。
- 确保默认项目选择的是安装了
Microsoft.EntityFrameworkCore.Tools的主项目。 - 执行以下命令:
Add-Migration InitialCreateAdd-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 的数据库,其中包含 Blogs 和 Posts 表,并且表结构、主键、外键关系都已正确创建。
常见操作与最佳实践#
1. 更新模型与添加新迁移#
如果你修改了模型(例如,在 Blog 类中添加了一个 string Author { get; set; } 属性),只需执行:
Add-Migration AddAuthorToBlog
Update-Database2. 回滚迁移#
如果你发现最新的迁移有问题,可以回滚到上一个版本。
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.sql4. 最佳实践总结#
- 领域模型优先: 专注于设计清晰、反映业务的实体类。
- 频繁迁移: 小步快跑,每次修改都生成一个迁移,便于管理和排查问题。
- 版本控制迁移文件: 务必将
Migrations文件夹纳入版本控制(如 Git)。 - 谨慎使用自动迁移:
context.Database.EnsureCreated()可以快速创建数据库,但它不支持迁移,仅适用于原型设计或简单场景。Migrate()是更专业的选择。 - 分离 DbContext: 对于大型项目,考虑将
DbContext和实体模型放在单独的程序集中(类库)。
总结#
通过本章的学习,我们成功地使用 EF Core 的 Code First 模式,从零开始创建了实体模型、数据库上下文,并通过迁移功能生成了实际的数据库和数据表。这种开发流程极大地提高了开发效率,让我们能够用熟悉的 C# 语言来管理数据架构。
你现在已经掌握了 ASP.NET Core 项目中进行数据持久化的基础。在下一章中,我们将学习如何通过 Repository 模式和依赖注入来使用这个 DbContext,实现对数据的增删改查(CRUD)操作。
参考#
希望这篇详细的指南能帮助你顺利掌握 ASP.NET Core 中的 Code First 开发!如有任何问题,欢迎留言讨论。