【EF Core】DbContext是如何識別出實體集合的
在開始之前說明一下,你不要指望閱讀完本文后會得到光,就算得到光你也未必能變成迪迦。本文老周僅介紹原理,可以給部分大伙伴們解惑。
咱們都知道,在派生 DbContext 類時,集體類的集合用 DbSet<TEntity> 表示,而咱們最常用的方法是在 DbContext 的派生類中公開 DbSet<TEntity> 屬性。但在實例化 DbContext 后,我們并未給這些屬性賦值,就能查詢數據了,那么,DbContext 類(包括其子類)是如何識別出這些公共屬性并填充數據的?
好,主題已經打開,接下來老周就開始表演了。有大伙伴會說了:切,這個看看源碼不就知道了。是的,但有些人天生懶啊,不想看,那老周幫你看。
首先,咱們要了解,DbContext 類是如何維護實體集合的?DbContext 類中有這么個字段聲明:
private Dictionary<(Type Type, string? Name), object>? _sets;
這行代碼老周嚴重希望你能看懂,看不懂會很麻煩的喲。這是一個字典類型,沒錯吧。然后,Key是啥類型,Value是啥類型?
Key:是一個二元元組,第一項為 Type 對象,第二項為字符串對象。type 指的是實體類的 Type,name 指的是你為這個實體集合分配的名字。有伙伴會問,我怎么給它命名,DbSet 實例又不是我創建的?不急,請看下文;
Value:猜得出來,這是與實體集合相關的實例,DbSet<>,實際類型是內部類 InternalDbSet<TEntity>。這個后面咱們再說。
咱們先不去關心 DbSet<TEntity> 實例是怎么創建的(因為這里面要繞繞彎子),至少咱們知道:在DbContext上聲明的實體集合是緩存到一個字典中的。而把集合實例添加到字典中的是一個名為 GetOrAddSet 的方法。注意該方法是顯示實現了 IDbSetCache 接口的??纯催@個接口的定義:
public interface IDbSetCache { object GetOrAddSet(IDbSetSource source, [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type); object GetOrAddSet( IDbSetSource source, string entityTypeName, [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type); IEnumerable<object> GetSets(); }
IDbSetSource 接口的實現者就是跟創建 DbSet 實例有關的,咱們先忽略它。把注意放那兩個重載方法 GetOrAddSet 上,它的功能就是獲取或者添加實體集合的引用。咱們看到,這兩個重載的區別在:1、以Type為標識添加;2、以Type + name為標識添加。而 DbContext 類是顯式實現了 IDbSetCache 接口的,即咱們上面提到過的,就是把 DbSet 實例存到那個名為 _sets 的字典中。
object IDbSetCache.GetOrAddSet( IDbSetSource source, [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type) { CheckDisposed(); _sets ??= []; if (!_sets.TryGetValue((type, null), out var set)) { set = source.Create(this, type); _sets[(type, null)] = set; _cachedResettableServices = null; } return set; } object IDbSetCache.GetOrAddSet( IDbSetSource source, string entityTypeName, [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type) { CheckDisposed(); _sets ??= []; if (!_sets.TryGetValue((type, entityTypeName), out var set)) { set = source.Create(this, entityTypeName, type); _sets[(type, entityTypeName)] = set; _cachedResettableServices = null; } return set; }
當添加的實體集合有名字時,字典的Key是由 type 和 entiyTypeName 組成;當集合不提供名字時,Key 就由 type 和 null 組成。
然后,DbContext 類公開一組重載方法,封裝了 GetOrAddSet 方法的調用。
public virtual DbSet<TEntity> Set<[DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] TEntity>() where TEntity : class => (DbSet<TEntity>)((IDbSetCache)this).GetOrAddSet(DbContextDependencies.SetSource, typeof(TEntity)); public virtual DbSet<TEntity> Set<[DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] TEntity>(string name) where TEntity : class => (DbSet<TEntity>)((IDbSetCache)this).GetOrAddSet(DbContextDependencies.SetSource, name, typeof(TEntity));
根據這個邏輯,那么,咱們在繼承 DbContext 類時,這樣寫也可以(假設實體類為 Student):
public class MyDbContext : DbContext { public DbSet<Student> Students => Set<Student>(); // 或者 public DbSet<Student> Students => Set<Student>("stu"); }
不過,咱們通常的寫法是實體集合作為公共屬性:
public class MyDbContext : DbContext { public DbSet<Student> Students { get; set; } }
那 DbContext 類是怎么識別并調用 GetOrAddSet 方法的?
這就要用到另一個輔助—— IDbSetInitializer,其實現類為 DbSetInitializer。
public class DbSetInitializer : IDbSetInitializer { private readonly IDbSetFinder _setFinder; private readonly IDbSetSource _setSource; public DbSetInitializer( IDbSetFinder setFinder, IDbSetSource setSource) { _setFinder = setFinder; _setSource = setSource; } public virtual void InitializeSets(DbContext context) { foreach (var setInfo in _setFinder.FindSets(context.GetType()).Where(p => p.Setter != null)) { setInfo.Setter!.SetClrValueUsingContainingEntity( context, ((IDbSetCache)context).GetOrAddSet(_setSource, setInfo.Type)); } } }
這個 InitializeSets 方法就是在 DbContext 類的構造函數中調用的。
public DbContext(DbContextOptions options) { …… ServiceProviderCache.Instance.GetOrAdd(options, providerRequired: false) .GetRequiredService<IDbSetInitializer>() .InitializeSets(this); EntityFrameworkMetricsData.ReportDbContextInitializing(); }
由于各種輔助類型間有依賴關系,因此,EF Core 內部其實也使用了服務容器技術來自動實例化。咱們回到上面 InitializeSets 方法的實現代碼上。從源代碼中我們看到,其實完成從 DbContext 的公共屬性識別 DbSet<> 這一功能的是名為 IDbSetFinder 的組件,它的內部實現類為 DbSetFinder。
public class DbSetFinder : IDbSetFinder { private readonly ConcurrentDictionary<Type, IReadOnlyList<DbSetProperty>> _cache = new(); public virtual IReadOnlyList<DbSetProperty> FindSets(Type contextType) => _cache.GetOrAdd(contextType, FindSetsNonCached); private static DbSetProperty[] FindSetsNonCached(Type contextType) { var factory = ClrPropertySetterFactory.Instance; return contextType.GetRuntimeProperties() .Where( p => !&& !&& p.DeclaringType != typeof&&&& p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)) .OrderBy(p => p.Name) .Select( p => new DbSetProperty( p.Name, p.PropertyType.GenericTypeArguments.Single(), p.SetMethod == null ? null : factory.Create(p))) .ToArray(); } }
總結一下,就是在 DbContext 的派生類中查找符合以下條件的屬性:
1、非靜態屬性;
2、不能是索引器;
3、屬性是 DbSet<> 類型,并且有泛型參數(即實體類型);
4、外加一條,屬性具有 set 訪問器(這個條件是在 InitializeSets 方法的代碼中,Where 方法篩選出來)。
到了這里,本文的主題就有了答案了:
DbContext 構造函數 --> IDbSetInitializer --> IDbSetFinder
還差一步,前面咱們說過,DbSet<> 實例是由 IDbSetSource 負責創建的,其內部實現類是 DbSetSource。
public class DbSetSource : IDbSetSource { private static readonly MethodInfo GenericCreateSet = typeof(DbSetSource).GetTypeInfo().GetDeclaredMethod(nameof(CreateSetFactory))!; private readonly ConcurrentDictionary<(Type Type, string? Name), Func<DbContext, string?, object>> _cache = new(); public virtual object Create(DbContext context, Type type) => CreateCore(context, type, null, GenericCreateSet); public virtual object Create(DbContext context, string name, Type type) => CreateCore(context, type, name, GenericCreateSet); private object CreateCore(DbContext context, Type type, string? name, MethodInfo createMethod) => _cache.GetOrAdd( (type, name), static (t, createMethod) => (Func<DbContext, string?, object>)createMethod .MakeGenericMethod(t.Type) .Invoke(null, null)!, createMethod)(context, name); [UsedImplicitly] private static Func<DbContext, string?, object> CreateSetFactory<TEntity>() where TEntity : class => (c, name) => new InternalDbSet<TEntity>(c, name); }
所以,默認創建的 DbSet<> 實例其實是 InternalDbSet<TEntity> 類型。
所有的組件都是通過 EntityFrameworkServicesBuilder 類的相關方法來添加到服務容器中的。
public virtual EntityFrameworkServicesBuilder TryAddCoreServices() { TryAdd<IDbSetFinder, DbSetFinder>(); TryAdd<IDbSetInitializer, DbSetInitializer>(); TryAdd<IDbSetSource, DbSetSource>(); TryAdd<IEntityFinderSource, EntityFinderSource>(); TryAdd<IEntityMaterializerSource, EntityMaterializerSource>(); TryAdd<IProviderConventionSetBuilder, ProviderConventionSetBuilder>(); TryAdd<IConventionSetBuilder, RuntimeConventionSetBuilder>(); TryAdd<IModelCustomizer, ModelCustomizer>(); TryAdd<IModelCacheKeyFactory, ModelCacheKeyFactory>(); TryAdd<ILoggerFactory>(p => ScopedLoggerFactory.Create(p, null)); TryAdd<IModelSource, ModelSource>(); TryAdd<IModelRuntimeInitializer, ModelRuntimeInitializer>(); TryAdd<IInternalEntityEntrySubscriber, InternalEntityEntrySubscriber>(); TryAdd<IEntityEntryGraphIterator, EntityEntryGraphIterator>(); TryAdd<IEntityGraphAttacher, EntityGraphAttacher>(); TryAdd<IValueGeneratorCache, ValueGeneratorCache>(); TryAdd<IKeyPropagator, KeyPropagator>(); TryAdd<INavigationFixer, NavigationFixer>(); TryAdd<ILocalViewListener, LocalViewListener>(); TryAdd<IStateManager, StateManager>(); TryAdd<IConcurrencyDetector, ConcurrencyDetector>(); TryAdd<IInternalEntityEntryNotifier, InternalEntityEntryNotifier>(); TryAdd<IValueGenerationManager, ValueGenerationManager>(); TryAdd<IChangeTrackerFactory, ChangeTrackerFactory>(); TryAdd<IChangeDetector, ChangeDetector>(); TryAdd<IDbContextServices, DbContextServices>(); TryAdd<IDbContextDependencies, DbContextDependencies>(); TryAdd<IDatabaseFacadeDependencies, DatabaseFacadeDependencies>(); TryAdd<IValueGeneratorSelector, ValueGeneratorSelector>(); TryAdd<IModelValidator, ModelValidator>(); TryAdd<IExecutionStrategyFactory, ExecutionStrategyFactory>(); TryAdd(p => p.GetRequiredService<IExecutionStrategyFactory>().Create()); TryAdd<ICompiledQueryCache, CompiledQueryCache>(); TryAdd<IAsyncQueryProvider, EntityQueryProvider>(); TryAdd<IQueryCompiler, QueryCompiler>(); TryAdd<ICompiledQueryCacheKeyGenerator, CompiledQueryCacheKeyGenerator>(); TryAdd<ISingletonOptionsInitializer, SingletonOptionsInitializer>(); TryAdd(typeof(IDiagnosticsLogger<>), typeof(DiagnosticsLogger<>)); TryAdd<IInterceptors, Interceptors>(); TryAdd<IInterceptorAggregator, SaveChangesInterceptorAggregator>(); TryAdd<IInterceptorAggregator, IdentityResolutionInterceptorAggregator>(); TryAdd<IInterceptorAggregator, QueryExpressionInterceptorAggregator>(); TryAdd<ILoggingOptions, LoggingOptions>(); TryAdd<ICoreSingletonOptions, CoreSingletonOptions>(); TryAdd<ISingletonOptions, ILoggingOptions>(p => p.GetRequiredService<ILoggingOptions>()); TryAdd<ISingletonOptions, ICoreSingletonOptions>(p => p.GetRequiredService<ICoreSingletonOptions>()); TryAdd(p => GetContextServices(p).Model); TryAdd<IDesignTimeModel>(p => new DesignTimeModel(GetContextServices(p))); TryAdd(p => GetContextServices(p).CurrentContext); TryAdd<IDbContextOptions>(p => GetContextServices(p).ContextOptions); TryAdd<IResettableService, ILazyLoaderFactory>(p => p.GetRequiredService<ILazyLoaderFactory>()); TryAdd<IResettableService, IStateManager>(p => p.GetRequiredService<IStateManager>()); TryAdd<IResettableService, IDbContextTransactionManager>(p => p.GetRequiredService<IDbContextTransactionManager>()); TryAdd<IEvaluatableExpressionFilter, EvaluatableExpressionFilter>(); TryAdd<IValueConverterSelector, ValueConverterSelector>(); TryAdd<IConstructorBindingFactory, ConstructorBindingFactory>(); TryAdd<ILazyLoaderFactory, LazyLoaderFactory>(); TryAdd<ILazyLoader>(p => p.GetRequiredService<ILazyLoaderFactory>().Create()); TryAdd<IParameterBindingFactories, ParameterBindingFactories>(); TryAdd<IMemberClassifier, MemberClassifier>(); TryAdd<IPropertyParameterBindingFactory, PropertyParameterBindingFactory>(); TryAdd<IParameterBindingFactory, LazyLoaderParameterBindingFactory>(); TryAdd<IParameterBindingFactory, ContextParameterBindingFactory>(); TryAdd<IParameterBindingFactory, EntityTypeParameterBindingFactory>(); TryAdd<IMemoryCache>(_ => new MemoryCache(new MemoryCacheOptions { SizeLimit = 10240 })); TryAdd<IUpdateAdapterFactory, UpdateAdapterFactory>(); TryAdd<IQueryCompilationContextFactory, QueryCompilationContextFactory>(); TryAdd<IQueryTranslationPreprocessorFactory, QueryTranslationPreprocessorFactory>(); TryAdd<IQueryTranslationPostprocessorFactory, QueryTranslationPostprocessorFactory>(); TryAdd<INavigationExpansionExtensibilityHelper, NavigationExpansionExtensibilityHelper>(); TryAdd<IExceptionDetector, ExceptionDetector>(); TryAdd<IAdHocMapper, AdHocMapper>(); TryAdd<IJsonValueReaderWriterSource, JsonValueReaderWriterSource>(); TryAdd<ILiftableConstantFactory, LiftableConstantFactory>(); TryAdd<ILiftableConstantProcessor, LiftableConstantProcessor>(); TryAdd( p => p.GetService<IDbContextOptions>()?.FindExtension<CoreOptionsExtension>()?.DbContextLogger ?? new NullDbContextLogger()); // This has to be lazy to avoid creating instances that are not disposed ServiceCollectionMap .TryAddSingleton<DiagnosticSource>(_ => new DiagnosticListener(DbLoggerCategory.Name)); ServiceCollectionMap.GetInfrastructure() .AddDependencySingleton<LazyLoaderParameterBindingFactoryDependencies>() .AddDependencySingleton<DatabaseProviderDependencies>() .AddDependencySingleton<ModelSourceDependencies>() .AddDependencySingleton<ValueGeneratorCacheDependencies>() .AddDependencySingleton<ModelValidatorDependencies>() .AddDependencySingleton<TypeMappingSourceDependencies>() .AddDependencySingleton<ModelCustomizerDependencies>() .AddDependencySingleton<ModelCacheKeyFactoryDependencies>() .AddDependencySingleton<ValueConverterSelectorDependencies>() .AddDependencySingleton<EntityMaterializerSourceDependencies>() .AddDependencySingleton<EvaluatableExpressionFilterDependencies>() .AddDependencySingleton<RuntimeModelDependencies>() .AddDependencySingleton<ModelRuntimeInitializerDependencies>() .AddDependencySingleton<NavigationExpansionExtensibilityHelperDependencies>() .AddDependencySingleton<JsonValueReaderWriterSourceDependencies>() .AddDependencySingleton<LiftableConstantExpressionDependencies>() .AddDependencyScoped<ProviderConventionSetBuilderDependencies>() .AddDependencyScoped<QueryCompilationContextDependencies>() .AddDependencyScoped<StateManagerDependencies>() .AddDependencyScoped<ExecutionStrategyDependencies>() .AddDependencyScoped<CompiledQueryCacheKeyGeneratorDependencies>() .AddDependencyScoped<QueryContextDependencies>() .AddDependencyScoped<QueryableMethodTranslatingExpressionVisitorDependencies>() .AddDependencyScoped<QueryTranslationPreprocessorDependencies>() .AddDependencyScoped<QueryTranslationPostprocessorDependencies>() .AddDependencyScoped<ShapedQueryCompilingExpressionVisitorDependencies>() .AddDependencyScoped<ValueGeneratorSelectorDependencies>() .AddDependencyScoped<DatabaseDependencies>() .AddDependencyScoped<ModelDependencies>() .AddDependencyScoped<ModelCreationDependencies>() .AddDependencyScoped<AdHocMapperDependencies>(); ServiceCollectionMap.TryAddSingleton<IRegisteredServices>( new RegisteredServices(ServiceCollectionMap.ServiceCollection.Select(s => s.ServiceType))); return this; }
DbContext 對象在初始化時只是查找實體集合,此時還沒有任何查詢被執行。當咱們要訪問實體數據時,DbSet<> 會把查詢任務交給 IAsyncQueryProvider 接口的實現類去處理,它的內部實現類是 EntityQueryProvider。
EntityQueryProvider 內部基于 LINQ 生成表達式樹,表達式樹傳遞給 IQueryCompiler 去編譯并運行。IQueryCompiler 接口有個內部實現類叫 QueryCompiler。
后面就一路往下傳遞到數據庫層,執行生成的SQL。當然這里頭還包含很多復雜的組件,此處咱們就不繼續挖,否則要挖到明天早上。
本文老周只講述了和 DbContext 類添加實體集合相關的組件,其他組件等后面說到相關內容再介紹。咱們總不能一口氣把整個框架都說一遍的,太復雜了。

浙公網安備 33010602011771號