Skip to content

.NET Core 实战 [No.377~378] 迁移实体并生成数据库

🏷️ 《.NET Core 实战》

实体模型

实体模型对应数据库中的表,相比普通的类,实体类一般都需要指定主键。在实体类中有三种方法可以指定主键。

  1. 名称为 Id 或者 类名 + Id 的属性会自动被识别为主键;

    下面类中的 CarId 属性会自动识别为主键。

    csharp
    public class Car
    {
        public int CarId { get; set; }
        public string Color { get; set; }
    }
  2. 通过添加 [Key] 特性指定主键;

    下面类中的 EmpIdentity 属性会被识别为主键。

    csharp
    public class Employee
    {
        [Key]
        public int EmpIdentity { get; set; }
        public int EmpAge { get; set; }
        public int EmpName { get; set; }
    }
  3. 通过在重写 DbContextOnModelCreating 方法中调用 HasKey 方法指定主键。

    csharp
    public class Activity
    {
        public Guid ActFlag { get; set; }
        public TimeSpan Period { get; set; }
    }

    OnModelCreating 方法中指定 ActFlag 为主键。

    csharp
    public class MyDbContext : DbContext
    {
        public DbSet<Car> Cars { get; set; }
        public DbSet<Employee> Employees { get; set; }
        public DbSet<Activity> Activities { get; set; }
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Activity>().HasKey(nameof(Activity.ActFlag));
        }
    }

迁移(Migration)实体到数据库

代码优先的模式下需要根据实体更新数据库,EntityFrameworkCore 提供了几个迁移相关的工具以支持这种开发模式。

首先,需要安装 Microsoft.EntityFrameworkCore.Tools 工具包,否则话会提示找不到命令。

powershell
Install-Package Microsoft.EntityFrameworkCore.Tools

成功安装后可以通过 get-help 获取 EFCore 的帮助文档。

powershell
get-help about_EntityFrameworkCore

可以看到除了 Migration 相关的命令外,也有根据 DB 生成实体(entity)等命令。

powershell
                     _/\__
               ---==/    \\
         ___  ___   |.    \|\
        | __|| __|  |  )   \\\
        | _| | _|   \_/ |  //|\\
        |___||_|       /   \\\/\\

TOPIC
    about_EntityFrameworkCore

SHORT DESCRIPTION
    Provides information about the Entity Framework Core Package Manager Console Tools.

LONG DESCRIPTION
    This topic describes the Entity Framework Core Package Manager Console Tools. See https://docs.efproject.net for
    information on Entity Framework Core.

    The following Entity Framework Core commands are available.

        Cmdlet                      Description
        --------------------------  ---------------------------------------------------
        Add-Migration               Adds a new migration.

        Drop-Database               Drops the database.

        Get-DbContext               Gets information about a DbContext type.

        Remove-Migration            Removes the last migration.

        Scaffold-DbContext          Scaffolds a DbContext and entity types for a database.

        Script-DbContext            Generates a SQL script from the current DbContext. 

        Script-Migration            Generates a SQL script from migrations.

        Update-Database             Updates the database to a specified migration.

SEE ALSO
    Add-Migration
    Drop-Database
    Get-DbContext
    Remove-Migration
    Scaffold-DbContext
    Script-DbContext
    Script-Migration
    Update-Database

Add-Migration

可以通过 Add-Migration 命令生成迁移的代码:

语法:

powershell
Add-Migration [-Name] <String> [-OutputDir <String>] [-Context <String>] [-Project <String>] [-StartupProject <String>] [<CommonParameters>]

参数:

  • -Name:指定迁移版本的名称。-Name 可以省略不写。
  • -OutputDir:指定生成的迁移代码保存的目录,默认值是 Migrations
  • -Context:指定自定义的 DbContext 的名字。当项目中存在多个自定义的 DbContext 时需要指定使用的 DbContext 的名称。
  • -Project:在 VS 的程序包管理控制器中执行时,若没有指定该参数,则默认使用程序包管理控制器顶部 默认项目 选项中选择的项目。
  • -StartupProject:指定启动的项目名称。若未指定,则默认使用解决方案中的启动项目。
powershell
Add-Migration InitialCreate

在本机尝试执行上述命令时出现了 ScriptHalted 的错误,同时尝试打开 工具 → 命令行 → 开发者 PowerShell 工具时显示了如下错误:

开发者 PowerShell 需要 PowerShell 3.0 或更高版本。可以从 https://aka.ms/installpowershell 安装最新版本。

提示 PowerShell 版本过低,在上面错误信息的链接中提供了各个版本的安装包的下载地址。

本机环境是 64 位 Win7 SP1,所以在 WMF 3.0 上下载了 Windows6.1-KB2506143-x64.msu 文件,安装了 PS3.0。

安装完成后可以通过 $PSVersionTable 命令查看其版本。执行结果中 PSVersion 对应的值就是 PowerShell 的版本。

之后再次执行 Add-Migration 命令,显示了如下错误:

No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.

看错误信息是因为没有提供 provider 或者没有提供一个接受 DbContextOptions<TContext> 对象的构造函数。

可以通过重写 DbContext.OnConfiguring 方法来指定 provider,也可以通过在 Startup.ConfigureServices 方法中调用 AddDbContext 来指定。

csharp
public void ConfigureServices(IServiceCollection services)
{
    // something else

    services.AddDbContext<MyDbContext>(optionsBuilder => optionsBuilder.UseSqlServer(Configuration.GetConnectionString("LocalDB")));
}

这里通过 Configuration.GetConnectionString 方法获取 appsettings.json 配置文件中的数据库连接字符串。

由于使用的是 SqlServer,还需要安装 Microsoft.EntityFrameworkCore.SqlServer 包。

powershell
Install-Package Microsoft.EntityFrameworkCore.SqlServer

然后还需在自定义的 DbContext 中添加一个带 DbContextOptions 类型参数的构造函数,并传递给基类。

csharp
public class MyDbContext : DbContext
{
    // something else

    public MyDbContext(DbContextOptions options) : base(options) { }
    
    // something else
}

之后再次执行 Add-Migration 命令,执行成功会看到类似如下的结果:

Both Entity Framework Core and Entity Framework 6 are installed. The Entity Framework Core tools are running. Use 'EntityFramework\Add-Migration' for Entity Framework 6.
Build started...
Build succeeded.
To undo this action, use Remove-Migration.

此时会在项目的 Migrations 目录下生成一个 20200419045958_InitialCreate.cs 文件。

代码包含连个方法 UpDownUp 会在更新数据库时调用,Down 则是在回退时调用。

csharp
public partial class InitialCreate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Activities",
            columns: table => new
            {
                ActFlag = table.Column<Guid>(nullable: false),
                Period = table.Column<TimeSpan>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Activities", x => x.ActFlag);
            });

        migrationBuilder.CreateTable(
            name: "Cars",
            columns: table => new
            {
                CarId = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                Color = table.Column<string>(nullable: true)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Cars", x => x.CarId);
            });

        migrationBuilder.CreateTable(
            name: "Employees",
            columns: table => new
            {
                EmpIdentity = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                EmpAge = table.Column<int>(nullable: false),
                EmpName = table.Column<int>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Employees", x => x.EmpIdentity);
            });
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(
            name: "Activities");

        migrationBuilder.DropTable(
            name: "Cars");

        migrationBuilder.DropTable(
            name: "Employees");
    }
}

如果再次执行,即使没有做任何修改,也还是会生成一个迁移代码文件,只不过 UpDown 方法体是空的。

powershell
Add-Migration EditNothing
csharp
public partial class EditNothing : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {

    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {

    }
}

Remove-Migration

Add-Migration 的执行结果中提到了 Remove-Migration 命令,这个命令用来删除生成的迁移代码。

为了防止误删除,Remove-Migration 命令只能每次删除一个迁移版本,而且不能指定版本名称,只能删除最近的一个迁移版本。

powershell
Remove-Migration [-Force] [-Context <String>] [-Project <String>] [-StartupProject <String>] [<CommonParameters>]

成功执行的话会显示类似如下的信息:

Build started...
Build succeeded.
Removing migration '20200419052626_InitialCreate'.
Removing model snapshot.
Done.

若迁移版本已经更新到数据库,删除时会显示类似如下的错误:

The migration '20200419052626_InitialCreate' has already been applied to the database. Revert it and try again. If the migration has been applied to other databases, consider reverting its changes using a new migration.

此时需要添加 -Force 参数,指定删除的同时回滚数据库的修改。

powershell
Remove-Migration -Force

Update-Database

若要应用迁移到数据库,可以使用 Update-Database 命令。它提供了一个 -Migration 可选参数用于指定要应用到数据库的迁移版本,未指定时默认应用所有的迁移版本。

powershell
Update-Database [[-Migration] <String>] [-Context <String>] [-Project <String>] [-StartupProject <String>] [<CommonParameters>]

若找不到服务器,则会显示如下错误信息:

A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 - Error Locating Server/Instance Specified)

执行成功会显示如下信息:

Build started...
Build succeeded.
Done.

EFCore 提供的其它命令

除了上面讲到的三个名另外,从之前的帮助文档中可以看到 EFCore 还提供了另外几个命令:

Drop-Database

删除数据库。

语法:

powershell
Drop-Database [-Context <String>] [-Project <String>] [-StartupProject <String>] [-WhatIf] [-Confirm] [<CommonParameters>]

Get-DbContext

获取 DbContext 类型信息。

语法:

powershell
Get-DbContext [-Context <String>] [-Project <String>] [-StartupProject <String>] [<CommonParameters>]

示例:

powershell
Get-DbContext -Context "MyDbContext"

执行结果:

powershell
Build started...
Build succeeded.

providerName                             databaseName  dataSource       options
------------                             ------------  ----------       -------
Microsoft.EntityFrameworkCore.SqlServer  SampleDb      EDG2GYKVF8G5P6Z  None

Scaffold-DbContext

从数据库构建 DbContext 和实体。

语法:

powershell
Scaffold-DbContext [-Connection] <String> [-Provider] <String> [-OutputDir <String>] [-ContextDir <String>] [-Context <String>] [-Schemas <String[]>] [-Tables <String[]>] [-DataAnnotations] [-UseDatabaseNames] [-Force] [-Project <String>] [-StartupProject <String>] [<CommonParameters>]

示例可以参考之前的一篇博客

Script-DbContext

从一个 DbContext 生成 SQL 脚本。

语法:

powershell
Script-DbContext [-Output <String>] [-Context <String>] [-Project <String>] [-StartupProject <String>] [<CommonParameters>]

示例:

csharp
Script-DbContext -Output "MyDb.sql" -Context "MyDbContext"

成功运行后会在解决方案的根目录生成一个 MyDb.sql 文件,内容如下:

sql
CREATE TABLE [Activities] (
    [ActFlag] uniqueidentifier NOT NULL,
    [Period] time NOT NULL,
    CONSTRAINT [PK_Activities] PRIMARY KEY ([ActFlag])
);
GO

CREATE TABLE [Cars] (
    [CarId] int NOT NULL IDENTITY,
    [Color] nvarchar(max) NULL,
    CONSTRAINT [PK_Cars] PRIMARY KEY ([CarId])
);
GO

CREATE TABLE [Employees] (
    [EmpIdentity] int NOT NULL IDENTITY,
    [EmpAge] int NOT NULL,
    [EmpName] int NOT NULL,
    CONSTRAINT [PK_Employees] PRIMARY KEY ([EmpIdentity])
);
GO

Script-Migration

生成两个版本之前改动的 SQL 脚本。

语法:

powershell
Script-Migration [-From] <String> [-To] <String> [-Idempotent] [-Output <String>] [-Context <String>] [-Project <String>] [-StartupProject <String>] [<CommonParameters>]
powershell
Script-Migration [[-From] <String>] [-Idempotent] [-Output <String>] [-Context <String>] [-Project <String>] [-StartupProject <String>] [<CommonParameters>]

示例:

powershell
Script-Migration InitialCreate AddCarNumber -Context "MyDbContext"

上面命令中的 InitialCreateAddCarNumber 均为迁移的版本名称。

运行成功会生成类似如下的脚本,其中还包含了为了测试所生成的一个没有任何变更的迁移版本。

sql
INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20200419080544_EditNothing', N'3.1.3');

GO

ALTER TABLE [Cars] ADD [Number] nvarchar(max) NULL;

GO

INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion])
VALUES (N'20200419084331_AddCarNumber', N'3.1.3');

GO

参考:《.NET Core 实战:手把手教你掌握 380 个精彩案例》 -- 周家安 著