如何关闭 Entity Framework Core 5 中的所有约定
How to turn off ALL conventions in Entity Framework Core 5
我想关闭 ALL(或至少大部分)Entity Framework Core 中的约定(我说的是 EF Core 5 或更高版本),然后“手工”构建整个模型。
有人可能想知道为什么。
原因如下:我的任务是将几个大型遗留数据库从 Entity Framework 6 (EF
) 迁移到 Entity Framework Core 5 (EFC
)。这涉及数百个 table 和几个数据库。其中一些数据库是使用 Code First 方法创建的,而另一些只是第三方数据库,我们需要从 C# 代码查询和更新这些数据库。对于后一种数据库,我们必须完全匹配它们的架构。
由于问题的规模,代码的 EF
和 EFC
风格必须共存,比方说,几个月。这可以通过使用条件编译轻松实现(见下文)。
与 EF
相比,EFC
最有可能不支持或不方便支持的任何内容(或被“黑入”EF
模型),例如空间索引、多-column KeyAttribute
PKs,多列ForeignKeyAttribute
FKs,多次自引用tables,在同一列上定义多个索引(有些是过滤器有些只是常规索引),等等等等。
没关系。我可以很容易地处理EFC
无法通过使用条件编译“覆盖”属性来处理这个问题,例如
#if EFCORE
using Key = MyKeyAttribute;
using Column = MyColumnAttribute;
using Index = MyIndexAttribute;
using ForeignKey = MyForeignKeyAttribute;
#endif
然后为每个MyProject.csproj
创建一个MyProject_EFC.csproj
,其中定义了EFCORE
,然后使用Reflection来“收集”所有这些自定义属性,然后使用EFC
Fluent API 配置所有 EFC
做不到的事情。因此,遗留 (EF
) 代码仍然会看到原始代码,例如KeyAttribute
然后遵循 EF
路线,而 EFC
代码将看不到属性,因为它们已被重新定义。因此,它不会抱怨。 我已经有了所有的代码,它可以工作,也许我会在某个时候把它放在这里或 GitHub,但不是今天.
让我发疯的是,无论我做什么,EFC
都会设法“潜入”影子属性和类似的糟糕事情。这到了我真的想关闭 ALL EFC
约定并手动构建整个模型的地步。毕竟,我已经在这样做了,比如 90% 的模型。我宁愿 EFC
抛出(带有有意义的错误消息),也不愿默默地做任何我不希望它做的事情。
按照@IvanStoev 的建议,我目前拥有的是:
public static IModel CreateModel<TContext, TContextInfo>(Action<ModelBuilder, TContextInfo>? modifier = null)
where TContext : DbContext, ISwyfftDbContext
where TContextInfo : ContextInfo<TContext>, new()
{
var contextInfo = new TContextInfo();
var modelBuilder = new ModelBuilder();
modelBuilder
.HasKeys<TContext, TContextInfo>(contextInfo)
.HasColumnNames<TContext, TContextInfo>(contextInfo)
.ToTables<TContext, TContextInfo>(contextInfo)
.DisableCascadeDeletes()
.HasDefaultValues<TContext, TContextInfo>(contextInfo)
.HasComputedColumns<TContext, TContextInfo>(contextInfo)
.HasForeignKeys<TContext, TContextInfo>(contextInfo)
.HasDatabaseIndexes<TContext, TContextInfo>(contextInfo);
modifier?.Invoke(modelBuilder, contextInfo);
var model = modelBuilder.FinalizeRelationalModel();
return model;
}
private static IModel FinalizeRelationalModel(this ModelBuilder modelBuilder)
{
var model = modelBuilder.Model;
var conventionModel = model as IConventionModel;
var databaseModel = new RelationalModel(model);
conventionModel.SetAnnotation(RelationalAnnotationNames.RelationalModel, databaseModel);
return modelBuilder.FinalizeModel();
}
其中HasKeys
、HasColumnNames
等是我[之前]写的扩展方法,用来继续使用多列PK、Fs等,[=16不支持=] 和 conventionModel.SetAnnotation(RelationalAnnotationNames.RelationalModel, databaseModel)
是强制性的,否则不会创建模型并且代码会因 NRE 而失败。
因此,当我将 CreateModel
插入 DbContextOptions
时:
public static DbContextOptions<TContext> GetDbContextOptions(string connectionString, Func<IModel> modelCreator) =>
new DbContextOptionsBuilder<TContext>()
.UseModel(modelCreator())
.UseSqlServer(connectionString, x => x.UseNetTopologySuite())
.Options;
并通过 运行 创建迁移,例如Add-Migration Initial
然后 ModelSnapshot
最终结果是正确的,没有垃圾影子属性,也没有其他废话 EFC
按照所有约定在这里或那里插入。但是,当我尝试查询任何 table 时,代码失败并显示:
(InvalidOperationException) Sequence contains no elements;
Sequence contains no elements ( at System.Linq.ThrowHelper.ThrowNoElementsException()
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
at Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression..ctor(IEntityType entityType, ISqlExpressionFactory sqlExpressionFactory)
at Microsoft.EntityFrameworkCore.Query.SqlExpressionFactory.Select(IEntityType entityType)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.CreateShapedQueryExpression(IEntityType entityType)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitExtension(Expression extensionExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitExtension(Expression extensionExpression)
at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToQueryString(IQueryable source)
这意味着 RelationalModel
严重不完整。
任何进一步的想法将不胜感激。非常感谢!
可以通过手动构建 IModel
static IModel CreateModel(/* args */)
{
var modelBuilder = new ModelBuilder();
// Build the model using the modelBuilder
// ...
return modelBuilder.FinalizeModel();
}
这里的本质是无参数ModelBuilder()
构造函数的使用
Initializes a new instance of the ModelBuilder
class with no conventions
然后使用 UseModel
方法将其与上下文相关联,例如在目标上下文中 OnConfiguring
override
optionsBuilder.UseModel(CreateModel())
使用此方法,不使用目标上下文的 OnModelCreating
。
这应该可以达到您的要求。但要注意使用的 ModelBuilder
构造函数的警告:
Warning: conventions are typically needed to build a correct model.
因此,您必须非常小心地显式映射所有内容。另一方面,EF Core 迁移在内部使用完全相同的方法(.designer.cs
文件中生成的方法 BuildTargetModel
)在 classes 可能不存在或不存在的位置生成模型可能完全不同,所以如果使用得当,它应该是一个可行的选择。
更新: 结果是 "conventions are typically needed to build a correct model" 在警告中确实意味着约定(至少其中一些)对于构建正确的运行时模型确实强制性,因为它们用于执行一些控制运行时行为的操作。
最引人注目的是创建关系模型(表、列等)映射的 RelationalModelConvention
和创建提供者数据类型映射的 TypeMappingConvention
。因此这两个是强制性的。但谁知道呢,可能还有更多。并且允许扩展添加自己的。
因此,在进一步阅读之前,请考虑使用具有所有约定的标准方法。严重地。 Fluent 配置具有更高的优先级(约定 < 数据注释 < fluent(显式)),因此如果您显式配置所有内容,则不应出现意外的阴影、鉴别器等。属性 问题。
现在,如果您想继续这条危险的道路,您应该创建所需的最少约定,或者更好的是,删除导致您出现问题的不需要的约定。 public EF Core 5.x 修改约定的方法是注册具有单一方法
的自定义 IConventionSetPlugin
实现
public ConventionSet ModifyConventions (ConventionSet conventionSet);
它允许您修改(替换、添加新的、删除)默认约定,甚至 return 一个全新的约定集。
注册这样的插件不是那么容易,需要一堆管道(即使是样板)代码。但它是首选,因为它允许您删除特定约定(请注意,约定 class 可以实现多个约定相关的接口,因此必须从多个 ConventionSet
列表中删除),以及强制性约定classes 具有额外的依赖项并使用 DI 容器来解析它们,因此从外部创建它们并不容易(如果不是不可能的话)。
话虽如此,这里是一个示例实现,它删除了所有约定,仅保留在 ModelFinalizingConventions
和 ModelFinalizedConventions
中注册的约定,这似乎对于构建正常运行的运行时模型至关重要:
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure
{
public class CustomConventionSetPlugin : IConventionSetPlugin
{
public ConventionSet ModifyConventions(ConventionSet conventionSet)
{
conventionSet.EntityTypeAddedConventions.Clear();
conventionSet.EntityTypeAnnotationChangedConventions.Clear();
conventionSet.EntityTypeBaseTypeChangedConventions.Clear();
conventionSet.EntityTypeIgnoredConventions.Clear();
conventionSet.EntityTypeMemberIgnoredConventions.Clear();
conventionSet.EntityTypePrimaryKeyChangedConventions.Clear();
conventionSet.ForeignKeyAddedConventions.Clear();
conventionSet.ForeignKeyAnnotationChangedConventions.Clear();
conventionSet.ForeignKeyDependentRequirednessChangedConventions.Clear();
conventionSet.ForeignKeyRequirednessChangedConventions.Clear();
conventionSet.ForeignKeyUniquenessChangedConventions.Clear();
conventionSet.IndexAddedConventions.Clear();
conventionSet.IndexAnnotationChangedConventions.Clear();
conventionSet.IndexRemovedConventions.Clear();
conventionSet.IndexUniquenessChangedConventions.Clear();
conventionSet.KeyAddedConventions.Clear();
conventionSet.KeyAnnotationChangedConventions.Clear();
conventionSet.KeyRemovedConventions.Clear();
conventionSet.ModelAnnotationChangedConventions.Clear();
//conventionSet.ModelFinalizedConventions.Clear();
//conventionSet.ModelFinalizingConventions.Clear();
conventionSet.ModelInitializedConventions.Clear();
conventionSet.NavigationAddedConventions.Clear();
conventionSet.NavigationAnnotationChangedConventions.Clear();
conventionSet.NavigationRemovedConventions.Clear();
conventionSet.PropertyAddedConventions.Clear();
conventionSet.PropertyAnnotationChangedConventions.Clear();
conventionSet.PropertyFieldChangedConventions.Clear();
conventionSet.PropertyNullabilityChangedConventions.Clear();
conventionSet.PropertyRemovedConventions.Clear();
conventionSet.SkipNavigationAddedConventions.Clear();
conventionSet.SkipNavigationAnnotationChangedConventions.Clear();
conventionSet.SkipNavigationForeignKeyChangedConventions.Clear();
conventionSet.SkipNavigationInverseChangedConventions.Clear();
conventionSet.SkipNavigationRemovedConventions.Clear();
return conventionSet;
}
}
}
// Boilerplate for regigistering the plugin
namespace Microsoft.EntityFrameworkCore.Infrastructure
{
public class CustomConventionSetOptionsExtension : IDbContextOptionsExtension
{
public CustomConventionSetOptionsExtension() { }
ExtensionInfo info;
public DbContextOptionsExtensionInfo Info => info ?? (info = new ExtensionInfo(this));
public void Validate(IDbContextOptions options) { }
public void ApplyServices(IServiceCollection services)
=> services.AddSingleton<IConventionSetPlugin, CustomConventionSetPlugin>();
sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
public ExtensionInfo(CustomConventionSetOptionsExtension extension) : base(extension) { }
public override bool IsDatabaseProvider => false;
public override string LogFragment => string.Empty;
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo) { }
public override long GetServiceProviderHashCode() => 1234;
}
}
}
namespace Microsoft.EntityFrameworkCore
{
public static partial class CustomDbContextOptionsExtensions
{
public static DbContextOptionsBuilder UseCustomConventionSet(this DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.Options.FindExtension<CustomConventionSetOptionsExtension>() == null)
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(new CustomConventionSetOptionsExtension());
return optionsBuilder;
}
}
}
它提供了一个方便的 Use
扩展方法,类似于其他扩展,所以你只需要在配置期间调用它,例如在 OnConfiguring
覆盖
optionsBuilder.UseCustomConventionSet();
或者用你的例子
public static DbContextOptions<TContext> GetDbContextOptions(string connectionString, Func<IModel> modelCreator) =>
new DbContextOptionsBuilder<TContext>()
.UseSqlServer(connectionString, x => x.UseNetTopologySuite())
.UseCustomConventionSet()
.Options;
OnConfiguring
是首选,因为这与数据库提供程序无关,也不像最初的建议那样使用外部模型创建(和 UseModel
)——流畅的配置回到 OnModelCreating
覆盖。
我想关闭 ALL(或至少大部分)Entity Framework Core 中的约定(我说的是 EF Core 5 或更高版本),然后“手工”构建整个模型。
有人可能想知道为什么。
原因如下:我的任务是将几个大型遗留数据库从 Entity Framework 6 (EF
) 迁移到 Entity Framework Core 5 (EFC
)。这涉及数百个 table 和几个数据库。其中一些数据库是使用 Code First 方法创建的,而另一些只是第三方数据库,我们需要从 C# 代码查询和更新这些数据库。对于后一种数据库,我们必须完全匹配它们的架构。
由于问题的规模,代码的 EF
和 EFC
风格必须共存,比方说,几个月。这可以通过使用条件编译轻松实现(见下文)。
与 EF
相比,EFC
最有可能不支持或不方便支持的任何内容(或被“黑入”EF
模型),例如空间索引、多-column KeyAttribute
PKs,多列ForeignKeyAttribute
FKs,多次自引用tables,在同一列上定义多个索引(有些是过滤器有些只是常规索引),等等等等。
没关系。我可以很容易地处理EFC
无法通过使用条件编译“覆盖”属性来处理这个问题,例如
#if EFCORE
using Key = MyKeyAttribute;
using Column = MyColumnAttribute;
using Index = MyIndexAttribute;
using ForeignKey = MyForeignKeyAttribute;
#endif
然后为每个MyProject.csproj
创建一个MyProject_EFC.csproj
,其中定义了EFCORE
,然后使用Reflection来“收集”所有这些自定义属性,然后使用EFC
Fluent API 配置所有 EFC
做不到的事情。因此,遗留 (EF
) 代码仍然会看到原始代码,例如KeyAttribute
然后遵循 EF
路线,而 EFC
代码将看不到属性,因为它们已被重新定义。因此,它不会抱怨。 我已经有了所有的代码,它可以工作,也许我会在某个时候把它放在这里或 GitHub,但不是今天.
让我发疯的是,无论我做什么,EFC
都会设法“潜入”影子属性和类似的糟糕事情。这到了我真的想关闭 ALL EFC
约定并手动构建整个模型的地步。毕竟,我已经在这样做了,比如 90% 的模型。我宁愿 EFC
抛出(带有有意义的错误消息),也不愿默默地做任何我不希望它做的事情。
按照@IvanStoev 的建议,我目前拥有的是:
public static IModel CreateModel<TContext, TContextInfo>(Action<ModelBuilder, TContextInfo>? modifier = null)
where TContext : DbContext, ISwyfftDbContext
where TContextInfo : ContextInfo<TContext>, new()
{
var contextInfo = new TContextInfo();
var modelBuilder = new ModelBuilder();
modelBuilder
.HasKeys<TContext, TContextInfo>(contextInfo)
.HasColumnNames<TContext, TContextInfo>(contextInfo)
.ToTables<TContext, TContextInfo>(contextInfo)
.DisableCascadeDeletes()
.HasDefaultValues<TContext, TContextInfo>(contextInfo)
.HasComputedColumns<TContext, TContextInfo>(contextInfo)
.HasForeignKeys<TContext, TContextInfo>(contextInfo)
.HasDatabaseIndexes<TContext, TContextInfo>(contextInfo);
modifier?.Invoke(modelBuilder, contextInfo);
var model = modelBuilder.FinalizeRelationalModel();
return model;
}
private static IModel FinalizeRelationalModel(this ModelBuilder modelBuilder)
{
var model = modelBuilder.Model;
var conventionModel = model as IConventionModel;
var databaseModel = new RelationalModel(model);
conventionModel.SetAnnotation(RelationalAnnotationNames.RelationalModel, databaseModel);
return modelBuilder.FinalizeModel();
}
其中HasKeys
、HasColumnNames
等是我[之前]写的扩展方法,用来继续使用多列PK、Fs等,[=16不支持=] 和 conventionModel.SetAnnotation(RelationalAnnotationNames.RelationalModel, databaseModel)
是强制性的,否则不会创建模型并且代码会因 NRE 而失败。
因此,当我将 CreateModel
插入 DbContextOptions
时:
public static DbContextOptions<TContext> GetDbContextOptions(string connectionString, Func<IModel> modelCreator) =>
new DbContextOptionsBuilder<TContext>()
.UseModel(modelCreator())
.UseSqlServer(connectionString, x => x.UseNetTopologySuite())
.Options;
并通过 运行 创建迁移,例如Add-Migration Initial
然后 ModelSnapshot
最终结果是正确的,没有垃圾影子属性,也没有其他废话 EFC
按照所有约定在这里或那里插入。但是,当我尝试查询任何 table 时,代码失败并显示:
(InvalidOperationException) Sequence contains no elements;
Sequence contains no elements ( at System.Linq.ThrowHelper.ThrowNoElementsException()
at System.Linq.Enumerable.Single[TSource](IEnumerable`1 source)
at Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression..ctor(IEntityType entityType, ISqlExpressionFactory sqlExpressionFactory)
at Microsoft.EntityFrameworkCore.Query.SqlExpressionFactory.Select(IEntityType entityType)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.CreateShapedQueryExpression(IEntityType entityType)
at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitExtension(Expression extensionExpression)
at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitExtension(Expression extensionExpression)
at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor)
at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToQueryString(IQueryable source)
这意味着 RelationalModel
严重不完整。
任何进一步的想法将不胜感激。非常感谢!
可以通过手动构建 IModel
static IModel CreateModel(/* args */)
{
var modelBuilder = new ModelBuilder();
// Build the model using the modelBuilder
// ...
return modelBuilder.FinalizeModel();
}
这里的本质是无参数ModelBuilder()
构造函数的使用
Initializes a new instance of the
ModelBuilder
class with no conventions
然后使用 UseModel
方法将其与上下文相关联,例如在目标上下文中 OnConfiguring
override
optionsBuilder.UseModel(CreateModel())
使用此方法,不使用目标上下文的 OnModelCreating
。
这应该可以达到您的要求。但要注意使用的 ModelBuilder
构造函数的警告:
Warning: conventions are typically needed to build a correct model.
因此,您必须非常小心地显式映射所有内容。另一方面,EF Core 迁移在内部使用完全相同的方法(.designer.cs
文件中生成的方法 BuildTargetModel
)在 classes 可能不存在或不存在的位置生成模型可能完全不同,所以如果使用得当,它应该是一个可行的选择。
更新: 结果是 "conventions are typically needed to build a correct model" 在警告中确实意味着约定(至少其中一些)对于构建正确的运行时模型确实强制性,因为它们用于执行一些控制运行时行为的操作。
最引人注目的是创建关系模型(表、列等)映射的 RelationalModelConvention
和创建提供者数据类型映射的 TypeMappingConvention
。因此这两个是强制性的。但谁知道呢,可能还有更多。并且允许扩展添加自己的。
因此,在进一步阅读之前,请考虑使用具有所有约定的标准方法。严重地。 Fluent 配置具有更高的优先级(约定 < 数据注释 < fluent(显式)),因此如果您显式配置所有内容,则不应出现意外的阴影、鉴别器等。属性 问题。
现在,如果您想继续这条危险的道路,您应该创建所需的最少约定,或者更好的是,删除导致您出现问题的不需要的约定。 public EF Core 5.x 修改约定的方法是注册具有单一方法
的自定义IConventionSetPlugin
实现
public ConventionSet ModifyConventions (ConventionSet conventionSet);
它允许您修改(替换、添加新的、删除)默认约定,甚至 return 一个全新的约定集。
注册这样的插件不是那么容易,需要一堆管道(即使是样板)代码。但它是首选,因为它允许您删除特定约定(请注意,约定 class 可以实现多个约定相关的接口,因此必须从多个 ConventionSet
列表中删除),以及强制性约定classes 具有额外的依赖项并使用 DI 容器来解析它们,因此从外部创建它们并不容易(如果不是不可能的话)。
话虽如此,这里是一个示例实现,它删除了所有约定,仅保留在 ModelFinalizingConventions
和 ModelFinalizedConventions
中注册的约定,这似乎对于构建正常运行的运行时模型至关重要:
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure
{
public class CustomConventionSetPlugin : IConventionSetPlugin
{
public ConventionSet ModifyConventions(ConventionSet conventionSet)
{
conventionSet.EntityTypeAddedConventions.Clear();
conventionSet.EntityTypeAnnotationChangedConventions.Clear();
conventionSet.EntityTypeBaseTypeChangedConventions.Clear();
conventionSet.EntityTypeIgnoredConventions.Clear();
conventionSet.EntityTypeMemberIgnoredConventions.Clear();
conventionSet.EntityTypePrimaryKeyChangedConventions.Clear();
conventionSet.ForeignKeyAddedConventions.Clear();
conventionSet.ForeignKeyAnnotationChangedConventions.Clear();
conventionSet.ForeignKeyDependentRequirednessChangedConventions.Clear();
conventionSet.ForeignKeyRequirednessChangedConventions.Clear();
conventionSet.ForeignKeyUniquenessChangedConventions.Clear();
conventionSet.IndexAddedConventions.Clear();
conventionSet.IndexAnnotationChangedConventions.Clear();
conventionSet.IndexRemovedConventions.Clear();
conventionSet.IndexUniquenessChangedConventions.Clear();
conventionSet.KeyAddedConventions.Clear();
conventionSet.KeyAnnotationChangedConventions.Clear();
conventionSet.KeyRemovedConventions.Clear();
conventionSet.ModelAnnotationChangedConventions.Clear();
//conventionSet.ModelFinalizedConventions.Clear();
//conventionSet.ModelFinalizingConventions.Clear();
conventionSet.ModelInitializedConventions.Clear();
conventionSet.NavigationAddedConventions.Clear();
conventionSet.NavigationAnnotationChangedConventions.Clear();
conventionSet.NavigationRemovedConventions.Clear();
conventionSet.PropertyAddedConventions.Clear();
conventionSet.PropertyAnnotationChangedConventions.Clear();
conventionSet.PropertyFieldChangedConventions.Clear();
conventionSet.PropertyNullabilityChangedConventions.Clear();
conventionSet.PropertyRemovedConventions.Clear();
conventionSet.SkipNavigationAddedConventions.Clear();
conventionSet.SkipNavigationAnnotationChangedConventions.Clear();
conventionSet.SkipNavigationForeignKeyChangedConventions.Clear();
conventionSet.SkipNavigationInverseChangedConventions.Clear();
conventionSet.SkipNavigationRemovedConventions.Clear();
return conventionSet;
}
}
}
// Boilerplate for regigistering the plugin
namespace Microsoft.EntityFrameworkCore.Infrastructure
{
public class CustomConventionSetOptionsExtension : IDbContextOptionsExtension
{
public CustomConventionSetOptionsExtension() { }
ExtensionInfo info;
public DbContextOptionsExtensionInfo Info => info ?? (info = new ExtensionInfo(this));
public void Validate(IDbContextOptions options) { }
public void ApplyServices(IServiceCollection services)
=> services.AddSingleton<IConventionSetPlugin, CustomConventionSetPlugin>();
sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
public ExtensionInfo(CustomConventionSetOptionsExtension extension) : base(extension) { }
public override bool IsDatabaseProvider => false;
public override string LogFragment => string.Empty;
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo) { }
public override long GetServiceProviderHashCode() => 1234;
}
}
}
namespace Microsoft.EntityFrameworkCore
{
public static partial class CustomDbContextOptionsExtensions
{
public static DbContextOptionsBuilder UseCustomConventionSet(this DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.Options.FindExtension<CustomConventionSetOptionsExtension>() == null)
((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(new CustomConventionSetOptionsExtension());
return optionsBuilder;
}
}
}
它提供了一个方便的 Use
扩展方法,类似于其他扩展,所以你只需要在配置期间调用它,例如在 OnConfiguring
覆盖
optionsBuilder.UseCustomConventionSet();
或者用你的例子
public static DbContextOptions<TContext> GetDbContextOptions(string connectionString, Func<IModel> modelCreator) =>
new DbContextOptionsBuilder<TContext>()
.UseSqlServer(connectionString, x => x.UseNetTopologySuite())
.UseCustomConventionSet()
.Options;
OnConfiguring
是首选,因为这与数据库提供程序无关,也不像最初的建议那样使用外部模型创建(和 UseModel
)——流畅的配置回到 OnModelCreating
覆盖。