找回密码
 立即注册
首页 业界区 业界 【EF Core】DbContext是如何识别出实体集合的 ...

【EF Core】DbContext是如何识别出实体集合的

洪势 2025-6-29 20:47:35
在开始之前说明一下,你不要指望阅读完本文后会得到光,就算得到光你也未必能变成迪迦。本文老周仅介绍原理,可以给部分大伙伴们解惑。
咱们都知道,在派生 DbContext 类时,集体类的集合用 DbSet 表示,而咱们最常用的方法是在 DbContext 的派生类中公开 DbSet 属性。但在实例化 DbContext 后,我们并未给这些属性赋值,就能查询数据了,那么,DbContext 类(包括其子类)是如何识别出这些公共属性并填充数据的?
好,主题已经打开,接下来老周就开始表演了。有大伙伴会说了:切,这个看看源码不就知道了。是的,但有些人天生懒啊,不想看,那老周帮你看。
首先,咱们要了解,DbContext 类是如何维护实体集合的?DbContext 类中有这么个字段声明:
  1. private Dictionary<(Type Type, string? Name), object>? _sets;
复制代码
这行代码老周严重希望你能看懂,看不懂会很麻烦的哟。这是一个字典类型,没错吧。然后,Key是啥类型,Value是啥类型?
Key:是一个二元元组,第一项为 Type 对象,第二项为字符串对象。type 指的是实体类的 Type,name 指的是你为这个实体集合分配的名字。有伙伴会问,我怎么给它命名,DbSet 实例又不是我创建的?不急,请看下文;
Value:猜得出来,这是与实体集合相关的实例,DbSet,实际类型是内部类 InternalDbSet。这个后面咱们再说。
咱们先不去关心 DbSet 实例是怎么创建的(因为这里面要绕绕弯子),至少咱们知道:在DbContext上声明的实体集合是缓存到一个字典中的。而把集合实例添加到字典中的是一个名为 GetOrAddSet 的方法。注意该方法是显示实现了 IDbSetCache 接口的。看看这个接口的定义:
  1. public interface IDbSetCache
  2. {
  3.    
  4.     object GetOrAddSet(IDbSetSource source, [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type);
  5.    
  6.     object GetOrAddSet(
  7.         IDbSetSource source,
  8.         string entityTypeName,
  9.         [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type);
  10.    
  11.     IEnumerable<object> GetSets();
  12. }
复制代码
IDbSetSource 接口的实现者就是跟创建 DbSet 实例有关的,咱们先忽略它。把注意放那两个重载方法 GetOrAddSet 上,它的功能就是获取或者添加实体集合的引用。咱们看到,这两个重载的区别在:1、以Type为标识添加;2、以Type + name为标识添加。而 DbContext 类是显式实现了 IDbSetCache 接口的,即咱们上面提到过的,就是把 DbSet 实例存到那个名为 _sets 的字典中。
  1.     object IDbSetCache.GetOrAddSet(
  2.         IDbSetSource source,
  3.         [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type)
  4.     {
  5.         CheckDisposed();
  6.         _sets ??= [];
  7.         if (!_sets.TryGetValue((type, null), out var set))
  8.         {
  9.             set = source.Create(this, type);
  10.             <em><strong>_sets[(type, </strong></em><em><strong>null)] = set</strong></em><em><strong>;</strong></em>
  11.             _cachedResettableServices = null;
  12.         }
  13.         return set;
  14.     }
  15.     object IDbSetCache.GetOrAddSet(
  16.         IDbSetSource source,
  17.         string entityTypeName,
  18.         [DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] Type type)
  19.     {
  20.         CheckDisposed();
  21.         _sets ??= [];
  22.         if (!_sets.TryGetValue((type, entityTypeName), out var set))
  23.         {
  24.             set = source.Create(this, entityTypeName, type);
  25.             <em><strong>_sets[(type, entityTypeName)] </strong></em><em><strong>= set</strong></em><em><strong>;</strong></em>
  26.             _cachedResettableServices = null;
  27.         }
  28.         return set;
  29.     }
复制代码
当添加的实体集合有名字时,字典的Key是由 type 和 entiyTypeName 组成;当集合不提供名字时,Key 就由 type 和 null 组成。
然后,DbContext 类公开一组重载方法,封装了 GetOrAddSet 方法的调用。
  1.     public virtual DbSet<TEntity> Set<[DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] TEntity>()
  2.         where TEntity : class
  3.         => (DbSet<TEntity>)((IDbSetCache)this).<strong>GetOrAddSet(DbContextDependencies.SetSource, typeof</strong><strong>(TEntity))</strong>;
  4.     public virtual DbSet<TEntity> Set<[DynamicallyAccessedMembers(IEntityType.DynamicallyAccessedMemberTypes)] TEntity>(string name)
  5.         where TEntity : class
  6.         => (DbSet<TEntity>)((IDbSetCache)this).<strong>GetOrAddSet(DbContextDependencies.SetSource, name, typeof(TEntity))</strong>;
复制代码
 
根据这个逻辑,那么,咱们在继承 DbContext 类时,这样写也可以(假设实体类为 Student):
  1. public class MyDbContext : DbContext
  2. {
  3.       public DbSet<Student> Students => Set<Student>();
  4.       // 或者
  5.       public DbSet<Student> Students => Set<Student>("stu");
  6. }
复制代码
不过,咱们通常的写法是实体集合作为公共属性:
  1. public class MyDbContext : DbContext
  2. {
  3.       public DbSet<Student> Students { get; set; }
  4. }
复制代码
那 DbContext 类是怎么识别并调用 GetOrAddSet 方法的?
这就要用到另一个辅助—— IDbSetInitializer,其实现类为 DbSetInitializer。
  1. public class DbSetInitializer : IDbSetInitializer
  2. {
  3.     private readonly IDbSetFinder _setFinder;
  4.     private readonly IDbSetSource _setSource;
  5.    
  6.     public DbSetInitializer(
  7.         IDbSetFinder setFinder,
  8.         IDbSetSource setSource)
  9.     {
  10.         _setFinder = setFinder;
  11.         _setSource = setSource;
  12.     }
  13.    
  14.     public virtual void InitializeSets(DbContext context)
  15.     {
  16.         foreach (var <strong>setInfo in _setFinder.FindSets(context.GetType()).Where(p => p.Setter != null</strong><strong>)</strong>)
  17.         {
  18.             setInfo.Setter!.SetClrValueUsingContainingEntity(
  19.                 context,
  20.                 ((IDbSetCache)context).<strong>GetOrAddSet(_setSource, setInfo.Type))</strong>;
  21.         }
  22.     }
  23. }
复制代码
这个 InitializeSets 方法就是在 DbContext 类的构造函数中调用的。
  1.     public DbContext(DbContextOptions options)
  2.     {
  3.         ……
  4.       
  5.         ServiceProviderCache.Instance.GetOrAdd(options, providerRequired: false)
  6.             <strong>.GetRequiredService</strong><strong><IDbSetInitializer></strong><strong>()
  7.             .InitializeSets(</strong><strong>this</strong><strong>)</strong>;
  8.         EntityFrameworkMetricsData.ReportDbContextInitializing();
  9.     }
复制代码
由于各种辅助类型间有依赖关系,因此,EF Core 内部其实也使用了服务容器技术来自动实例化。咱们回到上面 InitializeSets 方法的实现代码上。从源代码中我们看到,其实完成从 DbContext 的公共属性识别 DbSet 这一功能的是名为 IDbSetFinder 的组件,它的内部实现类为 DbSetFinder。
  1. public class DbSetFinder : IDbSetFinder
  2. {
  3.     private readonly ConcurrentDictionary<Type, IReadOnlyList<DbSetProperty>> _cache = new();
  4.     public virtual IReadOnlyList<DbSetProperty> FindSets(Type contextType)
  5.         => _cache.GetOrAdd(contextType, FindSetsNonCached);
  6.     private static DbSetProperty[] FindSetsNonCached(Type contextType)
  7.     {
  8.         var factory = ClrPropertySetterFactory.Instance;
  9.         return contextType.GetRuntimeProperties()
  10.             <strong>.Where(
  11. </strong>                <strong>p </strong><strong>=> !</strong><strong>&& !</strong><strong>&& p.DeclaringType != typeof</strong><strong>&&</strong><strong>&& p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<></strong><strong>))</strong>
  12.             .OrderBy(p => p.Name)
  13.             .Select(
  14.                 p => new DbSetProperty(
  15.                     p.Name,
  16.                     p.PropertyType.GenericTypeArguments.Single(),
  17.                     p.SetMethod == null ? null : factory.Create(p)))
  18.             .ToArray();
  19.     }
  20. }
复制代码
总结一下,就是在 DbContext 的派生类中查找符合以下条件的属性:
1、非静态属性;
2、不能是索引器;
3、属性是 DbSet 类型,并且有泛型参数(即实体类型);
4、外加一条,属性具有 set 访问器(这个条件是在 InitializeSets 方法的代码中,Where 方法筛选出来)。
 
到了这里,本文的主题就有了答案了:
DbContext 构造函数 --> IDbSetInitializer --> IDbSetFinder
还差一步,前面咱们说过,DbSet 实例是由 IDbSetSource 负责创建的,其内部实现类是 DbSetSource。
  1. public class DbSetSource : IDbSetSource
  2. {
  3.     private static readonly MethodInfo GenericCreateSet
  4.         = typeof(DbSetSource).GetTypeInfo().GetDeclaredMethod(nameof(CreateSetFactory))!;
  5.     private readonly ConcurrentDictionary<(Type Type, string? Name), Func<DbContext, string?, object>> _cache = new();
  6.     public virtual object Create(DbContext context, Type type)
  7.         => CreateCore(context, type, null, GenericCreateSet);
  8.     public virtual object Create(DbContext context, string name, Type type)
  9.         => CreateCore(context, type, name, GenericCreateSet);
  10.     private object CreateCore(DbContext context, Type type, string? name, MethodInfo createMethod)
  11.         => _cache.GetOrAdd(
  12.             (type, name),
  13.             static (t, createMethod) => (Func<DbContext, string?, object>)createMethod
  14.                 .MakeGenericMethod(t.Type)
  15.                 .Invoke(null, null)!,
  16.             createMethod)(context, name);
  17.     [UsedImplicitly]
  18.     private static Func<DbContext, string?, object> CreateSetFactory<TEntity>()
  19.         where TEntity : class
  20.         => <strong><em>(c, name) => new InternalDbSet<TEntity></em></strong><strong><em>(c, name)</em></strong>;
  21. }
复制代码
所以,默认创建的 DbSet 实例其实是 InternalDbSet 类型。
所有的组件都是通过 EntityFrameworkServicesBuilder 类的相关方法来添加到服务容器中的。
  1.     public virtual EntityFrameworkServicesBuilder TryAddCoreServices()
  2.     {
  3.         TryAdd<<strong>IDbSetFinder, DbSetFinder</strong>>();
  4.         TryAdd<<strong>IDbSetInitializer, DbSetInitializer</strong>>();
  5.         TryAdd<<strong>IDbSetSource, DbSetSource</strong>>();
  6.         TryAdd<IEntityFinderSource, EntityFinderSource>();
  7.         TryAdd<IEntityMaterializerSource, EntityMaterializerSource>();
  8.         TryAdd<IProviderConventionSetBuilder, ProviderConventionSetBuilder>();
  9.         TryAdd<IConventionSetBuilder, RuntimeConventionSetBuilder>();
  10.         TryAdd<IModelCustomizer, ModelCustomizer>();
  11.         TryAdd<IModelCacheKeyFactory, ModelCacheKeyFactory>();
  12.         TryAdd<ILoggerFactory>(p => ScopedLoggerFactory.Create(p, null));
  13.         TryAdd<IModelSource, ModelSource>();
  14.         TryAdd<IModelRuntimeInitializer, ModelRuntimeInitializer>();
  15.         TryAdd<IInternalEntityEntrySubscriber, InternalEntityEntrySubscriber>();
  16.         TryAdd<IEntityEntryGraphIterator, EntityEntryGraphIterator>();
  17.         TryAdd<IEntityGraphAttacher, EntityGraphAttacher>();
  18.         TryAdd<IValueGeneratorCache, ValueGeneratorCache>();
  19.         TryAdd<IKeyPropagator, KeyPropagator>();
  20.         TryAdd<INavigationFixer, NavigationFixer>();
  21.         TryAdd<ILocalViewListener, LocalViewListener>();
  22.         TryAdd<IStateManager, StateManager>();
  23.         TryAdd<IConcurrencyDetector, ConcurrencyDetector>();
  24.         TryAdd<IInternalEntityEntryNotifier, InternalEntityEntryNotifier>();
  25.         TryAdd<IValueGenerationManager, ValueGenerationManager>();
  26.         TryAdd<IChangeTrackerFactory, ChangeTrackerFactory>();
  27.         TryAdd<IChangeDetector, ChangeDetector>();
  28.         TryAdd<IDbContextServices, DbContextServices>();
  29.         TryAdd<IDbContextDependencies, DbContextDependencies>();
  30.         TryAdd<IDatabaseFacadeDependencies, DatabaseFacadeDependencies>();
  31.         TryAdd<IValueGeneratorSelector, ValueGeneratorSelector>();
  32.         TryAdd<IModelValidator, ModelValidator>();
  33.         TryAdd<IExecutionStrategyFactory, ExecutionStrategyFactory>();
  34.         TryAdd(p => p.GetRequiredService<IExecutionStrategyFactory>().Create());
  35.         TryAdd<ICompiledQueryCache, CompiledQueryCache>();
  36.         TryAdd<IAsyncQueryProvider, EntityQueryProvider>();
  37.         TryAdd<IQueryCompiler, QueryCompiler>();
  38.         TryAdd<ICompiledQueryCacheKeyGenerator, CompiledQueryCacheKeyGenerator>();
  39.         TryAdd<ISingletonOptionsInitializer, SingletonOptionsInitializer>();
  40.         TryAdd(typeof(IDiagnosticsLogger<>), typeof(DiagnosticsLogger<>));
  41.         TryAdd<IInterceptors, Interceptors>();
  42.         TryAdd<IInterceptorAggregator, SaveChangesInterceptorAggregator>();
  43.         TryAdd<IInterceptorAggregator, IdentityResolutionInterceptorAggregator>();
  44.         TryAdd<IInterceptorAggregator, QueryExpressionInterceptorAggregator>();
  45.         TryAdd<ILoggingOptions, LoggingOptions>();
  46.         TryAdd<ICoreSingletonOptions, CoreSingletonOptions>();
  47.         TryAdd<ISingletonOptions, ILoggingOptions>(p => p.GetRequiredService<ILoggingOptions>());
  48.         TryAdd<ISingletonOptions, ICoreSingletonOptions>(p => p.GetRequiredService<ICoreSingletonOptions>());
  49.         TryAdd(p => GetContextServices(p).Model);
  50.         TryAdd<IDesignTimeModel>(p => new DesignTimeModel(GetContextServices(p)));
  51.         TryAdd(p => GetContextServices(p).CurrentContext);
  52.         TryAdd<IDbContextOptions>(p => GetContextServices(p).ContextOptions);
  53.         TryAdd<IResettableService, ILazyLoaderFactory>(p => p.GetRequiredService<ILazyLoaderFactory>());
  54.         TryAdd<IResettableService, IStateManager>(p => p.GetRequiredService<IStateManager>());
  55.         TryAdd<IResettableService, IDbContextTransactionManager>(p => p.GetRequiredService<IDbContextTransactionManager>());
  56.         TryAdd<IEvaluatableExpressionFilter, EvaluatableExpressionFilter>();
  57.         TryAdd<IValueConverterSelector, ValueConverterSelector>();
  58.         TryAdd<IConstructorBindingFactory, ConstructorBindingFactory>();
  59.         TryAdd<ILazyLoaderFactory, LazyLoaderFactory>();
  60.         TryAdd<ILazyLoader>(p => p.GetRequiredService<ILazyLoaderFactory>().Create());
  61.         TryAdd<IParameterBindingFactories, ParameterBindingFactories>();
  62.         TryAdd<IMemberClassifier, MemberClassifier>();
  63.         TryAdd<IPropertyParameterBindingFactory, PropertyParameterBindingFactory>();
  64.         TryAdd<IParameterBindingFactory, LazyLoaderParameterBindingFactory>();
  65.         TryAdd<IParameterBindingFactory, ContextParameterBindingFactory>();
  66.         TryAdd<IParameterBindingFactory, EntityTypeParameterBindingFactory>();
  67.         TryAdd<IMemoryCache>(_ => new MemoryCache(new MemoryCacheOptions { SizeLimit = 10240 }));
  68.         TryAdd<IUpdateAdapterFactory, UpdateAdapterFactory>();
  69.         TryAdd<IQueryCompilationContextFactory, QueryCompilationContextFactory>();
  70.         TryAdd<IQueryTranslationPreprocessorFactory, QueryTranslationPreprocessorFactory>();
  71.         TryAdd<IQueryTranslationPostprocessorFactory, QueryTranslationPostprocessorFactory>();
  72.         TryAdd<INavigationExpansionExtensibilityHelper, NavigationExpansionExtensibilityHelper>();
  73.         TryAdd<IExceptionDetector, ExceptionDetector>();
  74.         TryAdd<IAdHocMapper, AdHocMapper>();
  75.         TryAdd<IJsonValueReaderWriterSource, JsonValueReaderWriterSource>();
  76.         TryAdd<ILiftableConstantFactory, LiftableConstantFactory>();
  77.         TryAdd<ILiftableConstantProcessor, LiftableConstantProcessor>();
  78.         TryAdd(
  79.             p => p.GetService<IDbContextOptions>()?.FindExtension<CoreOptionsExtension>()?.DbContextLogger
  80.                 ?? new NullDbContextLogger());
  81.         // This has to be lazy to avoid creating instances that are not disposed
  82.         ServiceCollectionMap
  83.             .TryAddSingleton<DiagnosticSource>(_ => new DiagnosticListener(DbLoggerCategory.Name));
  84.         ServiceCollectionMap.GetInfrastructure()
  85.             .AddDependencySingleton<LazyLoaderParameterBindingFactoryDependencies>()
  86.             .AddDependencySingleton<DatabaseProviderDependencies>()
  87.             .AddDependencySingleton<ModelSourceDependencies>()
  88.             .AddDependencySingleton<ValueGeneratorCacheDependencies>()
  89.             .AddDependencySingleton<ModelValidatorDependencies>()
  90.             .AddDependencySingleton<TypeMappingSourceDependencies>()
  91.             .AddDependencySingleton<ModelCustomizerDependencies>()
  92.             .AddDependencySingleton<ModelCacheKeyFactoryDependencies>()
  93.             .AddDependencySingleton<ValueConverterSelectorDependencies>()
  94.             .AddDependencySingleton<EntityMaterializerSourceDependencies>()
  95.             .AddDependencySingleton<EvaluatableExpressionFilterDependencies>()
  96.             .AddDependencySingleton<RuntimeModelDependencies>()
  97.             .AddDependencySingleton<ModelRuntimeInitializerDependencies>()
  98.             .AddDependencySingleton<NavigationExpansionExtensibilityHelperDependencies>()
  99.             .AddDependencySingleton<JsonValueReaderWriterSourceDependencies>()
  100.             .AddDependencySingleton<LiftableConstantExpressionDependencies>()
  101.             .AddDependencyScoped<ProviderConventionSetBuilderDependencies>()
  102.             .AddDependencyScoped<QueryCompilationContextDependencies>()
  103.             .AddDependencyScoped<StateManagerDependencies>()
  104.             .AddDependencyScoped<ExecutionStrategyDependencies>()
  105.             .AddDependencyScoped<CompiledQueryCacheKeyGeneratorDependencies>()
  106.             .AddDependencyScoped<QueryContextDependencies>()
  107.             .AddDependencyScoped<QueryableMethodTranslatingExpressionVisitorDependencies>()
  108.             .AddDependencyScoped<QueryTranslationPreprocessorDependencies>()
  109.             .AddDependencyScoped<QueryTranslationPostprocessorDependencies>()
  110.             .AddDependencyScoped<ShapedQueryCompilingExpressionVisitorDependencies>()
  111.             .AddDependencyScoped<ValueGeneratorSelectorDependencies>()
  112.             .AddDependencyScoped<DatabaseDependencies>()
  113.             .AddDependencyScoped<ModelDependencies>()
  114.             .AddDependencyScoped<ModelCreationDependencies>()
  115.             .AddDependencyScoped();
  116.         ServiceCollectionMap.TryAddSingleton<IRegisteredServices>(
  117.             new RegisteredServices(ServiceCollectionMap.ServiceCollection.Select(s => s.ServiceType)));
  118.         return this;
  119.     }
复制代码
 
DbContext 对象在初始化时只是查找实体集合,此时还没有任何查询被执行。当咱们要访问实体数据时,DbSet 会把查询任务交给 IAsyncQueryProvider 接口的实现类去处理,它的内部实现类是 EntityQueryProvider。
EntityQueryProvider 内部基于 LINQ 生成表达式树,表达式树传递给 IQueryCompiler 去编译并运行。IQueryCompiler 接口有个内部实现类叫 QueryCompiler。
后面就一路往下传递到数据库层,执行生成的SQL。当然这里头还包含很多复杂的组件,此处咱们就不继续挖,否则要挖到明天早上。
 
本文老周只讲述了和 DbContext 类添加实体集合相关的组件,其他组件等后面说到相关内容再介绍。咱们总不能一口气把整个框架都说一遍的,太复杂了。
 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册