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






4.60/5 (3投票s)
自定义 EF Core 迁移表的选项
背景
Entity Framework Core 通过在名为 __EFMigrationsHistory
的表中,以及 dbo
模式中添加日志来跟踪已应用的迁移。迁移表包含两列,MigrationId
和 ProductVersion
。在这里,我们将探讨一些自定义此表的选项。
- 更改迁移表名和模式
- 更改迁移表的列名
- 在迁移表中添加默认值列
- 在迁移表中添加必填列
更改表名和模式
我们将为迁移表使用自定义表名 __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
类之前添加。最初,Up
和 Down
方法将为空。
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
参考文献
- https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/history-table
- https://stackoverflow.com/questions/55342435/how-to-customize-migration-history-table-with-entity-framework-core
历史
- 2022 年 8 月 4 日:初始版本