一个如何覆盖默认的SQL迁移生成器?

问题描述 投票:1回答:1

我正在尝试覆盖SQL迁移生成器的默认行为,以便我可以指定自定义外键约束名称,如here所述。我已按照建议连接好配置。

不幸的是,进展并不顺利。

快速记录语句显示GetFkName()函数从未被点击。

我尝试了替代配置构造,如herehere所述,但是在尝试生成迁移时出现此错误:

在程序集“ ConsoleApp1”中找到了多个迁移配置类型。指定要使用的名称。

我发现此结果有点奇怪,因为我只有一个配置类,一个SQL生成类和一个上下文类(下面的代码没有反映这一点,但是我为实际测试注释了其他内容)。在命令行上指定配置类型,如here所示,此错误:

System.InvalidOperationException:类型'ConsoleApp1.Db.CustomDbConfiguration2'不继承自'System.Data.Entity.DbConfiguration'。基于实体框架代码的配置类必须继承自'System.Data.Entity.DbConfiguration'。

所有这些都使我们回到here,但是由于上述原因(GetFkName()从未被击中),这是行不通的。因此,似乎我正在追尾巴(直到今天我才知道自己有一条)。

我应该怎么做才能使此替代正常工作?


配置

Imports System.Data.Entity
Imports System.Data.Entity.Migrations
Imports System.Data.Entity.SqlServer

Namespace Db
  Friend Class CustomDbConfiguration
    Inherits DbConfiguration

    Public Sub New()
      Me.SetMigrationSqlGenerator(SqlProviderServices.ProviderInvariantName, Function() New CustomSqlGenerator)
    End Sub
  End Class

  Friend Class CustomDbConfiguration2
    Inherits DbMigrationsConfiguration(Of Context)

    Public Sub New()
      Me.SetSqlGenerator(SqlProviderServices.ProviderInvariantName, New CustomSqlGenerator2(Me.GetSqlGenerator(SqlProviderServices.ProviderInvariantName)))
      Me.ContextType = GetType(Context)
    End Sub
  End Class
End Namespace

SQL生成器

Imports System.Data.Entity.Migrations.Model
Imports System.Data.Entity.Migrations.Sql
Imports System.Data.Entity.SqlServer

Namespace Db
  Friend Class CustomSqlGenerator
    Inherits SqlServerMigrationSqlGenerator

    Protected Overrides Sub Generate(AddForeignKeyOperation As AddForeignKeyOperation)
      AddForeignKeyOperation.Name = GetFkName(AddForeignKeyOperation.PrincipalTable, AddForeignKeyOperation.DependentTable, AddForeignKeyOperation.DependentColumns.ToArray())
      MyBase.Generate(AddForeignKeyOperation)
    End Sub

    Protected Overrides Sub Generate(DropForeignKeyOperation As DropForeignKeyOperation)
      DropForeignKeyOperation.Name = GetFkName(DropForeignKeyOperation.PrincipalTable, DropForeignKeyOperation.DependentTable, DropForeignKeyOperation.DependentColumns.ToArray())
      MyBase.Generate(DropForeignKeyOperation)
    End Sub

    Private Shared Function GetFkName(PrimaryKeyTable As String, ForeignKeyTable As String, ParamArray ForeignTableFields As String()) As String
      IO.File.WriteAllText("D:\Logs\FkNameTest.log", $"{Now.ToString}{vbCrLf}")

      Return $"FK_{ForeignKeyTable}_{PrimaryKeyTable}"
    End Function
  End Class

  Friend Class CustomSqlGenerator2
    Inherits MigrationSqlGenerator

    Public Sub New(Generator As MigrationSqlGenerator)
      Me.Generator = Generator
    End Sub

    Public Overrides Function Generate(MigrationOperations As IEnumerable(Of MigrationOperation), ProviderManifestToken As String) As IEnumerable(Of MigrationStatement)
      Return Me.Generator.Generate(MigrationOperations, ProviderManifestToken)
    End Function

    Private ReadOnly Generator As MigrationSqlGenerator
  End Class
End Namespace

Context

Imports System.Data.Common
Imports System.Data.Entity
Imports System.Data.SqlClient
Imports System.Reflection

Namespace Db
  <DbConfigurationType(GetType(CustomDbConfiguration2))>
  Friend Class Context
    Inherits DbContext

    Public Sub New()
      MyBase.New(DbConnection.ConnectionString)
    End Sub

    Private Sub New(Connection As DbConnection)
      MyBase.New(Connection, True)

      Database.SetInitializer(New CreateDatabaseIfNotExists(Of Context))
      Database.SetInitializer(New MigrateDatabaseToLatestVersion(Of Context, Migrations.Configuration))

      Me.Database.Initialize(False)
    End Sub

    Public Shared Function Create() As Context
      Return New Context(DbConnection)
    End Function

    Private Shared ReadOnly Property DbConnection As SqlConnection
      Get
        Return New SqlConnection(Utils.DbConnectionString)
      End Get
    End Property

    Protected Overrides Sub OnModelCreating(Builder As DbModelBuilder)
      Builder.Configurations.AddFromAssembly(Assembly.GetExecutingAssembly)
      MyBase.OnModelCreating(Builder)
    End Sub

    Public Property Documents As DbSet(Of Document)
    Public Property Sections As DbSet(Of Section)
  End Class
End Namespace
vb.net entity-framework-6 ef-code-first ef-migrations
1个回答
0
投票

免责声明:多年来我都没有在VB中进行编码,这些代码示例是我的拙劣尝试,试图将C#中的工作示例转换为OPs原生VB。请随时更新我的​​语法;)

您可以手动创建迁移脚本,为每个ForeignKey指定一个custom名称,方法是在创建表语句的一部分中,在对Name的调用中为可选Tablebuilder.ForeignKey参数指定一个值:] >

CreateTable(
  "dbo.CorporationVariety",
  Function(c) New With
    {
      .Id = c.Int(nullable: false, identity:= true),
      .CorporationId = c.Int(nullable:= false),
      .VarietyId = c.Int(nullable:= false),
    }) _
  .PrimaryKey(Function(t) t.Id)
  .ForeignKey("dbo.Corporation", Function(t) t.CorporationId, name := "FKCorporatationCorporationVarietyCorporationId")
  .ForeignKey("dbo.Variety", Function(t) t.VarietyId, name := "FKVarietyCorporationVarietyVarietyId")
  .Index(Function(t) t.CorporationId)
  .Index(Function(t) t.VarietyId)

或作为DbMigration.AddForeignKey语句的一部分:

AddForeignKey("dbo.CorporationVariety", "CorporationId", "dbo.Corporation", name := "FKCorporatationCorporationVarietyCorporationId")
AddForeignKey("dbo.CorporationVariety", "VarietyId", "dbo.Variety", name := "FKVarietyCorporationVarietyVarietyId")

如果您的模型中有很多键,并且您想实现特定的惯例

,(如您要在给定场景中应用的标准规则或代码序列中)所有键,然后通常寻找解决方案的第一位是EF Code First Conventions

不幸的是,既没有标准的约定可以为您提供帮助,也无法使用流畅的符号为外键定义自定义名称...

通常

我们将继续创建Custom Code First Convention来定义您的自定义逻辑,这通常以两种方式起作用:
  1. 您的约定通过Fluent表示法执行标准配置
    • 我们已经注意到,此选项对我们不可用...
  2. 您的约定逻辑通过注释将自定义元数据存储到模型中
  3. Primary

Foreign键在EF Code First Runtime中似乎是一个异常,似乎没有一种方法可以轻松地从associations访问注释,即使它们相对容易定义。

我很惊讶地发现了这一点,偶然发现了该帖子,进一步证实了这一点:https://stackoverflow.com/a/54369685/1690217

Update

我以Conventions是正确的方式开始这篇文章,因为多年来我将其用于许多其他需要应用的定制。如果要实现其他类似类型的自定义,请首先查看Conventions

我们仍然可以轻松地覆盖生成迁移代码文件的标准VisualBasicMigrationCodeGenerator,因此让我们直接跳入其中。共同应用于ForeignKey的自定义名称,然后实现自定义MigrationCodeGenerator

来处理约定的输出。
  1. 创建自定义VisualBasicMigrationCodeGenerator
  2. 注册代码生成器
  3. ,以便EF将其用于生成下一个迁移

    NOTE:

这不会强制重命名数据库中的现有密钥。为此,您需要强制删除每个密钥并重新添加回去。对于大型模型,考虑到上述步骤,请考虑使用T4模板来创建自定义的一次性迁移逻辑以实现此目标。]

首先考虑将Custom VisualBasicMigrationCodeGenerator作为您的个人EF代码酸面团文化

,您可以共享此代码并将其重新用于每个新应用程序,并在每次迭代中添加新功能和改进。但是Conventions是您可能不需要在每个项目中使用的配置选项,(这就是为什么将_Conventions_用于OP解决方案是我的第一个方向。]

1。创建一个自定义VisualBasicMigrationCodeGenerator

创建一个从EF VisualBasicMigrationCodeGenerator]继承的新类,我们要做的最少工作就是重写AddForeignKeyOperation并修改键的Name并调用基本实现。这将影响添加到模型中的所有新键。

要定位作为CreateTable的一部分添加的键,我们将不得不覆盖GenerateInline(AddForeignKeyOperation...),但是基本实现(在C#Generator中...)不遵循自定义Name,所以我们改为必须完全替换实现。

  • 执行此操作时,转到EF project on GitHub并从原始实现开始,然后根据需要注入您的自定义。

请原谅此C#,我没有时间翻译它,尽管它确实会生成正确的VB代码;)

public class CustomVBMigrationCodeGenerator : System.Data.Entity.Migrations.Design.VisualBasicMigrationCodeGenerator
{

    protected override void Generate(AddForeignKeyOperation addForeignKeyOperation, IndentedTextWriter writer)
    {
        ApplyCustomFKName(addForeignKeyOperation);
        base.Generate(addForeignKeyOperation, writer);
    }

    private void ApplyCustomFKName(ForeignKeyOperation operation)
    {
        // expecting FK without scheme or underscores: "FK{DependentTable}{PrincipalTable}{FKField}"
        operation.Name = $"FK{StripSchemeFromName(operation.DependentTable)}{StripSchemeFromName(operation.PrincipalTable)}{String.Join("", operation.DependentColumns)}";
    }
    private string StripSchemeFromName(string dbObjectName)
    {
        return dbObjectName.Split(new[] { '.' }, 2).Last();
    }

    /// <summary>
    /// Generates code to perform an <see cref="AddForeignKeyOperation" /> as part of a <see cref="CreateTableOperation" />.
    /// </summary>
    /// <param name="addForeignKeyOperation"> The operation to generate code for. </param>
    /// <param name="writer"> Text writer to add the generated code to. </param>
    protected virtual void GenerateInline(AddForeignKeyOperation addForeignKeyOperation, IndentedTextWriter writer)
    {
        // sourced from https://github.com/aspnet/EntityFramework6/blob/master/src/EntityFramework/Migrations/Design/VisualBasicMigrationCodeGenerator.cs
        Check.NotNull(addForeignKeyOperation, "addForeignKeyOperation");
        Check.NotNull(writer, "writer");

        writer.WriteLine(" _");
        writer.Write(".ForeignKey(" + Quote(addForeignKeyOperation.PrincipalTable) + ", ");
        Generate(addForeignKeyOperation.DependentColumns, writer);

        // Our Custom logic
        ApplyCustomFKName(addForeignKeyOperation);

        // Insert our custom name if provided
        if (!addForeignKeyOperation.HasDefaultName)
        {
            writer.Write(", name := " + Quote(addForeignKeyOperation.Name));
        }

        if (addForeignKeyOperation.CascadeDelete)
        {
            writer.Write(", cascadeDelete := True");
        }

        writer.Write(")");
    }
}

2。注册代码生成器,以便EF可以使用它生成下一个迁移

在项目中找到Configuration.vb

,将构造方法将CodeGenerator设置为CustomVBMigrationCodeGenerator的实例:
Public Sub New()

  AutomaticMigrationsEnabled = false
  CodeGenerator = new CustomVBMigrationCodeGenerator()

End Sub

现在执行add-migration以生成新的迁移,您将看到在迁移脚本中定义的新的自定义名称。

[如果您需要从此配置降级,或者如果alter table命令要求先删除密钥,则可能也需要类似地重写Generate(DropForeignKeyOperation...)方法。

© www.soinside.com 2019 - 2024. All rights reserved.