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文件中,可以找到目标数据库连接字符串。