【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 类添加实体集合相关的组件,其他组件等后面说到相关内容再介绍。咱们总不能一口气把整个框架都说一遍的,太复杂了。