在开始之前说明一下,你不要指望阅读完本文后会得到光,就算得到光你也未必能变成迪迦。本文老周仅介绍原理,可以给部分大伙伴们解惑。

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

【EF Core】DbContext是如何识别出实体集合的的更多相关文章

  1. EF Core中Key属性相同的实体只能被跟踪(track)一次

    在EF Core的DbContext中,我们可以通过DbContext或DbSet的Attach方法,来让DbContext上下文来跟踪(track)一个实体对象,假设现在我们有User实体对象,其U ...

  2. EF Core中怎么实现自动更新实体的属性值到数据库

    我们在开发系统的时候,经常会遇到这种需求数据库表中的行被更新时需要自动更新某些列. 数据库 比如下面的Person表有一列UpdateTime,这列数据要求在行被更新后自动更新为系统的当前时间. Pe ...

  3. 记一次EF Core DBContext在Action委托中GC异常的问题.

    今天在开发过程中发现.在SaveChanges的时候偶尔会抛出异常:Cannot access a disposed object. A common cause of this error is d ...

  4. EF Core 中多次从数据库查询实体数据,DbContext跟踪实体的情况

    使用EF Core时,如果多次从数据库中查询一个表的同一行数据,DbContext中跟踪(track)的实体到底有几个呢?我们下面就分情况讨论下. 数据库 首先我们的数据库中有一个Person表,其建 ...

  5. EF Core 中DbContext不会跟踪聚合方法和Join方法返回的结果,及FromSql方法使用讲解

    EF Core中: 如果调用Queryable.Count等聚合方法,不会导致DbContext跟踪(track)任何实体. 此外调用Queryable.Join方法返回的匿名类型也不会被DbCont ...

  6. EF Core 2.0 已经支持自动生成父子关系表的实体

    现在我们在SQL Server数据库中有Person表如下: CREATE TABLE [dbo].[Person]( ,) NOT NULL, ) NULL, ) NULL, ) NULL, [Cr ...

  7. asp.net core系列 28 EF模型配置(字段,构造函数,拥有实体类型)

    一. 支持字段 EF允许读取或写入字段而不是一个属性.在使用实体类时,用面向对象的封装来限制或增强应用程序代码对数据访问的语义时,这可能很有用.无法使用数据注释配置.除了约定,还可以使用Fluent ...

  8. EF Core 快速上手——EF Core的三种主要关系类型

    系列文章 EF Core 快速上手--EF Core 入门 本节导航 三种数据库关系类型建模 Migration方式创建和习修改数据库 定义和创建应用DbContext 将复杂查询拆分为子查询   本 ...

  9. 项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获

    项目开发中的一些注意事项以及技巧总结   1.jquery采用ajax向后端请求时,MVC框架并不能返回View的数据,也就是一般我们使用View().PartialView()等,只能返回json以 ...

  10. 深入理解 EF Core:EF Core 写入数据时发生了什么?

    阅读本文大概需要 14 分钟. 原文:https://bit.ly/2C67m1C 作者:Jon P Smith 翻译:王亮 声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的.其中可能 ...

随机推荐

  1. 【JVM之内存与垃圾回收篇】本地方法接口

    本地方法接口 什么是本地方法 简单地讲,一个 Native Method 是一个 Java 调用非 Java 代码的接囗.一个 Native Method 是这样一个 Java 方法:该方法的实现由非 ...

  2. dxSpreadSheet的报表demo-关于设计报表模板的Datagroup问题

    看随机的报表DEMO,主从表也好,数据分组也好.呈现的非常到位. 问题:可是自己在实现数据分组时,一旦设定分组字段就出现了混乱的数据记录. 问题的原因: 看一下一个报表页面设计时需要理清的概念. 页头 ...

  3. Armbian 安装与更换为国内软件源

    Armbian 是为 ARM 架构的单板计算机(如树莓派.NanoPi.Orange Pi 等)提供的开源镜像系统,它基于 Debian 或 Ubuntu 系统.在使用 Armbian 进行开发.调试 ...

  4. 【Linux】编译用于exynos4412(ARM)的Linux-3.14内核

    [Linux]编译用于exynos4412(ARM)的Linux-3.14内核 零.准备 1.下载 Linux-3.14内核源代码 下载页面:https://www.kernel.org/pub/li ...

  5. 一款让 Everything 更加如虎添翼的 .NET 开源辅助工具!

    前言 相信很多同学都应该用过 Everything 这个实用的 Windows 文件搜索神器吧,今天大姚给大家分享一款让 Everything 更加如虎添翼的 .NET 开源辅助工具:Everythi ...

  6. 🎀avif转png在线工具推荐

    简介 本文为avif格式图片转png图片在线工具推荐 工具 https://convertio.co/zh/avif-png/ 使用 上传avif图片 选择转换的格式 点击转换 下载 结束

  7. jmeter返回值作为参数传给后面的步骤使用的方法

    如,系统返回data 通过正则获取data后的数据,且名称定义为id 然后通过${id}的方式传参给需要使用的地方

  8. SpringMVC的执行过程

    环境准备 package org.example.springmvclearn; public record Greeting(long id, String content) { } package ...

  9. Python3循环结构(一)for循环

    Python3循环结构 在Python中主要有两种类型的循环结构:for循环和while循环.for循环一般用于有明显边界范围的情况,例如,计算1+2+3+4+5+-+100等于几的问题,就可以用fo ...

  10. redis 配置redis.config

    目录 配置日志位置 配置日志位置 编辑redis.config文件 默认logfile的值为"",修改为指定位置后重启服务. logfile "/usr/local/lo ...