以正确的顺序在EntityFrameworkCore迁移中执行SQL代码(SQLite)

目录

介绍

背景

使用代码

了解核心

兴趣点


介绍

MigrationOperations从生成SQL的新的MigrationSqlGenerator可以具有属性ExecuteAfterExecuteBefore。因此,允许在同一迁移中更改列之后立即更改列的值。

背景

当我想将数据库列类型从NOT NULL更改为NULL时,问题就来了。修改后,我希望我所有的"null"值都更改为真实的null。所有这一切都在同一个迁移文件中,因为我不希望一个人只执行一次迁移(因为可以通过两次迁移轻松解决该问题)。因此,编写这个查看MigrationOperation类的属性的新MigrationsSqlGenerator以选择如何对它们进行分组并按组执行它们。

我正在使用SQLite。所以,也许问题没有发生在使用另一个数据库,因此,另一个MigrationsSqlGenerator。如果有人可以对此进行测试,并看到其他数据库发生的问题,我将更改文章,说明它也修复了其他数据库。

我在StackOverFlow上提出的第一个问题。

使用代码

GitHub上的完整项目com.cyberinternauts.csharp.Database(但不包括使用方法)。

就目前而言,该项目只有两个MigrationOperation,允许将日期类型列从NULL更改为NOT NULL,反之亦然。当列为NOT NULL时,假null值为'0001-01-01 00:00:00'

我添加了两个新的MigrationOperation调用ArbitrarySqlBeforeArbitrarySqlAfter,因此除非需要,否则无需创建其他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类:ExecuteBeforeExecuteAfter

一、泛型类使用装饰器模式:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值