Vb.net 如何覆盖默认的SQL迁移生成器?
我正试图覆盖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生成类和一个上下文类(下面的代码没有反映这一点,但我注释掉了实际测试中的额外内容)。如图所示,在
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符号为外键定义自定义名称
通常情况下,我们会继续创建一个自定义逻辑来定义您的自定义逻辑,这通常有两种方式:
- 我们已经注意到,我们无法使用此选项
关联中访问注释,即使它们相对容易定义
我很惊讶地发现了这一点,并偶然发现了这篇文章,进一步证实了这一点:
更新我开始写这篇文章时假设约定是正确的方式,因为我将其用于多年来需要应用的许多其他定制。如果您希望实现其他类似类型的定制,请首先查看约定
我们仍然可以轻松覆盖生成迁移代码文件的标准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