Vb.net 如何覆盖默认的SQL迁移生成器?

Vb.net 如何覆盖默认的SQL迁移生成器?,vb.net,entity-framework-6,ef-code-first,entity-framework-migrations,Vb.net,Entity Framework 6,Ef Code First,Entity Framework Migrations,我正试图覆盖SQL迁移生成器的默认行为,以便指定自定义外键约束名称,如前所述。我已经按照建议连接了配置 然而,不幸的是,事情进展得并不顺利 一条快速日志语句显示,GetFkName()函数从未被命中 如前所述,我尝试了一个备用配置构造,但在尝试生成迁移时出现了以下错误: 在程序集“ConsoleApp1”中找到多个迁移配置类型。指定要使用的名称 我觉得这个结果有点奇怪,因为我只有一个配置类、一个SQL生成类和一个上下文类(下面的代码没有反映这一点,但我注释掉了实际测试中的额外内容)。如图所示,在

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

然而,不幸的是,事情进展得并不顺利

一条快速日志语句显示,
GetFkName()
函数从未被命中

如前所述,我尝试了一个备用配置构造,但在尝试生成迁移时出现了以下错误:

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

我觉得这个结果有点奇怪,因为我只有一个配置类、一个SQL生成类和一个上下文类(下面的代码没有反映这一点,但我注释掉了实际测试中的额外内容)。如图所示,在命令行上指定配置类型会导致以下错误:

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

所有这些都让我们回到了过去,但由于上述原因(
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
上下文

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
导入System.Data.Common
导入System.Data.Entity
导入System.Data.SqlClient
输入系统。反射
名称空间数据库
好友类上下文
继承DbContext
公共分新()
MyBase.New(DbConnection.ConnectionString)
端接头
私有子新建(连接为DbConnection)
MyBase.New(连接,True)
SetInitializer(新的CreateDatabaseIfNotExists(上下文))
Database.SetInitializer(新的MigrateDatabaseToLatestVersion(上下文的Migrations.Configuration))
Me.Database.Initialize(False)
端接头
公共共享函数Create()作为上下文
返回新上下文(DbConnection)
端函数
私有共享只读属性DbConnection作为SqlConnection
得到
返回新的SqlConnection(Utils.DbConnectionString)
结束
端属性
创建模型时受保护的覆盖子项(生成器作为DbModelBuilder)
Builder.Configurations.AddFromAssembly(Assembly.getExecutionGassembly)
MyBase.OnModelCreating(生成器)
端接头
作为数据库集的公共财产文件(文件)
作为DbSet的公共财产部分(部分)
末级
结束命名空间
免责声明:我已经很多年没有用VB编写代码了,这些代码示例是我将C#中的工作示例转换为本机VB的微弱尝试。请随时更新我的语法;)

您可以手动编辑迁移脚本,通过在create table语句的调用中为可选的
name
参数指定值,为每个
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)
或作为声明的一部分:

AddForeignKey("dbo.CorporationVariety", "CorporationId", "dbo.Corporation", name := "FKCorporatationCorporationVarietyCorporationId")
AddForeignKey("dbo.CorporationVariety", "VarietyId", "dbo.Variety", name := "FKVarietyCorporationVarietyVarietyId")
如果您的模型中有很多密钥,并且您希望在所有密钥中实现特定约定(如在给定场景中要应用的标准规则或代码序列),那么通常第一个寻找解决方案的地方是

不幸的是,这里既没有一个标准约定可以帮助您,也不能使用fluent符号为外键定义自定义名称

通常情况下,我们会继续创建一个自定义逻辑来定义您的自定义逻辑,这通常有两种方式:

  • 您的约定通过Fluent表示法执行标准配置
    • 我们已经注意到,我们无法使用此选项
  • 您的约定逻辑通过注释将自定义元数据存储到模型中
  • 主键和外键似乎是EF Code First运行时中的一种异常情况,似乎没有一种方法可以轻松地从关联中访问注释,即使它们相对容易定义

    我很惊讶地发现了这一点,并偶然发现了这篇文章,进一步证实了这一点:

    更新我开始写这篇文章时假设约定是正确的方式,因为我将其用于多年来需要应用的许多其他定制。如果您希望实现其他类似类型的定制,请首先查看约定

    我们仍然可以轻松覆盖生成迁移代码文件的标准
    VisualBasicMigrationCodeGenerator
    ,因此让我们直接跳到这一步。 为您的
    ForeignKey
    应用自定义名称,然后实现一个自定义MigrationCodeGenerator来处理约定的输出

  • 创建自定义的
    VisualBasicMigrationCodeGenerator
  • 注册代码生成器,以便EF使用它生成下一次迁移
  • 注意:这不会强制重命名数据库中的现有密钥。要做到这一点,您需要强制删除并重新添加每个键。对于一个大的模型考虑使用T4模板来创建自定义一次性迁移逻辑来实现这一点,一旦上述步骤就位。 将您的自定义
    VisualBasicMigrationCodeGenerator
    视为您的个人EF代码,您可以将其共享并在每个新应用程序中重复使用,在每次迭代中添加新功能和改进。但约定是您可能不希望在每个项目中都使用的配置选项(这就是为什么我的第一个方向是在OPs解决方案中使用约定)

    1.创建自定义VisualBasicMigrationCodeGenerator 创造
    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(")");
        }
    }
    
    Public Sub New()
    
      AutomaticMigrationsEnabled = false
      CodeGenerator = new CustomVBMigrationCodeGenerator()
    
    End Sub