自定义EF Core迁移历史表

Entity Framework Core (EF Core) 是一个轻量级的、可扩展的ORM(对象关系映射)框架,用于.NET平台。在使用EF Core进行数据库迁移时,EF Core默认会在数据库中创建一个名为 __EFMigrationsHistory 的表,用于记录迁移历史。本文将介绍如何自定义这个迁移历史表的名称、架构、列名,以及如何添加带有默认值的列和必填列。

更改迁移表的名称和架构

默认情况下,迁移历史表的名称是 __EFMigrationsHistory,架构是 dbo。如果需要自定义迁移表的名称和架构,可以通过DbContext的配置来实现。以下是一个示例代码,展示了如何将迁移表的名称更改为 __Migrations,并将架构更改为 track。

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"); }); } }

在这个示例中,通过DbContext的OnConfiguring方法,使用DbContextOptionsBuilder的MigrationsHistoryTable方法,指定了迁移历史表的名称和架构。

更改迁移表的列名

迁移历史表默认包含两个列:MigrationId和ProductVersion。如果需要自定义这些列的名称,可以通过自定义IHistoryRepository服务来实现。以下是一个示例代码,展示了如何将MigrationId列的名称更改为Id,将ProductVersion列的名称更改为Version。

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>(); } } 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"); } }

在这个示例中,首先通过DbContext的OnConfiguring方法,使用DbContextOptionsBuilder的ReplaceService方法,替换了默认的IHistoryRepository服务。然后在自定义的HistoryRepository类中,通过重写ConfigureTable方法,使用EntityTypeBuilder的Property方法,指定了列的新名称。

添加带有默认值的列

有时候,可能需要在迁移历史表中添加一个带有默认值的列。以下是一个示例代码,展示了如何在迁移历史表中添加一个名为AppliedAtUtc的列,并设置其默认值为当前的UTC日期和时间。

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方法添加了一个新的列AppliedAtUtc,并设置其数据类型为DATETIME,允许空值。然后通过Sql方法添加了一个名为DF__Migrations_AppliedAtUtc的约束,将AppliedAtUtc列的默认值设置为当前的UTC日期和时间。在Down方法中,通过DropCheckConstraint方法和DropColumn方法,分别移除了约束和列。

添加必填列

有时候,可能需要在迁移历史表中添加一个必填列。以下是一个示例代码,展示了如何在迁移历史表中添加一个名为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>(); } } public class ContextConstants { public const 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(); } }

在这个示例中,首先通过DbContext的OnConfiguring方法,使用DbContextOptionsBuilder的ReplaceService方法,替换了默认的IHistoryRepository服务。然后在自定义的HistoryRepository类中,通过重写ConfigureTable方法,使用EntityTypeBuilder的Property方法,指定了新列的名称、最大长度和是否必填。然后在GetInsertScript方法中,自定义了插入语句,将新列的值设置为ContextConstants中的ProjectName常量。

关于代码示例

本文的代码示例基于Visual Studio 2022和EF Core6(也测试了EF Core 5)。测试时需要将Db.Custom项目设置为启动项目。在appsettings.json文件中,可以找到目标数据库连接字符串。

  • Add-Migration Init:添加名为Init的迁移。
  • Update-Database:更新数据库。
  • Script-Migration:生成迁移脚本。
  • Drop-Database:删除数据库。
  • Remove-Migration Init:移除名为Init的迁移。
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485