本文正文第一节,会对 Code First 进行基本的介绍,以及对相关名词进行说明,读者一开始可以不用在这里消耗过多时间,可以先操作一遍例子,再回过头理解。
第二节,以一个简单的例子,展示 EF Core 的 Code First 模式的操作流程。
第三节,将 Code First 的其他指令例举出来,以便于日后翻查。
第四节(未完成),将 Code First 其他一些操作,如:在迁移代码中添加 SQL 语句等。
第五节,将 Code First 模式常见的问题列举出来,防止踩坑。
以 EF Core 模型为准,使用迁移的方式,将 EF Core 模型的变化以增量的方式更新到数据库。
简单理解:以C#代码定义的数据实体,生成数据库的表结构。
数据库上下文(DbContext):继承自 DbContext,主要作用是连接数据库,跟踪数据实体状态(实体状态包括:added、modified、deleted 等),将数据库实体的状态写入数据库(持久化至数据库中)。
数据实体(Entity):C#的实体类,与数据库的表对应
数据模型(Model):暂且认为是数据库的表吧(因为官方文档的描述,感觉就像是)
约定(conventions):主要是数据实体的类名、属性。
数据注释(data annotations):应用于类上、属性的特性(如:[Table("SysUser")]
),会被 Fluent API 的配置覆盖。
Fluent API:于自定义的 DbContext 中重写 OnModelCreating 方法中,对数据模型描述的配置,如:
protected override void OnModelCreating(ModelBuilder modelBuilder) { builder.Entity<User>().ToTable("SysUser"); }
数据实体(Entity)、数据模型(Model)、约定(conventions)、数据注释(data annotations)、Fluent API 说明:
数据实体(Entity)的类名、属性等,称之为约定(conventions),约定主要是为了定义数据模型(Model)的形状。
但是光靠约定可能不足以完整描述数据模型,有时我们的数据模型与我们的数据实体可能也有差异,这时,就可以通过数据注释(data annotations)和 Fluent API 补充。
这里基于 VS Code 工具,使用命令行创建一个 WebApi 程序:
mkdir CodeFirstTest & cd CodeFirstTest #新建文件夹DbFirstTest并切换至该目录下 dotnet new webapi --framework net6.0 #新建ASP.NET6.0 WebAPI程序
EF Core 部分 Nuget 包如下:
Microsoft.EntityFrameworkCore -->> 核心包 Microsoft.EntityFrameworkCore.Design -->> Design包:Code First 或 Db First 需要 Microsoft.EntityFrameworkCore.SqlServer -->> 微软官方 SQL Server 驱动 Pomelo.EntityFrameworkCore.MySql -->> 社区 MySql 驱动 MySql.EntityFrameworkCore -->> Oracle官方 MySql 驱动
其中核心包是必须的,另外还需配备对应数据库的驱动包,而 Design 包主要是在使用 Code First 或 Db First 需要的包。
这里,我们向工程引入必须的 Nuget 包,SQL 驱动程序选择 SQL Server 的:
dotnet add package Microsoft.EntityFrameworkCore --version 6.0.4 dotnet add package Microsoft.EntityFrameworkCore.Design --version 6.0.4 dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 6.0.4 # dotnet add package Pomelo.EntityFrameworkCore.MySql --version 6.0.1
在 appsettings.json 中增加一个节点,用于连接数据库时使用。
"ConnectionStrings": { "SqlServer": "server=localhost;database=efcore;uid=sa;pwd=Qwe123456;", "MySql": "server=localhost;port=3306;database=efcore;user=root;password=123456;charset=utf8mb4;" }
新建一个自定义的数据库上下文 TestDbContext:
using Microsoft.EntityFrameworkCore; namespace CodeFirstTest; public class TestContext : DbContext { public TestContext(DbContextOptions<TestContext> options) : base(options) { } protected override void OnConfiguring(DbContextOptionsBuilder options) { base.OnConfiguring(options); } protected override void OnModelCreating(ModelBuilder builder) { builder.Entity<User>(); base.OnModelCreating(builder); } public virtual DbSet<User> User { get; set; } }
创建一个数据实体 User 如下:
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; namespace CodeFirstTest; [Table("SysUser")] public class User { [Key] public Guid Id { get; set; } = new Guid(); [StringLength(128)] [Comment("姓名")] public string? Name { get; set; } [StringLength(11)] [Comment("手机号码")] public string? Phone { get; set; } }
[Table("SysUser")]
注释数据库中的表名为 SysUser。
[StringLength(128)]
注释字符串长度,[Comment("姓名")]
注释数据库中该字段含义。
这些注释,称为“数据注释”,主要是对数据模型的补充描述(可以简单认为:对数据库的表结构的补充描述)。
关于配置数据模型的详细内容,可以翻查EF Core官方文档:创建并配置模型。
在 Program.cs 中注册服务,并配置数据库连接串。
var configuration = builder.Configuration; builder.Services.AddDbContext<TestContext>(options => { options.UseSqlServer(configuration["ConnectionStrings:SqlServer"]); });
在生成迁移之前,需要先对工程进行编译,否则会报错。
dotnet build
如果没有安装 EF 工具,需要先安装
# 安装全局工具 dotnet tool install --global dotnet-ef # 更新工具 dotnet tool update --global dotnet-ef
创建一个名为 Initial 的迁移,将会在项目根目录下生成一个 Migrations 的目录:
dotnet ef migrations add Initial
更新到数据库
dotnet ef database update
增加一个 UserController 用于测试。
using Microsoft.AspNetCore.Mvc; namespace CodeFirstTest.Controllers; [ApiController] [Route("[controller]")] public class UserController : ControllerBase { private readonly TestContext _db; public UserController(TestContext db) { _db = db; } [HttpGet] public User? Get(Guid id) { return _db.User.Find(id); } [HttpPost] public void Post(User user) { _db.User.Add(user); _db.SaveChanges(); } [HttpDelete] public bool Delete(Guid id) { User? user = _db.User.Find(id) ?? null; if (user == null) return false; _db.User.Remove(user); _db.SaveChanges(); return true; } }
dotnet build dotnet run
访问:https://localhost:7232/swagger/index.html
对接口进行操作,可以实现对 User 的增删查。
Gitee:https://gitee.com/lisheng741/testnetcore/tree/master/EFCore/CodeFirstTest
Github:https://github.com/lisheng741/testnetcore/tree/master/EFCore/CodeFirstTest
# 安装全局工具 dotnet tool install --global dotnet-ef # 更新工具 dotnet tool update --global dotnet-ef # 验证安装 dotnet ef
创建一个名为 Migrations 的目录,并生成一些文件
dotnet ef migrations add InitialCreate
创建迁移时指定迁移目录
dotnet ef migrations add InitialCreate --output-dir [directory]
删除迁移
dotnet ef migrations remove
列出所有迁移
dotnet ef migrations list
应用迁移主要有2种方式,一种是生成 SQL 脚本,一种是通过命令行工具执行命令进行迁移,具体请参考EF Core 官方文档:应用迁移。
除了这两种迁移方式外,还有一种是在程序种迁移,即将迁移的代码写入程序中,由程序运行时触发。
将迁移应用到数据库
dotnet ef database update
将迁移应用到数据库:指定迁移
dotnet ef database update AddNewTables
注意:使用该命令,也可以进行迁移回滚(回滚到之前的某个迁移)。
dotnet ef migrations script
指定迁移起点(From)
dotnet ef migrations script AddNewTables
指定迁移起点(From)和结束点(To)
dotnet ef migrations script AddNewTables AddAuditTable
幂等 SQL 脚本(idempotent):脚本将在内部检查已经应用哪些迁移(通过迁移历史记录表),并且只应用缺少的迁移。
dotnet ef migrations script --idempotent
请参考:EF Core 官方文档:管理迁移:添加原始 SQL
下面的例子,用一个新的 FullName
属性替换现有的 FirstName
和 LastName
属性,并将现存的数据转移到新的列上。
migrationBuilder.AddColumn<string>( name: "FullName", table: "Customer", nullable: true); migrationBuilder.Sql("UPDATE Customer SET FullName = FirstName + ' ' + LastName;"); migrationBuilder.DropColumn( name: "FirstName", table: "Customer"); migrationBuilder.DropColumn( name: "LastName", table: "Customer");
MigrationBuilder.Sql() 或 自定义 MigrationOperation 对象,可以对 MigrationBuilder 进行扩展。
如:想要在迁移代码中使用如下代码(CreateUser 方法为自定义方法)
migrationBuilder.CreateUser("SQLUser1", "Password");
CreateUser 自定义代码如下:
public static OperationBuilder<SqlOperation> CreateUser( this MigrationBuilder migrationBuilder, string name, string password) => migrationBuilder.Sql($"CREATE USER {name} WITH PASSWORD '{password}';");
在程序运行中可以调用的 API,用于管理数据库的创建和删除。
具体请查看EF Core 官方文档:管理迁移:列重命名。
官方举的例子:如果你将属性从 Name
重命名为 FullName
,EF Core 将生成以下迁移:
migrationBuilder.DropColumn( name: "Name", table: "Customers"); migrationBuilder.AddColumn<string>( name: "FullName", table: "Customers", nullable: true);
该迁移代码将 Name
列删除,然后添加新的列 FullName
,这样做,会导致 Name
列原有的数据丢失。
所以需要自行将该迁移代码修改如下:
migrationBuilder.RenameColumn( name: "Name", table: "Customers", newName: "FullName");