C# 如何首先在EF核心代码中定制迁移生成?
我的C# 如何首先在EF核心代码中定制迁移生成?,c#,sql-server,asp.net-core,entity-framework-core,entity-framework-migrations,C#,Sql Server,Asp.net Core,Entity Framework Core,Entity Framework Migrations,我的DbContext中有一种特殊的基表类型。当从它继承时,我需要生成一个额外的“SQL”迁移操作来为它创建一个特定的触发器。它通过检查重叠范围来确保表结构的一致性。因为SQL Server中没有重叠的索引或检查约束,所以我必须使用触发器(在检查约束中使用函数会导致迁移出现同样的问题,同时SQL中的函数“名称空间”也会很混乱) 因为我还没有找到任何方法在建模过程中创建触发器,所以我想改变生成的迁移。但如何做到这一点呢 尝试使用SQLServerMigrationSqlGenerator和SQLS
DbContext
中有一种特殊的基表类型。当从它继承时,我需要生成一个额外的“SQL”迁移操作来为它创建一个特定的触发器。它通过检查重叠范围来确保表结构的一致性。因为SQL Server中没有重叠的索引或检查约束,所以我必须使用触发器(在检查约束中使用函数会导致迁移出现同样的问题,同时SQL中的函数“名称空间”也会很混乱)
因为我还没有找到任何方法在建模过程中创建触发器,所以我想改变生成的迁移。但如何做到这一点呢
尝试使用SQLServerMigrationSqlGenerator
和SQLServerMigrationAnnotationProvider
,但顾名思义,它们仅在生成SQL命令的最后阶段使用。这使得它们在使用迁移时有点“隐藏”在视线之外。在需要时很难定制并在以后进行维护
考虑使用CSharpMigrationOperationGenerator
,它似乎非常适合我的需要。但是有一个问题-我不能访问这个类。也不是名称空间
根据源代码,该类位于Microsoft.EntityFrameworkCore.Migrations.Design命名空间中,是公共的。为了访问它,必须安装Microsoft.EntityFrameworkCore.Design
软件包
但它不起作用
我错过了什么?如何访问和继承这个类?或者,有一种更好、更合适的方法可以在迁移过程中为特定表自动创建触发器?打开迁移文件并更改
Up
方法
然后从package manager控制台使用更新数据库应用迁移
大概是这样的:
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);
});
}
}
公共部分类CreateDatabase:迁移
{
受保护的覆盖作废(MigrationBuilder MigrationBuilder)
{
Sql(“一些自定义Sql语句”);
migrationBuilder.CreateTable(
姓名:“作者”,
列:表=>new
{
AuthorId=table.Column(可空:false)
.Annotation(“SqlServer:ValueGenerationStrategy”,SqlServerValueGenerationStrategy.IdentityColumn),
FirstName=table.Column(可空:true),
LastName=table.Column(可空:true)
},
约束:表=>
{
表.PrimaryKey(“PK_Authors”,x=>x.authord);
});
}
}
如何提供自己的ICSharpMigrationOperationGenerator
实现
考虑使用CSharpMigrationOperationGenerator,它似乎非常适合我的需要。但是有一个问题-我不能访问这个类。也不是名称空间
根据源代码,此类位于Microsoft.EntityFrameworkCore.Migrations.Design命名空间中,是公共的。为了访问它,必须安装Microsoft.EntityFrameworkCore.Design软件包
但它不起作用
我错过了什么如何访问和继承此类?
假设您在设计时调用以下CLI命令来添加新迁移:
dotnet ef migrations add "SomeMigration"
下面是一个完全工作的示例控制台程序,它将使用一个自定义的ICSharpMigrationOperationGenerator
实现,名为mycsharrpmigrationoperationgenerator
,它继承自csharrpmigrationoperationgenerator
:
使用系统;
使用Microsoft.EntityFrameworkCore;
使用Microsoft.EntityFrameworkCore.Design;
使用Microsoft.EntityFrameworkCore.Internal;
使用Microsoft.EntityFrameworkCore.Migrations.Design;
使用Microsoft.EntityFrameworkCore.Migrations.Operations;
使用Microsoft.Extensions.DependencyInjection;
使用Microsoft.Extensions.Logging;
命名空间IssueConsoleTemplate
{
公共类MyCSharpMigrationOperationGenerator:CSharpMigrationOperationGenerator
{
公共MyCSharpMigrationOperationGenerator(CSharpMigrationOperationGeneratorDependencies依赖项)
:base(依赖项)
{
}
受保护的覆盖无效生成(CreateTableOperation操作,缩进StringBuilder)
{
Console.WriteLine(“\r\n\r\n--\r\nMySharpMigrationOperationGenerator被使用\r\n--\r\n”);
生成(操作、生成器);
}
}
公共类MyDesignTimeServices:IDesignTimeServices
{
public void配置DesignTimeServices(iSeries收集服务)
=>services.AddSingleton();
}
公共级冰淇淋
{
public int IceCreamId{get;set;}
公共字符串名称{get;set;}
}
公共类上下文:DbContext
{
公共DbSet冰淇淋{get;set;}
配置时受保护的覆盖无效(DBContextOptions Builder Options Builder)
{
选项生成器
.UseSqlServer(@“数据源=。\MSSQL14;集成安全性=SSPI;初始目录=So63575132”)
.UseLoggerFactory(
LoggerFactory,创建(
b=>b
.AddConsole()
.AddFilter(level=>level>=LogLevel.Information)))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
}
内部静态类程序
{
私有静态void Main()
{
}
}
}
MyCSharpMigrationOperationGenerator
类为每个添加的表输出以下行,以证明它被调用:
---
使用MyCSharpMigrationOperationGenerator
---
正如@KasbolatKumakhov在他的评论中指出的那样,还应该注意从2.2中引用Microsoft.EntityFrameworkCore.Design
的方式。T
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();
}
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
base.OnConfiguring(optionsBuilder);
if (optionsBuilder == null) {
return;
}
optionsBuilder.ReplaceService<IMigrationsModelDiffer, MyMigrationsModelDiffer>();
}
builder.Entity<MyTable>().HasAnnotation("WantTrigger", "1.0");
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;");
}
}
}