繁体   English   中英

如何在 EF Core Code First 中自定义迁移生成?

[英]How to customize migration generation in EF Core Code First?

我的DbContext中有一个特殊的基表类型。 当从它继承时,我需要生成一个额外的“SQL”迁移操作来为它创建一个特定的触发器。 它通过检查重叠范围来确保表结构是一致的。 由于 SQL 服务器中没有重叠的索引或检查约束,我必须使用触发器(在检查约束中使用函数会导致与迁移相同的问题以及 SQL 中的杂乱函数“命名空间”)。

由于在OnModelCreating期间我没有找到任何创建触发器的方法,因此我想到了更改生成的迁移。 但是怎么做呢?

尝试使用SqlServerMigrationsSqlGeneratorSqlServerMigrationsAnnotationProvider ,但顾名思义,它们仅用于最后阶段,即在生成 SQL 命令期间。 这使得它们在使用迁移时有点“隐藏”。 难以在需要时进行定制并在事后进行维护。

考虑过使用似乎非常适合我的需求的CSharpMigrationOperationGenerator 但是有一个问题 - 我无法访问这个 class。 也不是名称空间。

根据消息来源,此 class 位于Microsoft.EntityFrameworkCore.Migrations.Design命名空间中并且是公共的。 为了访问它,必须安装Microsoft.EntityFrameworkCore.Design package。

但它不起作用。

我在这里想念什么? 如何访问和继承这个 class? 或者,在迁移特定表的过程中,是否有更好、更合适的方法来自动创建触发器?

如何提供自己的ICSharpMigrationOperationGenerator实现

考虑过使用似乎非常适合我的需求的 CSharpMigrationOperationGenerator。 但是有一个问题 - 我无法访问这个 class。 也不是名称空间。

根据消息来源,此 class 位于 Microsoft.EntityFrameworkCore.Migrations.Design 命名空间中并且是公共的。 为了访问它,必须安装 Microsoft.EntityFrameworkCore.Design package。

但它不起作用。

我在这里想念什么? 如何访问和继承这个 class?

假设您在设计时调用以下 CLI 命令来添加新迁移:

dotnet ef migrations add "SomeMigration"

这是一个完整的示例控制台程序,它将使用一个名为MyCSharpMigrationOperationGenerator的自定义ICSharpMigrationOperationGenerator实现,继承自CSharpMigrationOperationGenerator

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Migrations.Design;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace IssueConsoleTemplate
{
    public class MyCSharpMigrationOperationGenerator : CSharpMigrationOperationGenerator
    {
        public MyCSharpMigrationOperationGenerator(CSharpMigrationOperationGeneratorDependencies dependencies)
            : base(dependencies)
        {
        }

        protected override void Generate(CreateTableOperation operation, IndentedStringBuilder builder)
        {
            Console.WriteLine("\r\n\r\n---\r\nMyCSharpMigrationOperationGenerator was used\r\n---\r\n");
            base.Generate(operation, builder);
        }
    }
    
    public class MyDesignTimeServices : IDesignTimeServices
    {
        public void ConfigureDesignTimeServices(IServiceCollection services)
            => services.AddSingleton<ICSharpMigrationOperationGenerator, MyCSharpMigrationOperationGenerator>();
    }
    
    public class IceCream
    {
        public int IceCreamId { get; set; }
        public string Name { get; set; }
    }
    
    public class Context : DbContext
    {
        public DbSet<IceCream> IceCreams { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseSqlServer(@"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=So63575132")
                .UseLoggerFactory(
                    LoggerFactory.Create(
                        b => b
                            .AddConsole()
                            .AddFilter(level => level >= LogLevel.Information)))
                .EnableSensitiveDataLogging()
                .EnableDetailedErrors();
        }
    }

    internal static class Program
    {
        private static void Main()
        {
        }
    }
}

MyCSharpMigrationOperationGenerator class 为每个添加的表输出以下行,以证明它被调用:

---
MyCSharpMigrationOperationGenerator was used
---

正如@KasbolatKumakhov 在他的评论中指出的那样,还应该指出的是,引用Microsoft.EntityFrameworkCore.Design的方式已从 2.2 更改 到 3.0:

从 EF Core 3.0 开始,它是一个 DevelopmentDependency package。 这意味着依赖项不会传递到其他项目,并且默认情况下您不能再引用它的程序集。 [...] 如果您需要引用此 package 来覆盖 EF Core 的设计时行为,那么您可以在项目中更新 PackageReference 项元数据。

 <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.0.0"> <PrivateAssets>all</PrivateAssets> <;-- Remove IncludeAssets to allow compiling against the assembly --> <;--<IncludeAssets>runtime; build; native; contentfiles analyzers buildtransitive</IncludeAssets>--> </PackageReference>

如何正确实现额外的MigrationOperation (例如用于触发器创建)

由于在 OnModelCreating 期间我没有找到任何创建触发器的方法,因此我想到了更改生成的迁移。 但是怎么做呢?

要正确执行此操作,您需要执行以下操作:

  • 将您自己的注释添加到有问题的表中(例如MyPrefix:Trigger
  • 实现您自己的MigrationOperation (例如CreateTriggerMigrationOperation
  • 提供您自己的IMigrationsModelDiffer实现(派生自MigrationsModelDiffer ;这是内部的)返回您自己的MigrationOperation
  • 提供您自己的ICSharpMigrationOperationGenerator实现(派生自CSharpMigrationOperationGenerator ),然后为您自己的MigrationOperation生成 C# 代码
  • 提供您自己的IMigrationsSqlGenerator实现(从SqlServerMigrationsSqlGenerator派生),然后将您自己的MigrationOperation转换为 SQL

这并不完全符合您的要求,但它以低成本完成了类似的工作,并且对某人来说可能会派上用场。

using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;

public static class MigrationBuilderExtensions
{
    public static void ConfigForOracle(this MigrationBuilder migrationBuilder)
    {
        //For each table registered in the builder, let's create a sequence and a trigger
        foreach (CreateTableOperation createTableOperation in migrationBuilder.Operations.ToArray().OfType<CreateTableOperation>())
        {
            string tableName = createTableOperation.Name;
            string primaryKey = createTableOperation.PrimaryKey.Columns[0];
            migrationBuilder.CreateSequence<int>(name: $"SQ_{tableName}", schema: createTableOperation.Schema);
            migrationBuilder.Sql($@"CREATE OR REPLACE TRIGGER ""TR_{tableName}""
                                    BEFORE INSERT ON ""{tableName}""
                                    FOR EACH ROW
                                    WHEN (new.""{primaryKey}"" IS NULL)
                                    BEGIN
                                        SELECT ""SQ_{tableName}"".NEXTVAL
                                        INTO   :new.""{primaryKey}""
                                        FROM   dual;
                                    END;");
        }
    }
}

您可以在扩展方法中做任何您想做的事情,然后在Migration.Up()方法的末尾调用它。 我用它为 Oracle 11g 表创建序列和触发器,以增加标识符。

打开您的迁移文件并更改您的Up方法。

然后使用 package 管理器控制台中的Update-Database应用迁移。

像这样的东西:

public partial class CreateDatabase : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.Sql("Some custom SQL statement");
        migrationBuilder.CreateTable(
            name: "Authors",
            columns: table => new
            {
                AuthorId = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                FirstName = table.Column<string>(nullable: true),
                LastName = table.Column<string>(nullable: true)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Authors", x => x.AuthorId);
            });
    }
}

我认为它不是为了修改 ef 核心 csharp 代码生成。 但是要生成自定义迁移语句(在我的情况下为触发器),我使用 SqlOperation 执行以下操作(缩短为相关)。

实现一个 ModelDiffer

public class MyMigrationsModelDiffer : MigrationsModelDiffer {

  public MyMigrationsModelDiffer(IRelationalTypeMappingSource typeMappingSource,
    IMigrationsAnnotationProvider migrationsAnnotations,
    IChangeDetector changeDetector,
    IUpdateAdapterFactory updateAdapterFactory,
    CommandBatchPreparerDependencies commandBatchPreparerDependencies)
    : base(typeMappingSource, migrationsAnnotations, changeDetector, updateAdapterFactory, commandBatchPreparerDependencies) { }

  protected override IEnumerable<MigrationOperation> Diff(IModel source, IModel target, DiffContext diffContext) {
    return base.Diff(source, target, diffContext).Concat(GetTriggerTriggerDifferences(source, target));
  }

  public override Boolean HasDifferences(IModel source, IModel target) {
    return base.HasDifferences(source, target) || HasTriggerAnnotationDifferences(source, target);
  }

  public IEnumerable<MigrationOperation> GetTriggerTriggerDifferences(IModel source, IModel target) {
    if (source == null || target == null) {
      return new new List<MigrationOperation>(0);
    }

    Dictionary<String, IAnnotation> triggerAnnotationPerEntity = new Dictionary<String, IAnnotation>();
    foreach (var entityType in source.GetEntityTypes()) {
      triggerAnnotationPerEntity[entityType.Name] = GetTableAnnotation(entityType);
    }
    var operations = new List<MigrationOperation>();
    foreach (var entityType in target.GetEntityTypes()) {
      triggerAnnotationPerEntity.TryGetValue(entityType.Name, out IAnnotation sourceTriggerTable);
      IAnnotation targetTriggerTable = GetTableAnnotation(entityType);

      if (targetTriggerTable?.Value == sourceTriggerTable?.Value) {
        continue;
      }

      Boolean isCreate = targetTriggerTable != null;
      String tableName = (entityType as EntityType)?.GetTableName();
      String primaryKey = entityType.FindPrimaryKey().Properties[0].Name;
      if (isCreate) {
        SqlOperation sqlOperation = new SqlOperation();
        sqlOperation.Sql = $@"CREATE TRIGGER...";
        operations.Add(sqlOperation);
      }
      else {
        // drop trigger sqloperation
      }
    }
    return operations;
  }

  private static IAnnotation GetTableAnnotation(IEntityType entityType) {
    return entityType.GetAnnotations()?.FirstOrDefault(x => x.Name == "WantTrigger");
  }

  public Boolean HasTriggerAnnotationDifferences(IModel source, IModel target) {
    return GetTriggerTriggerDifferences(source, target).Any();
  }
}

替换 DbContext 中不同的 model

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
  base.OnConfiguring(optionsBuilder);
  if (optionsBuilder == null) {
    return;
  }
  optionsBuilder.ReplaceService<IMigrationsModelDiffer, MyMigrationsModelDiffer>();
}

用注释标记所需的模型。

builder.Entity<MyTable>().HasAnnotation("WantTrigger", "1.0");

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM