65.9K
CodeProject 正在变化。 阅读更多。
Home

自定义 Entity Framework Core 迁移历史记录表

2022 年 8 月 3 日

CPOL

2分钟阅读

viewsIcon

13069

downloadIcon

96

自定义 EF Core 迁移表的选项

背景

Entity Framework Core 通过在名为 __EFMigrationsHistory 的表中,以及 dbo 模式中添加日志来跟踪已应用的迁移。迁移表包含两列,MigrationIdProductVersion。在这里,我们将探讨一些自定义此表的选项。

  • 更改迁移表名和模式
  • 更改迁移表的列名
  • 在迁移表中添加默认值列
  • 在迁移表中添加必填列

更改表名和模式

我们将为迁移表使用自定义表名 __Migrations 和模式名 track。模式名是可选的,如果未指定,则默认值为 dbo

public class AppDb : DbContext
{
    public DbSet<Process> Process { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var config = new ConfigurationBuilder()
            .AddJsonFile(Path.Combine(AppContext.BaseDirectory, "appsettings.json"), 
             optional: false, reloadOnChange: true)
            .Build();
        optionsBuilder
            .UseSqlServer(config.GetConnectionString("DatabaseConnection"), 
                d => { d.MigrationsHistoryTable("__Migrations", "track"); });
    }
}

更改列名

要更改列名,我们将用自定义配置类 HistoryRepository 替换类型为 IHistoryRepository 的现有/默认服务。

public class AppDb : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var config = new ConfigurationBuilder()
            .AddJsonFile(Path.Combine(AppContext.BaseDirectory, "appsettings.json"), 
                         optional: false, reloadOnChange: true)
            .Build();
        optionsBuilder
            .UseSqlServer(config.GetConnectionString("DatabaseConnection")})
            .ReplaceService<IHistoryRepository, HistoryRepository>();
    }
}

这里,我们将表列名从

  • MigrationId 更改为 Id
  • ProductVersion 更改为 Version
internal class HistoryRepository : SqlServerHistoryRepository
{
    public HistoryRepository(HistoryRepositoryDependencies dependencies) : 
                             base(dependencies)
    {
    }
    protected override void ConfigureTable(EntityTypeBuilder<HistoryRow> history)
    {
        base.ConfigureTable(history);
        history.Property(h => h.MigrationId).HasColumnName("Id");
        history.Property(h => h.ProductVersion).HasColumnName("Version");
    }
}

ConfigureTable(EntityTypeBuilder<HistoryRow> history) 方法内部,这部分实际上看起来更小,就像使用 Fluent API 进行实体映射一样。我们应该能够更改其他内容,例如表名、模式名、数据类型等。我还没有尝试过,但从逻辑上讲,应该可以实现。

添加带有默认值的列

向项目中添加一个空的迁移 Init。重要的是要确保这是第一个迁移,并且在将任何表或任何其他对象添加到 DbContext 类之前添加。最初,UpDown 方法将为空。

Add-Migration Init
向上

将新的 AppliedAtUtc 列添加到迁移表 [track].[__Migrations],并为该列创建一个约束 DF__Migrations_AppliedAtUtc,以将当前的 UTC 日期时间设置为默认值。

向下

删除我们在 up 部分添加的约束以及列本身。

public partial class Init : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.Sql("ALTER TABLE [track].[__Migrations] 
                              ADD AppliedAtUtc DATETIME NULL;");
        migrationBuilder.Sql("ALTER TABLE [track].[__Migrations] 
                              ADD CONSTRAINT DF__Migrations_AppliedAtUtc 
                              DEFAULT GETUTCDATE() FOR [AppliedAtUtc];");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropCheckConstraint("DF__Migrations_AppliedAtUtc", 
                                             "__Migrations", "track");
        migrationBuilder.DropColumn("AppliedAtUtc", "__Migrations", "track");
    }
}

等效 SQL

/*changes*/
ALTER TABLE [track].[__Migrations] ADD CreatedON DATETIME NULL;
ALTER TABLE [track].[__Migrations] _
      ADD CONSTRAINT DF__Migrations_CreatedON DEFAULT GETUTCDATE() FOR [CreatedON];
/*rollback*/
ALTER TABLE [track].[__Migrations] DROP CONSTRAINT DF__Migrations_CreatedON;
ALTER TABLE [track].[__Migrations] DROP COLUMN CreatedON;

添加必填列

在这里,我们将向迁移表中添加一个必填列 ProjectName

public class AppDb : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var config = new ConfigurationBuilder()
            .AddJsonFile(Path.Combine(AppContext.BaseDirectory, 
            "appsettings.json"), optional: false, reloadOnChange: true)
            .Build();
        optionsBuilder
            .UseSqlServer(config.GetConnectionString("DatabaseConnection")})
            .ReplaceService<IHistoryRepository, HistoryRepository>();
    }
}

ConfigureTable(EntityTypeBuilder<HistoryRow> history) 内部,我们正在添加一个新的必填列。在 GetInsertScript(HistoryRow row) 方法内部,我们正在为迁移表创建一个自定义 insert 语句。

public class ContextConstants
{
    public static string ProjectName = "Console";
}

internal class HistoryRepository : SqlServerHistoryRepository
{
    public const string CustomColumnName = "ProjectName";
    public HistoryRepository(HistoryRepositoryDependencies dependencies) : 
                             base(dependencies)
    {
    }
    protected override void ConfigureTable(EntityTypeBuilder<HistoryRow> history)
    {
        base.ConfigureTable(history);
        history.Property<string>(CustomColumnName).HasMaxLength(300).IsRequired();
    }
    public override string GetInsertScript(HistoryRow row)
    {
        var stringTypeMapping = 
            Dependencies.TypeMappingSource.GetMapping(typeof(string));
        return new StringBuilder()
            .Append("INSERT INTO ")
            .Append(SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema))
            .Append("(")
            .Append(SqlGenerationHelper.DelimitIdentifier(MigrationIdColumnName))
            .Append(", ")
            .Append(SqlGenerationHelper.DelimitIdentifier(ProductVersionColumnName))
            .Append(", ")
            .Append(SqlGenerationHelper.DelimitIdentifier(CustomColumnName))
            .Append(") ")
            .Append("VALUES (")
            .Append(stringTypeMapping.GenerateSqlLiteral(row.MigrationId))
            .Append(", ")
            .Append(stringTypeMapping.GenerateSqlLiteral(row.ProductVersion))
            .Append(", ")
            .Append(stringTypeMapping.GenerateSqlLiteral(ContextConstants.ProjectName))
            .Append(")")
            .AppendLine(SqlGenerationHelper.StatementTerminator)
            .ToString();
    }
}

关于代码示例

  • Visual Studio 2022 解决方案
  • EF Core 6(也已使用 EF Core 5 测试)
  • 检查 Db.Custom 项目的代码,AppDb.cs。为了测试,我们需要将此项目设置为启动项目。

appsettings.json 中,我们将找到目标数据库连接

{
  "ConnectionStrings": {
    "DatabaseConnection": "Data Source=.\\SQLEXPRESS;Initial Catalog=Cup;
                           Integrated Security=True"
  }
}

Commands

Add-Migration Init
Update-Database
Script-Migration

Drop-Database 
Remove-Migration Init

Db

参考文献

历史

  • 2022 年 8 月 4 日:初始版本
© . All rights reserved.