gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-05 05:06:59 26 4
gpt4 key购买 nike

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

不幸的是,它并没有那么顺利。

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

我尝试了另一种配置构造,如讨论的那样 herehere ,但是当我尝试生成迁移时出现此错误:

More than one migrations configuration type was found in the assembly 'ConsoleApp1'. Specify the name of the one to use.

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

System.InvalidOperationException: The type 'ConsoleApp1.Db.CustomDbConfiguration2' does not inherit from 'System.Data.Entity.DbConfiguration'. Entity Framework code-based configuration classes must inherit from '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

上下文

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

最佳答案

Disclaimer: I haven't coded in VB for many years, these code examples are my feeble attempt to translate my working example in C# into OPs native VB. Please feel free to update my syntax ;)

您可以手动编辑迁移脚本,通过在调用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")

如果您的模型中有很多键,并且您想要实现一个特定的约定,(就像您想要在给定中应用的标准规则或代码序列中一样scenarios),然后通常第一个寻找解决方案的地方是EF Code First Conventions .

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

Normally we would go ahead and create a Custom Code First Convention to define your custom logic, this works in generally 2 ways:

  1. Your convention executes standard configuration via Fluent Notation
    • we already noted that this option is not available to us...
  2. Your convention logic stores custom metadata to the model via annotations

Primary and Foreign keys seem to be an anomaly in the EF Code First Runtime, there does not seem to be a way to easily access the annotations from the associations even though they are relatively easy to define.

I was surprised to find this and stumbled across this post that further confirms this: https://stackoverflow.com/a/54369685/1690217

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

我们仍然可以轻松地覆盖生成迁移代码文件的标准 VisualBasicMigrationCodeGenerator,所以让我们直接进入。 共同为您的 ForeignKey 应用自定义名称,然后实现自定义 MigrationCodeGenerator 来处理约定的输出。

  1. 创建自定义VisualBasicMigrationCodeGenerator
  2. 注册代码生成器,以便 EF 使用它生成下一个迁移

NOTE: This will not force existing keys in your database to be renamed. To do that you would need to force each key to be dropped and re-added back. For a large model Consider using a T4 template to create custom once-off migration logic to achieve this, once the above steps are in place.

Think of your Custom VisualBasicMigrationCodeGenerator as your personal EF code first sour dough culture, you can share this and re-use it for every new application, adding new functionality and improvements with each iteration. But Conventions are the configuration options that you may not want in every project, (which is why using _Conventions_ for OPs solution was my first direction.)

1。创建自定义 VisualBasicMigrationCodeGenerator

创建一个继承自 EF VisualBasicMigrationCodeGenerator 的新类,我们至少需要重写 AddForeignKeyOperation 并修改 Name key 并调用基本实现。这将影响添加到模型中的所有新 key 。

要定位作为 CreateTable 的一部分添加的键,我们必须覆盖 GenerateInline(AddForeignKeyOperation...),但是基本实现(在 C# 生成器中... ) 不遵守自定义 Name 因此我们必须完全替换实现。

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

Please excuse this C#, I didn't have time to translate it, it does generate the correct VB code though ;)

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 命令需要删除 key ,您可能还需要类似地覆盖 Generate(DropForeignKeyOperation...) 方法首先。

关于vb.net - 如何覆盖默认的 SQL 迁移生成器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59494942/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com