EntityFramework Core表名原理解析,让我来,揭开你神秘的面纱

上一节我们针对最开始抛出的异常只是进行了浅尝辄止的解析,是不是有点意犹未尽的感觉,是的,我也有这种感觉,看到这里相信您和我会有一些疑惑,要是我们接下来通过注解、Fluent APi、DbSet分别对表名进行如下设置,那么其优先级到底是怎样的呢?内置具体是如何实现的呢?让我们从头开始揭开其神秘的面纱。

既然涉及到表名解析优先级,那么接下来我们进行如下配置,模型请参考上一节内容

在还未进入原理解析之前,让我们大胆猜测通过如上配置后优先级将是怎样的呢?是Fluent Api > 注解 > DbSet > 约定吗?假设是这样的话,EntityFramework Core内置是怎样实现的呢?是采用覆盖的机制吗?

一堆疑问浮现在我们眼前,来,让我们进入探究枯燥源码的世界,为您一一解惑。首先我们需要明确的是,在我们实例化上下文进行操作之前,EntityFramework Core具体做了些什么?故事就要从我们派生自DbContext上下文说起,如下:

在EntityFramework Core中我们利用上下文进行操作之前就是按照上述代码由上至下整体上做了如下三步准备工作:

【1】实例化上下文时,查找DbSet属性并缓存到内存中。

【2】以上下文作为缓存的键,获取缓存在内存中的模型数据。

【3】若未缓存,则创建上下文中所有模型有关数据。

查找DbSet属性并缓存

接下来我们步步分析,步步逼近以上三步操作实现,无论是主动实例化还是在Web中添加上下文中间件时,都必须经过将我们需要用到所有接口进行依赖注入

当然EntityFramework Core引用的是.NET Core中依赖注入库,至于注册了哪些,这些细节我们并不关心,我们只关注所需要用到的且会一一说明,获取接口IDbSetInitializer的具体实现DbSetInitializer,调用该类中的如下方法:

接下来获取接口IDbSetFinder的具体实现DbSetFinder去过滤查找存在Setter属性的DbSet(这点就不用我解释),查找细节我们不关心,每个DbSet都有其DbSetProperty属性,所以查找到后添加到该属性并缓存到IDbSetCache中,到此对于DbSet的查找和缓存就已完事,接下来去创建上下文中的所有模型数据。

创建上下文模型

首先是去获取上下文中所有模型数据,以上下文为键去查找缓存的模型数据,若没有则创建,否则创建缓存,如下:

接下来到了缓存不存在创建模型的环节,创建模型主要做了以下三件事。

当实例化ModelBuilder通过约定分发机制处理各个约定,具体做了哪些操作呢?主要做了以下三件事

【1】各个约定进行初始化做一些准备工作,并将其添加到对应约定集合中去。

【2】遍历自定义约定插件集合,修改对应默认约定并返回最新约定集合。

【3】通过约定分发机制,处理获取得到的最新约定集合。 

上述第【1】和【2】步通过如下代码实现:

EntityFramework Core内置提供了三个创建默认约定集合提供者接口IProviderConventionSetBuilder的具体实现,三者属于继承关系。

【1】ProviderConventionSetBuilder:构建针对数据库使用的默认约定集合的提供者

【2】RelationalConventionSetBuilder:构建模型与数据库映射的默认约定集合的提供者

【3】SqlServerConventionSetBuilder:针对SQL Server数据库构建默认约定集合的提供者

我们用不到的约定已经剔除,上述向实体类型添加约定集合中先后添加了RelationalTableAttributeConvention和TableNameFromDbSetConvention对于表名的约定,对于TableNameFromDbSetConvention约定在构造实例化时做了如下操作:

我们继续看上述通过上下文是如何获取对应模型的DbSet属性的呢?

因为在初始化上下文时我们就已经对上下文中的所有DbSet属性进行了缓存,所以通过如上方法就是获取模型与对应上下文缓存的DbSet属性的映射,还是很好理解,如下也给出调试源码时所显示Blog对应的DbSet属性信息。

现在我们已经获取到了所有默认约定集合,接下来实例化ModelBuilder,将默认约定集合作为参数传进去,如下:

接下来继续实例化Model,传入默认约定集合,开始实例化约定分配类并通过约定分发机制对模型进行处理,如下:

上述ConventionDispatcher类就是对模型的各个阶段进行分发处理,关于分发处理机制后续再单独通过一篇文章来详细分析,因为上述我们将表名的两个约定放在EntityTypeAddedConventions集合中,接下来我们来到约定分发机制对该约定集合中12个默认约定遍历处理,如下:

因为首先添加的RelationalTableAttributeConvention约定,所以当遍历到RelationalTableAttributeConvention约定时,就去到处理该约定的具体实现,说白了该约定就是获取表名的注解即遍历特性,如下:

方法ProcessEntityTypeAdded的最终具体实现就是设置对应具体模型的表名,如下:

有童鞋就问了,我们在表特性上只定义架构名称,那么上述不就产生bug了吗,用过注解的都知道既然在表特性上提供了架构名称,那么表名必须提供,但是表名提供,架构名称可不提供,所以上述处理逻辑并没任何毛病。    

我们继续看上述在RelationalEntityTypeBuilderExtensions类中对于ToTable方法的实现,如下:

我们看到该方法主要目的是判断该表名是否可设置,若不可设置则返回空,否则将设置该注解的名称作为模型的表名,我们看看上述CanSetTable又是如何判断是否可设置呢?

注:RelationalAnnotationNames.TableName是专为通过注解获取表名而定义的常量其值为Relational:TableName

真是一层套一层,此时在注解字典中不存在该键,最终当然也就将模型的表特性名称作为模型的表名,如下:

上述就是ToTable方法中调用第一个方法CanSetTable是否可设置表名的过程,主要就是在注解字典中查找注解名称为Relational:TableName是否已存在的过程,我们可以看到注解字典中不存在表名的注解名称,接下来调用第二个方法SetTableName方法去设置表名

接下来将是向注解字典中添加名为Relational:TableName,值为Blog2的注解,通过如下图监视可以清楚看到:

到目前为止,对于模型Blog已经通过注解即表特性设置了表名,接下来处理约定TableNameFromDbSetConvention,到底是覆盖还是跳过呢?我们还是一探其实现,如下:

首先获取模型Blog的元数据,接下来判断其基类是否为空,该类型的原始类型不能为空,同时在其暴露的DbSet属性中包含该类型,很显然都满足条件,最后将我们上述对模型和DbSet属性进行了映射,所以设置其表名为Blog1,如下:

如上只是满足了条件进行设置,我们还要看看方法ToTable的具体实现才能最终下结论,此时依然会和注解判断逻辑一样,但是此时在注解字典中已存在键Relational:TableName,所以将跳过,如下:

好了,到此为止针对注解和DbSet对表名的设置已经讨论完毕,接下来我们进行到执行OnModelCreating方法即我们自定义的设置即如下代码:

此时将执行到我们对Blog自定义设置的表名Blog3,我们看看最终其ToTable方法直接跳过了CanSetTable方法,直接将参数名称赋值作为模型表名。

到此为止对模型的初始化准备工作已经完成,接下来开始利用上下文进行操作,此时我们回到上一节利用上下文获取表名的方法,如下:

通过分析可知,无论是根据DbSet配置表名还是通过注解配置表名又或者是通过在OnModelCreating方法中自定义配置表名,最终在落地设置时,都统一以Relational:TableName为键设置表名值,所以上述若基类不存在就获取该表名常量的值,否则都未配置表名的话,才去以模型名称作为表名。

通过此篇和上一篇我们才算对EntityFramework Core中表名的详细解析才算明朗,我们下一个结论:EntityFramework Core对于表名的配置优先级是自定义(OnModelCreating方法)> 注解(表特性)> DbSet属性名称 > 模型名称,可能我们会想何不先注册DbSet约定,然后再注册表特性约定,采取覆盖的机制呢?但是事实并非如此,这里我们仅仅只是研究源码的冰山一角或许是为了考虑其他吧。若暴露DbSet属性,根据注册的默认约定表名为DbSet属性名称,否则表名为模型名称,若通过注解设置表名,此时上下文中暴露的DbSet属性将会被忽略,若通过OnModelCreating方法自定义配置表名,则最终以其自定义表名为准。那么问题随之又来了,对于属性是否可依此而类推呢?这个问题,只能您亲自去看源码一探究竟了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/312271.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++vector容器-容量和大小

vector容量和大小 功能描述&#xff1a; 对vector容器的容量和大小操作 函数原型&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <vector> //vector容器的容量和大小操作void printVector(vector<int > &v) {…

openresty+mysql+乱码_openresty记录响应body乱码问题

问题背景最近新上了一个功能&#xff0c;openresty通过syslog记录请求日志&#xff0c;然后由logstash推送至ES。测试上线时未发现这个问题&#xff0c;在日常查看日志的过程中&#xff0c;发现logstash推送有错误日志&#xff0c;错误内容为&#xff1a;Error parsing json&am…

【Azure学习.01】先从账号注册开始

本文部分内容配套视频&#xff1a;https://www.bilibili.com/video/av82898957马上要放假了&#xff0c;决定在家里简单了解一下Azure云服务&#xff0c;虽然公司其他部分用到了这个Azure&#xff0c;但是我还是没有接触到&#xff0c;只是听说很贵&#xff0c;好几千每天&…

C++vector容器-插入和删除

vector插入和删除 功能描述&#xff1a; 对vector容器进行插入&#xff0c;删除操作 函数原型&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <vector> //vector插入和删除void printVector(vector<int > &v…

C++vector容器-数据存取

vector数据存取 功能描述&#xff1a; 对vector中的数据的存取操作 函数原型&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <vector>//vector容器 数据存取 void test01() {vector<int >v1;for (int i 0; i &l…

如何快速融入团队(四)

作者&#xff1a;邹溪源&#xff0c;长沙资深互联网从业者&#xff0c;架构师社区特邀嘉宾&#xff01;01不知不觉这个系列已经开始第四篇的&#xff0c;其实我的原始意图只是思考一下如果有幸加入一个新团队&#xff0c;我们在思想和行动上该做哪些准备呢。不过随着内容的逐渐…

C++vector容器-互换容器

vector容器互换 功能描述&#xff1a; 实现两个容器内元素进行互换 函数原型&#xff1a; swap(vec);//将vec与本身的元素互换 1.基本使用 代码如下: #include <iostream> using namespace std; //vector容器互换 #include <vector> //1.基本使用void printVect…

.NET 状态机Automatonymous快速入门

介绍 Automatonymous是.NET开发人员的状态机库。它提供了一种流畅的语法来声明状态机&#xff0c;包括状态&#xff0c;事件&#xff08;支持触发器和数据事件&#xff09;以及状态/事件活动。尽管Automatonymous在简单的状态机上非常容易使用&#xff0c;但它具有许多高级功能…

BeetleX实现HTTP协议详解

在传统网络服务中扩展中需要处理Bytes来进行协议的读写&#xff0c;这种原始的处理方式让工作变得相当繁琐复杂&#xff0c;出错和调试的工作量都非常大&#xff1b;组件为了解决这一问题引用Stream读写方式&#xff0c;这种方式可以极大的简化网络协议读写的工作量&#xff0c…

euclidea4攻略_Euclidea几何构建11.4通关攻略

Euclidea几何构建10.2通关攻略Euclidea游戏10.2怎么过&#xff1f;下面小编为大家带来Euclidea几何构建10.2通关攻略&#xff1a;更多攻略不断更新中——Euclidea游戏全关卡通关攻略大全分两次做图第一次&#xff0c;画圆就成了&#xff0c;具体看图应该能懂。第二次&#xff0…

C++set容器-构造和赋值

set基本概念 简介&#xff1a; 所有元素都会自动在插入时自动被排序&#xff0c;set容器也叫集合容器 本质&#xff1a; set/multiset属于关联式容器&#xff0c;底层结构是用二叉树排序 set和multiset区别&#xff1a; 1.set不允许容器中有重复的元素 2.multiset允许容器中有…

(1)解锁MongoDB replica set核心姿势

本文倒腾目前大热的MongoDB Replica Set集群&#xff0c;在倒腾的同时串讲一些 MongoDB特性。 副本集Replica Set是一个术语&#xff0c;定义具有多节点的数据库集群&#xff0c;这些节点具有主从复制(master-slave replication) 且节点之间实现了自动故障转移。 这样的结构通常…

java写dnf外掛_dnf卡盟_Java的泛型详解(一)

Java实现DDD中UnitOfWorkdnf卡盟Java的泛型详解泛型的利益编写的代码可以被差别类型的工具所重用。由于上面的一个优点&#xff0c;泛型也可以削减代码的编写。泛型的使用简朴泛型类public class Pair {private T first;private T second;public Pair() {first null;second n…

C++set容器-大小和交换

set大小和交换 功能描述&#xff1a; 统计set容器大小以及交换set容器 函数原型&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <set> //set容器 大小和容器void printSet(set<int > &s) {for (set<int …

Asp.Net Core下的开源任务调度平台ScheduleMaster—快速上手

概述ScheduleMaster是一个开源的分布式任务调度系统&#xff0c;它基于Asp.Net Core平台构建&#xff0c;支持跨平台多节点部署运行。它的项目主页在这里&#xff1a;https://github.com/hey-hoho/ScheduleMasterCore关于它的简单介绍可以看这里&#xff1a;https://www.cnblog…

C++set容器-插入和删除

set插入和删除 功能描述&#xff1a; set容器进行插入数据和删除数据 函数原型&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <set>void printSet(set<int > &s) {for (set<int >::iterator it s.be…

基于Jenkins的持续交付全流程设计与实践

1 从理论开始什么是DevOps?近年来&#xff0c;随着DevOps理念的逐渐深入人心&#xff0c;企业逐渐意识到从看似重复的手工劳动中实现自动化流程处理&#xff0c;对于提高企业劳动生产力已经非常重要&#xff0c;尤其是面向互联网的开发者&#xff0c;往往每次上线时&#xff0…

C++set容器-查找和统计

set查找和统计 功能描述&#xff1a; 对set容器进行查找数据以及统计数据 函数原型&#xff1a; 代码如下: #include <iostream> using namespace std; #include <set>//set查找和统计void test01() {//查找set<int >s1;//插入数据s1.insert(10);s1.inse…

C++set和multiset区别

区别&#xff1a; 代码如下&#xff1a; #include <iostream> using namespace std; #include <set>//set容器 和multiset容器的区别 void test01() {set<int >s;pair<set<int >::iterator, bool> ret s.insert(10);if (ret.second) {cout &…

.NET CORE(C#) WPF简单菜单MVVM绑定

阅读导航 本文背景 代码实现 本文参考 源码 1. 本文背景 WPF中垂直导航菜单大家应该都常用&#xff0c;本文介绍使用MVVM的方式怎么绑定菜单&#xff0c;真的很简单。 2. 代码实现 使用 .Net Core 3.1 创建名为 “MenuMVVM” 的WPF模板项目&#xff0c;添加两个Nuget库&…