簡體   English   中英

如何關閉 Entity Framework Core 5 中的所有約定

[英]How to turn off ALL conventions in Entity Framework Core 5

我想關閉 Entity Framework Core 中的所有(或至少大部分)約定(我說的是 EF Core 5 或更高版本),然后“手動”構建整個 model。

有人可能想知道為什么

原因如下:我的任務是將幾個大型遺留數據庫從 Entity Framework 6 ( EF ) 遷移到 Entity Framework Core 5 ( EFC )。 這涉及數百個表和幾個數據庫。 其中一些數據庫是使用 Code First 方法創建的,而一些只是第三方數據庫,我們需要從 C# 代碼中查詢和更新這些數據庫。 對於后面的數據庫,我們必須完全匹配它們的模式。

由於問題的規模, EFEFC風格的代碼必須共存,比如說幾個月。 這可以通過使用條件編譯輕松實現(見下文)。

EF相比,很可能在EFC中不支持或不方便支持的任何內容(或被“入侵”到EF模型中),例如空間索引、多列KeyAttribute PK、多列ForeignKeyAttribute FK、自引用多個時間表,在同一列上定義的多個索引(有些是過濾器,有些只是常規索引),依此類推。

沒關系。 我可以通過使用條件編譯“覆蓋”屬性來輕松處理EFC無法處理的問題,例如

#if EFCORE
using Key = MyKeyAttribute;
using Column = MyColumnAttribute;
using Index = MyIndexAttribute;
using ForeignKey = MyForeignKeyAttribute;
#endif

然后為每個MyProject.csproj創建一個定義EFCOREMyProject_EFC.csproj ,然后使用反射“收集”所有這些自定義屬性,然后使用EFC Fluent API 配置所有EFC不能做的東西。 因此,遺留 ( EF ) 代碼仍將看到原始代碼,例如KeyAttribute ,然后遵循EF路線,而EFC代碼將看不到屬性,因為它們被重新定義。 所以,它不會抱怨。 我已經有了所有的代碼,它可以工作,也許我會把它放在這里或在某個時候放在 GitHub 中,但不是今天

讓我發瘋的是,無論我做什么, EFC都會設法“潛入”陰影屬性和類似的蹩腳的東西。 這到了我真的想關閉所有EFC約定並手動構建整個 model 的地步。 畢竟,我已經在這樣做了,就像 90% 的 model 一樣。 我寧願希望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();
}

其中HasKeysHasColumnNames等是我 [earlier] 為了繼續使用EFC不支持的多列 PK、Fs 等而編寫的擴展方法, conventionModel.SetAnnotation(RelationalAnnotationNames.RelationalModel, databaseModel)是強制性的,否則model 未創建,代碼因 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在這里或那里插入的廢話。 但是,當我嘗試查詢任何表時,代碼失敗並顯示:

(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()構造函數,它

初始化沒有約定的ModelBuilder器 class 的新實例

然后使用UseModel方法將它與上下文相關聯,例如在目標上下文OnConfiguring覆蓋中

optionsBuilder.UseModel(CreateModel())

使用這種方法,不使用目標上下文的OnModelCreating

這應該可以達到您的要求。 但請注意使用的ModelBuilder構造函數的警告:

警告:通常需要約定來構建正確的 model。

所以你必須非常小心地明確 map 一切。 另一方面,EF Core 遷移在內部使用完全相同的方法(在.designer.cs文件中生成的方法BuildTargetModel )在類可能不存在或可能完全不同的位置生成 model,因此它應該是正確使用時可行的選擇。


更新:事實證明,警告中的“通常需要約定來構建正確的模型”實際上意味着約定(至少其中一些)對於構建正確的運行時 model 確實是強制性的,因為它們用於執行一些控制操作運行時行為。

最引人注目的是創建RelationalModelConvention model(表、列等)映射的 RelationalModelConvention 和創建提供程序數據類型映射的TypeMappingConvention 因此,這兩個是強制性的。 但誰知道呢,可能還有更多。 並且允許擴展添加自己的。

因此,在進一步閱讀之前,請考慮使用所有約定的標准方法。 嚴重地。 流暢的配置具有更高的優先級(約定 < 數據注釋 < 流暢(顯式)),因此如果您顯式配置所有內容,您不應該遇到意外的影子、鑒別器等屬性問題。

現在,如果您想繼續危險的道路,您應該創建所需的最小約定,或者更好的是,刪除導致您出現問題的不需要的約定。 修改約定的公共 EF Core 5.x 方法是注冊具有單一方法的自定義IConventionSetPlugin實現

public ConventionSet ModifyConventions (ConventionSet conventionSet);

它允許您修改(替換、添加、刪除)默認約定,甚至返回一個全新的約定集。

注冊這樣的插件並不容易,需要一堆管道(即使是樣板)代碼。 但它是首選,因為它允許您刪除特定的約定(只需注意約定 class 可以實現多個約定相關的接口,因此必須從多個ConventionSet列表中刪除),並且強制約定類具有額外的依賴關系並使用 DI 容器為了解決它們,所以從外部創建它們並不容易(如果不是不可能的話)。

話雖如此,這里是一個示例實現,它刪除了所有約定,只保留在ModelFinalizingConventionsModelFinalizedConventions中注冊的那些,這似乎對於構建正常運行的運行時 model 至關重要:

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 override

optionsBuilder.UseCustomConventionSet();

或以您的示例

public static DbContextOptions<TContext> GetDbContextOptions(string connectionString, Func<IModel> modelCreator) =>
    new DbContextOptionsBuilder<TContext>()
        .UseSqlServer(connectionString, x => x.UseNetTopologySuite())
        .UseCustomConventionSet()
        .Options;

不過,首選OnConfiguring ,因為這與數據庫提供程序無關,並且也不像最初的建議那樣使用外部 model 創建(和UseModel )——流暢的配置回到OnModelCreating覆蓋。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM