您当前的位置: 首页 >  sql

寒冰屋

暂无认证

  • 2浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

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

寒冰屋 发布时间:2022-07-26 22:35:59 ,浏览量:2

目录

介绍

背景

使用代码

了解核心

兴趣点

介绍

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

背景

当我想将数据库列类型从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调用ArbitrarySqlBefore和ArbitrarySqlAfter,因此除非需要,否则无需创建其他MigrationOperation类。

1、首先通过以下方式将项目com.cyberinternauts.csharp.Database添加到您的项目中:

  • 将子模块添加到您的存储库
  • 克隆没有子模块的repo
  • 将代码复制到您的项目中

2、将此添加到您的DbContext:

protected override void OnConfiguring(DbContextOptionsBuilder options)
{
    options.ReplaceService();
}

3、创建迁移。

4、添加using到新创建的迁移中:

using com.cyberinternauts.csharp.Database;

5、Up方法中的使用示例。

migrationBuilder.AlterColumn(
    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 : 
    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();
combined.AddRange(before);
combined.AddRange(middle);
combined.AddRange(after);

这给出了最终方法代码:

public IReadOnlyList Generate(IReadOnlyList 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();
    combined.AddRange(before);
    combined.AddRange(middle);
    combined.AddRange(after);

    // Return combined generations
    return combined;
}

我跳过了遍历迁移操作的private方法Generate,并在每个迁移操作上调用一个方法Generate来获取它们正确的SQL代码。

protected IReadOnlyList Generate
          (List 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 Generate
  (IReadOnlyList operations, IModel? model = null, 
  MigrationsSqlGenerationOptions options = MigrationsSqlGenerationOptions.Default)

我决定在迁移操作类中生成真正的SQL查询,因为我不想在添加新操作时修改生成器类。

兴趣点
  • 只有一次迁移而不是两次
  • MigrationsSqlGenerator是一个泛型类,可以与任何其他类一起使用:例如我的SqliteMigrationsSqlGenerator
  • 你能想象出其他的吗?我会在这里列出它们。

https://www.codeproject.com/Tips/5327089/Executing-SQL-Code-within-EntityFrameworkCore-Migr

关注
打赏
1665926880
查看更多评论
立即登录/注册

微信扫码登录

0.0459s