简体   繁体   English

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

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

There is a special base table type in my DbContext .我的DbContext中有一个特殊的基表类型。 And when inherited from it I need to generate an additional "SQL" migration operation to create a specific trigger for it.当从它继承时,我需要生成一个额外的“SQL”迁移操作来为它创建一个特定的触发器。 It makes sure table structure is consistent by checking overlapped ranges.它通过检查重叠范围来确保表结构是一致的。 Since there are no overlapping indexes or check constraints in SQL Server I have to use triggers (using functions in check constraints leads to same problems with migrations along with cluttering functions "namespace" in SQL).由于 SQL 服务器中没有重叠的索引或检查约束,我必须使用触发器(在检查约束中使用函数会导致与迁移相同的问题以及 SQL 中的杂乱函数“命名空间”)。

Since I haven't found any way to create triggers during OnModelCreating I thought of altering generated migrations.由于在OnModelCreating期间我没有找到任何创建触发器的方法,因此我想到了更改生成的迁移。 But how to do that?但是怎么做呢?

Tried using SqlServerMigrationsSqlGenerator and SqlServerMigrationsAnnotationProvider , but as their name suggests they are used only on a final stage, during generation of SQL commands.尝试使用SqlServerMigrationsSqlGeneratorSqlServerMigrationsAnnotationProvider ,但顾名思义,它们仅用于最后阶段,即在生成 SQL 命令期间。 This makes them a bit "hidden" from sight when using migrations.这使得它们在使用迁移时有点“隐藏”。 Hard to customize when needed and maintain afterwards.难以在需要时进行定制并在事后进行维护。

Thought about using CSharpMigrationOperationGenerator which seems to be perfect for my needs.考虑过使用似乎非常适合我的需求的CSharpMigrationOperationGenerator But there is a problem - I can't access this class.但是有一个问题 - 我无法访问这个 class。 Nor it's namespace.也不是名称空间。

According to source this class resides in Microsoft.EntityFrameworkCore.Migrations.Design namespace and is public.根据消息来源,此 class 位于Microsoft.EntityFrameworkCore.Migrations.Design命名空间中并且是公共的。 And in order to access it a Microsoft.EntityFrameworkCore.Design package has to be installed.为了访问它,必须安装Microsoft.EntityFrameworkCore.Design package。

But it doesn't work.但它不起作用。

What am I missing here?我在这里想念什么? How to access and inherit this class?如何访问和继承这个 class? Or perhaps there is a much better and proper way to create triggers automatically during migrations for specific tables?或者,在迁移特定表的过程中,是否有更好、更合适的方法来自动创建触发器?

How to provide your own ICSharpMigrationOperationGenerator implementation如何提供自己的ICSharpMigrationOperationGenerator实现

Thought about using CSharpMigrationOperationGenerator which seems to be perfect for my needs.考虑过使用似乎非常适合我的需求的 CSharpMigrationOperationGenerator。 But there is a problem - I can't access this class.但是有一个问题 - 我无法访问这个 class。 Nor it's namespace.也不是名称空间。

According to source this class resides in Microsoft.EntityFrameworkCore.Migrations.Design namespace and is public.根据消息来源,此 class 位于 Microsoft.EntityFrameworkCore.Migrations.Design 命名空间中并且是公共的。 And in order to access it a Microsoft.EntityFrameworkCore.Design package has to be installed.为了访问它,必须安装 Microsoft.EntityFrameworkCore.Design package。

But it doesn't work.但它不起作用。

What am I missing here?我在这里想念什么? How to access and inherit this class?如何访问和继承这个 class?

Let's assume you are calling the following CLI command to add a new migration at design time:假设您在设计时调用以下 CLI 命令来添加新迁移:

dotnet ef migrations add "SomeMigration"

Here is a fully working sample console program, that will use a custom ICSharpMigrationOperationGenerator implementation called MyCSharpMigrationOperationGenerator , inherited from CSharpMigrationOperationGenerator :这是一个完整的示例控制台程序,它将使用一个名为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()
        {
        }
    }
}

The MyCSharpMigrationOperationGenerator class outputs the following lines for every added table as prove that it was called: MyCSharpMigrationOperationGenerator class 为每个添加的表输出以下行,以证明它被调用:

---
MyCSharpMigrationOperationGenerator was used
---

As @KasbolatKumakhov pointed out in his comment, it should also be mentinued that the way for referencing Microsoft.EntityFrameworkCore.Design has been changed from 2.2.正如@KasbolatKumakhov 在他的评论中指出的那样,还应该指出的是,引用Microsoft.EntityFrameworkCore.Design的方式已从 2.2 更改 to 3.0:到 3.0:

Starting with EF Core 3.0, it is a DevelopmentDependency package.从 EF Core 3.0 开始,它是一个 DevelopmentDependency package。 This means that the dependency won't flow transitively into other projects, and that you can no longer, by default, reference its assembly.这意味着依赖项不会传递到其他项目,并且默认情况下您不能再引用它的程序集。 [...] If you need to reference this package to override EF Core's design-time behavior, then you can update PackageReference item metadata in your project. [...] 如果您需要引用此 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>

How to properly implement an additional MigrationOperation (eg for trigger creation)如何正确实现额外的MigrationOperation (例如用于触发器创建)

Since I haven't found any way to create triggers during OnModelCreating I thought of altering generated migrations.由于在 OnModelCreating 期间我没有找到任何创建触发器的方法,因此我想到了更改生成的迁移。 But how to do that?但是怎么做呢?

To do this properly, you would need to do the following:要正确执行此操作,您需要执行以下操作:

  • Add your own annotation to the tables in question (eg MyPrefix:Trigger )将您自己的注释添加到有问题的表中(例如MyPrefix:Trigger
  • Implement your own MigrationOperation (eg CreateTriggerMigrationOperation )实现您自己的MigrationOperation (例如CreateTriggerMigrationOperation
  • Provide your own IMigrationsModelDiffer implementation (derived from MigrationsModelDiffer ; this is internal) that returns your own MigrationOperation提供您自己的IMigrationsModelDiffer实现(派生自MigrationsModelDiffer ;这是内部的)返回您自己的MigrationOperation
  • Provide your own ICSharpMigrationOperationGenerator implementation (derived from CSharpMigrationOperationGenerator ), that then generates the C# code for your own MigrationOperation提供您自己的ICSharpMigrationOperationGenerator实现(派生自CSharpMigrationOperationGenerator ),然后为您自己的MigrationOperation生成 C# 代码
  • Provide your own IMigrationsSqlGenerator implementation (derived from SqlServerMigrationsSqlGenerator ) that then handles translating your own MigrationOperation to SQL提供您自己的IMigrationsSqlGenerator实现(从SqlServerMigrationsSqlGenerator派生),然后将您自己的MigrationOperation转换为 SQL

It's not exactly what you asked for, but it does the similar job with low cost and it may come handy for someone.这并不完全符合您的要求,但它以低成本完成了类似的工作,并且对某人来说可能会派上用场。

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;");
        }
    }
}

You can do whatever you want in the extension method and then call it at the end of the Migration.Up() method.您可以在扩展方法中做任何您想做的事情,然后在Migration.Up()方法的末尾调用它。 I use it to create sequences and triggers for Oracle 11g tables for identifier increment.我用它为 Oracle 11g 表创建序列和触发器,以增加标识符。

Open your migration file and make changes to your Up method.打开您的迁移文件并更改您的Up方法。

Then apply the migration using Update-Database from package manager console.然后使用 package 管理器控制台中的Update-Database应用迁移。

Something like this:像这样的东西:

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);
            });
    }
}

I think it is not intended to modify the ef core csharp code generation.我认为它不是为了修改 ef 核心 csharp 代码生成。 But to generate custom migration statements (in my case triggers) i do following (shortened to relevant) using SqlOperation.但是要生成自定义迁移语句(在我的情况下为触发器),我使用 SqlOperation 执行以下操作(缩短为相关)。

Implement a ModelDiffer实现一个 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();
  }
}

Replace the model differ in your DbContext替换 DbContext 中不同的 model

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

Mark the desired Models with an annotation.用注释标记所需的模型。

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

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

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