目录
介绍
将MigrationOperations从生成SQL的新的MigrationSqlGenerator可以具有属性“ExecuteAfter”或“ExecuteBefore”。因此,允许在同一迁移中更改列之后立即更改列的值。
背景
当我想将数据库列类型从NOT NULL更改为NULL时,问题就来了。修改后,我希望我所有的"null"值都更改为真实的null。所有这一切都在同一个迁移文件中,因为我不希望一个人只执行一次迁移(因为可以通过两次迁移轻松解决该问题)。因此,编写这个查看MigrationOperation类的属性的新MigrationsSqlGenerator以选择如何对它们进行分组并按组执行它们。
我正在使用SQLite。所以,也许问题没有发生在使用另一个数据库,因此,另一个MigrationsSqlGenerator。如果有人可以对此进行测试,并看到其他数据库发生的问题,我将更改文章,说明它也修复了其他数据库。
使用代码
GitHub上的完整项目com.cyberinternauts.csharp.Database(但不包括使用方法)。
就目前而言,该项目只有两个MigrationOperation,允许将日期类型列从NULL更改为NOT NULL,反之亦然。当列为NOT NULL时,假null值为'0001-01-01 00:00:00'。
我添加了两个新的MigrationOperation调用ArbitrarySqlBefore和ArbitrarySqlAfter,因此除非需要,否则无需创建其他MigrationOperation类。
1、首先通过以下方式将项目com.cyberinternauts.csharp.Database添加到您的项目中:
- 将子模块添加到您的存储库
- 克隆没有子模块的repo
- 将代码复制到您的项目中
2、将此添加到您的DbContext:
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.ReplaceService<IMigrationsSqlGenerator,
com.cyberinternauts.csharp.Database.MigrationsSqlGenerator
<SqliteMigrationsSqlGenerator>>();
}
3、创建迁移。
4、添加using到新创建的迁移中:
using com.cyberinternauts.csharp.Database;
5、Up方法中的使用示例。
migrationBuilder.AlterColumn<DateTime>(
name: "BirthDay",
table: "MetaPersons",
type: "TEXT",
nullable: true,
oldClrType: typeof(DateTime),
oldType: "TEXT");
migrationBuilder.ChangeDateToNullable("MetaPersons", "BirthDay");
了解核心
本文最重要的一点是了解这是如何实现的。使用装饰器模式创建一个类:com.cyberinternauts.csharp.Database.MigrationsSqlGenerator。
此类实现IMigrationsSqlGenerator,因此可以使用它来代替您当前的迁移SQL生成器。它使用一个泛型参数,允许您传递当前正在使用的真实生成器,并允许您添加支持属性的新MigrationOperation类:ExecuteBefore或ExecuteAfter。
一、泛型类使用装饰器模式:
public class MigrationsSqlGenerator<GeneratorType> :
IMigrationsSqlGenerator where GeneratorType : IMigrationsSqlGenerator
该类实现IMigrationsSqlGenerator并具有需要实现IMigrationsSqlGenerator的泛型类型。和下图有区别:没有Decorator abstract类,我直接去掉了一个可用的具体类。
有了这个,该类可以用作迁移SQL生成器,它可以创建和使用特定的现有生成器。因此,由于需要创建底层生成器,该类有一个构造函数,该构造函数具有与Microsoft相同的参数:
Microsoft.EntityFrameworkCore.Migrations.MigrationsSqlGenerator。
public MigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies,
IRelationalAnnotationProvider migrationsAnnotations)
{
if (Activator.CreateInstance(typeof(GeneratorType), new object[]
{ dependencies, migrationsAnnotations }) is GeneratorType generator)
{
BaseGenerator = generator;
Dependencies = dependencies;
}
else
{
throw new MissingMethodException(typeof(GeneratorType) +
" is missing a constructor (" + typeof(MigrationsSqlGeneratorDependencies) + ",
" + typeof(IRelationalAnnotationProvider) + ")");
}
}
在构造函数中使用泛型参数(GeneratorType)而不是参数是为了继续支持使用与Microsoft相同的构造函数签名的依赖注入。
IMigrationsSqlGenerator接口的方法实现是真正的“魔法”出现的地方:重新排序操作并分组执行。
首先,它选择标记有ExecuteBefore属性的迁移操作,将它们存储并从传递的操作列表中删除它们。
// Take operations to execute before and remove them from "middle" operations
var operationsToExecuteBefore = middleOperations
.Where(o => o.GetType().CustomAttributes.Any
(a => a.AttributeType.Equals(typeof(MigrationAttributes.ExecuteBeforeAttribute))))
.ToList();
operationsToExecuteBefore.ForEach(o => middleOperations.Remove(o));
它对标有ExecuteAfter的迁移操作执行相同的操作。
// Take operations to execute after and remove them from "middle" operations
var operationsToExecuteAfter = middleOperations
.Where(o => o.GetType().CustomAttributes.Any
(a => a.AttributeType.Equals(typeof(MigrationAttributes.ExecuteAfterAttribute))))
.ToList();
operationsToExecuteAfter.ForEach(o => middleOperations.Remove(o));
它最终独立执行所有组并组合结果。
// Generate operations by group (before, middle, after)
var before = Generate(operationsToExecuteBefore, model);
var middle = BaseGenerator.Generate(middleOperations, model, options);
var after = Generate(operationsToExecuteAfter, model);
// Combine generations
var combined = new List<MigrationCommand>();
combined.AddRange(before);
combined.AddRange(middle);
combined.AddRange(after);
这给出了最终方法代码:
public IReadOnlyList<MigrationCommand> Generate(IReadOnlyList<MigrationOperation> operations,
IModel? model = null, MigrationsSqlGenerationOptions options =
MigrationsSqlGenerationOptions.Default)
{
var middleOperations = operations.ToList();
// Take operations to execute before and remove them from "middle" operations
var operationsToExecuteBefore = middleOperations
.Where(o => o.GetType().CustomAttributes.Any
(a => a.AttributeType.Equals(typeof(MigrationAttributes.ExecuteBeforeAttribute))))
.ToList();
operationsToExecuteBefore.ForEach(o => middleOperations.Remove(o));
// Take operations to execute after and remove them from "middle" operations
var operationsToExecuteAfter = middleOperations
.Where(o => o.GetType().CustomAttributes.Any
(a => a.AttributeType.Equals(typeof(MigrationAttributes.ExecuteAfterAttribute))))
.ToList();
operationsToExecuteAfter.ForEach(o => middleOperations.Remove(o));
// Generate operations by group (before, middle, after)
var before = Generate(operationsToExecuteBefore, model);
var middle = BaseGenerator.Generate(middleOperations, model, options);
var after = Generate(operationsToExecuteAfter, model);
// Combine generations
var combined = new List<MigrationCommand>();
combined.AddRange(before);
combined.AddRange(middle);
combined.AddRange(after);
// Return combined generations
return combined;
}
我跳过了遍历迁移操作的private方法Generate,并在每个迁移操作上调用一个方法Generate来获取它们正确的SQL代码。
protected IReadOnlyList<MigrationCommand> Generate
(List<MigrationOperation> operations, IModel? model)
{
MigrationCommandListBuilder migrationCommandListBuilder = new(Dependencies);
try
{
foreach (BaseMigrationOperation operation in operations)
{
operation.Generate(Dependencies, model, migrationCommandListBuilder);
}
}
catch
{
//Nothing to do
}
return migrationCommandListBuilder.GetCommandList();
}
我跳过了它,因为实际上它只是模仿了类中以下方法的行为Microsoft.EntityFrameworkCore.Migrations.MigrationsSqlGenerator。
public virtual IReadOnlyList<MigrationCommand> Generate
(IReadOnlyList<MigrationOperation> operations, IModel? model = null,
MigrationsSqlGenerationOptions options = MigrationsSqlGenerationOptions.Default)
我决定在迁移操作类中生成真正的SQL查询,因为我不想在添加新操作时修改生成器类。
兴趣点
- 只有一次迁移而不是两次
- MigrationsSqlGenerator是一个泛型类,可以与任何其他类一起使用:例如我的SqliteMigrationsSqlGenerator
- 你能想象出其他的吗?我会在这里列出它们。
https://www.codeproject.com/Tips/5327089/Executing-SQL-Code-within-EntityFrameworkCore-Migr