【EF Core】框架底层的数据库连接管理

news/2025/9/24 18:08:42/文章来源:https://www.cnblogs.com/tcjiaan/p/19108838

在开始水文章前,老周补充说明一下,在前一篇水文中,老周扯了跟 Owned 关系相关的一些话题,这里补充一句:Owned 关系是不存在“多对多”的,如果 A 实体 Own B 实体,那么,A 对 B 是完全占有,B只属于A,数据不共享,这样就不存在“多”的一端;但A可以同时占用B、C实体,所以一占多是存在的。说得简单一点,就是 B 都被 A 独占了,其他人还来凑啥热闹?

好了,正片开始!外面台风呼啸,雨声沥沥,很适合探讨一些轻松的话题。于是,老周就说一下 EF Core 底层对数据库连接的管控吧。其实,各位如果项目中常用 EF Core,就会发现,大多数时候我们根本不用去考虑连接数据库的事,引用数据库对应的包,添加 Provider(指 UseSql、UseSqlite 等方法的调用),传递一个连接字符串就完事了。

这是因为 EF Core 已经把数据库连接相关操作封装了。实际上它的底层还是会用到 ADO.NET 相关的 API。嗯,就是大伙都很熟悉的三件套:DbConnection、DbCommand、DbDataReader。当然,这里列出的是通过的类,还有 DbBatch、DbDataAdapter 等类,只是不完全通用,有些数据库是不提供的。这些以 Db 开头的类均位于 System.Data.Common 空间,属于公共基类,不同的数据库必须提供自身的实现类。如:

1、SQL Server:SqlConnection、SqlCommand、SqlDataReader;

2、SQLite:SqliteConnection、SqliteCommand、SqliteDataReader;

3、PostgreSQL:NpgsqlConnection、NpgsqlCommand、NpgsqlDataReader;

……

其中,SQL Server 和 PostgreSQL 是有 DataAdapter 的,分别是 SqlDataAdapter 和 NpgsqlDataAdapter。SQLite 没有提供。

这样就保证了尽管面向不同的数据库,但 API 的调用过程差不多:

A、实例化 XXXConnection;

B、创建 XXXCommand 实例,设置 SQL 语句;

C、XXXCommand 实例调用 ExecuteXXX 执行 SQL,可能不需要返回结果,也可能需要 XXXDataReader 来读取结果;

D、关闭 XXXConnection 对象。

由于各种数据库相关的连接对象都是 DbConnection 的子类,于是,在连接管理上,只要统使用 DbConnection 类型就能把连接管理抽象出来,统一描述。为了实现这个“宏大目标”,EF Core 在面向关系数据库专用包(xxxx.Relational.dll)中提供了 IRelationalConnection 接口。这个接口完成了以下规范:

1、ConnectionString 属性:通过它,你可以设置 / 获取连接字符串;

2、DbConnection 属性:这个很重要,有了此属性,就可以设置 / 获取连接对象了,定义的类型正是公共基类 DbConnection;

3、Open / OpenAsync 方法:打开连接;

4、Close / CloseAsync 方法:关闭连接;

5、RentCommand 方法:调用它,它会自动帮你创建/重用一个命令实例,用 IRelationalCommand 接口封装了。这个接口稍后再介绍;

6、ReturnCommand 方法:命令对象使用完后,可以调用这个方法,把实例仍回去,以便将来可以重复/或全新使用。框架帮你管理其内存,不用你操心。

有小伙伴会疑惑:咦,我在 EFCore 源代码中搜索 SqlCommand、SqliteCommand 等关键字,居然找不到它在哪里使用命令。你不要忘了,DbConnection 类有个叫 CreateCommand 的方法,所有派生类都实现这个方法。SqlConnection 类会让它返回 SqlCommand 实例,SqliteConnection 类会让它返回 SqliteCommand 实例。但由于 CreateCommand 方法定义的返回类型是 DbCommand 类,因此它有通用性。EF Core 中就是调用了 CreateCommand 方法来获得命令实例的。这就不得不提前文中出现的一个接口了—— IRelationalCommand。它统一了一些方法:

1、ExecuteNonQuery / ExecuteNonQueryAsync 方法:执行命令,通常不返回查询结果,如 INSERT、DELETE 等;

2、ExecuteReader / ExecuteReaderAsync 方法:执行后会有查询结果,但 XXXDataReader 类被 RelationalDataReader 类封装了;

3、ExecuteScalar / ExecuteScalarAsync 方法:返回单个值。

IRelationalCommand 接口的实现类是 RelationalCommand。它有个 CreateDbCommand 公共方法。

    public virtual DbCommand CreateDbCommand(RelationalCommandParameterObject parameterObject,Guid commandId,DbCommandMethod commandMethod){var (connection, context, logger) = (parameterObject.Connection, parameterObject.Context, parameterObject.Logger);var connectionId = connection.ConnectionId;var startTime = DateTimeOffset.UtcNow;DbCommand command;var stopwatch = SharedStopwatch.StartNew();var logCommandCreate = logger?.ShouldLogCommandCreate(startTime) == true;if (logCommandCreate){var interceptionResult = logger!.CommandCreating(connection, commandMethod, context, commandId, connectionId, startTime,parameterObject.CommandSource);command = interceptionResult.HasResult? interceptionResult.Result: connection.DbConnection.CreateCommand();command = logger.CommandCreated(connection,command,commandMethod,context,commandId,connectionId,startTime,stopwatch.Elapsed,parameterObject.CommandSource);}else{command = connection.DbConnection.CreateCommand();}command.CommandText = CommandText;        if (connection.CurrentTransaction != null){command.Transaction = connection.CurrentTransaction.GetDbTransaction();}if (connection.CommandTimeout != null){command.CommandTimeout = (int)connection.CommandTimeout;}for (var i = 0; i < Parameters.Count; i++){Parameters[i].AddDbParameter(command, parameterObject.ParameterValues);}if (logCommandCreate){command = logger!.CommandInitialized(connection,command,commandMethod,context,commandId,connectionId,startTime,stopwatch.Elapsed,parameterObject.CommandSource);}return command;}

 

好了,现在基本的原理通了,咱们回到 IRelationalConnection 接口,它有一个抽象类实现:RelationalConnection。这个类中定义了一个抽象方法叫 CreateDbConnection,各种数据库在匹配 API 时会重写此方法。比如:

A、SQLite 数据库提供者,从 RelationalConnection 派生出 SqliteRelationalConnection 类,重写 CreateDbConnection 方法。

protected override DbConnection CreateDbConnection()
{var connection = new SqliteConnection(GetValidatedConnectionString());InitializeDbConnection(connection);return connection;
}

B、SQL Server 数据提供者:从 RelationalConnection 派生出 SqlServerConnection 类,重写 CreateDbConnection 方法。

protected override DbConnection CreateDbConnection()=> new SqlConnection(GetValidatedConnectionString());

C、PostgreSQL 数据库提供者:从 RelationalConnection 派生出 NpgsqlRelationalConnection 类,重写 CreateDbConnection 方法。

    protected override DbConnection CreateDbConnection(){if (DataSource is not null){return DataSource.CreateConnection();}var conn = new NpgsqlConnection(ConnectionString);        if (_provideClientCertificatesCallback is not null || _remoteCertificateValidationCallback is not null){conn.SslClientAuthenticationOptionsCallback = o =>{if (_provideClientCertificatesCallback is not null){o.ClientCertificates ??= new();_provideClientCertificatesCallback(o.ClientCertificates);}o.RemoteCertificateValidationCallback = _remoteCertificateValidationCallback;};}if (_providePasswordCallback is not null){
#pragma warning disable 618 // ProvidePasswordCallback is obsoleteconn.ProvidePasswordCallback = _providePasswordCallback;
#pragma warning restore 618}return conn;}

 

每个数据库提供者都会把实现 IRelationalConnection 接口的类注册到服务容器中,也就是说,咱们在应用代码中是可以访问此接口的功能的。

// 构建连接字符串
SqliteConnectionStringBuilder csbuilder = new();
csbuilder.DataSource = "test.db";
csbuilder.Password = "huhuhahe";
// 构建选项
DbContextOptions<DbContext> options = new DbContextOptionsBuilder<DbContext>().UseSqlite(csbuilder.ConnectionString).Options;// 此处只用来测试 IRelationalConnection 服务的访问、
// 所以无实体类无 DbContext 的派生类
using DbContext context = new(options);// 获取服务
IRelationalConnection conn = context.GetService<IRelationalConnection>();
// 打印连接字符串
Console.WriteLine($"连接字符串:{conn.ConnectionString}");

代码运行后,输出结果如下:

连接字符串:Data Source=test.db;Password=huhuhahe

 

------------------------------------------------------------------------------------------------------------------------------------------------------------

上面的示例其实没啥鸟用,接下来老周讲个比较有实用性的。下面咱们看看共享 DbConnection。

实体和 Context 如下:

public class Dog
{public Guid DogId { get; set; }public string Name { get; set; } = "Who?";public int Age { get; set; }public string? Category { get; set; }
}public class MyDbContext : DbContext
{protected override void OnModelCreating(ModelBuilder modelBuilder){EntityTypeBuilder<Dog> tb = modelBuilder.Entity<Dog>();tb.HasKey(d => d.DogId).HasName("PK_Dog");tb.ToTable("tb_dogs");tb.Property(x => x.Name).HasMaxLength(20).IsRequired();tb.Property(a => a.Category).HasDefaultValue("未确认");}protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){optionsBuilder.UseSqlite(_connection);}DbConnection _connection;public MyDbContext(DbConnection c){_connection = c;}public DbSet<Dog> DogSet { get; set; }
}

注意这个 MyDbContext 类,它的构造函数可以传递 DbConnection 对象,然后在重写的 OnConfiguring 方法中调用 UseSqlite 扩展方法直接引用外部的 DbConnection 对象。这样就实现了连接对象的共享。接着看代码:

 static void Main(string[] args){SqliteConnection myconn = new("data source=mme.db");// 初始化数据库using (MyDbContext ctx = new(myconn)){ctx.Database.EnsureDeleted();ctx.Database.EnsureCreated();}// 插入数据using (var ctx = new MyDbContext(myconn)){ctx.DogSet.Add(new(){Name = "小菜",Age = 2,Category = "吉娃娃"});ctx.DogSet.Add(new(){Name = "Jimy",Age = 3,Category = "贵宾犬"});ctx.SaveChanges();}// 查询数据using (MyDbContext c = new MyDbContext(myconn)){foreach (Dog d in c.DogSet){Console.WriteLine($"{d.Name} - {d.Category}, {d.Age}岁
");
         }}}

先实例化连接对象,然后依次传递三个 MyDbContext 实例使用。

好了,今天咱们就水到这里吧。

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

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

相关文章

iNeuOS工业互联网操作系统,更新计量数据处理方式和在线报表,实现能源管理基础功能

本次iNeuOS工业互联网操作系统升级内容主要包括:对计量数据点的处理方式和在线报表增加时间格式化配置。主要应用场景针对能源管理的在线监测、数据处理和在线(日、月、年)报表的整体应用,完成能源管理系统的基础应…

React 展示Markdown内容 - 教程

React 展示Markdown内容 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco&quo…

江门网站推广哪家好长沙建网

使用Benchmark.NET对C# 代码进行基准测试的简介在我以前的文章中[10]&#xff0c;我介绍了该系列文章[11]&#xff0c;在其中我将分享我的经验&#xff0c;同时了解C&#xff03;和.NET Core&#xff08;corefx&#xff09;框架的新性能。在本文中&#xff0c;我想着重于对现有…

西电PCB设计指南第4章学习笔记

西电PCB设计指南第4章学习笔记 四、电源路径分析电源路径的基本原理回路路径与感抗 感抗与路径有关,信号线看似很短很直,但是引入了较大的环路感抗。多路信号线的环路之间的互感,造成相互干扰 所以,我们不能但按照…

图像处理去除噪点验证码的识别逻辑实践

验证码设计中常见的防护手段之一就是加入随机噪点,让字符边缘不清晰,从而干扰自动识别。本文介绍一种基于图像滤波与形态学操作的处理流程,帮助提取出清晰的字符区域。 一、问题分析 噪点验证码的典型特征是: 图像…

读取zip包中的文件

private List<String> readFile(String zipFilePath,String fileName){File file = new File(zipFilePath);List<String> list;System.out.println(file.getName());//20250709-5427327003468403533.ziptr…

网站开发合同知识产权手机在线制作图片加字

来源&#xff1a;图灵人工智能作者&#xff1a;王健宗等https://wwwihcm/people/euu-lin-jun-7原文链接&#xff1a;https://kns.cnki.net/kcms/detail/31.1289.tp.20201123.1641.002.html摘要&#xff1a;随着计算机行业和互联网时代的不断发展与进步&#xff0c;图神经网络已…

网站营销的优缺点网站视觉

2010/2/8号 星期一 1.决定记录下每天学到的东西和感悟 2.看老赵的博客&#xff0c;学到一句话&#xff1a;Apple告诉我们的铁律是&#xff1a;表面功夫一定要做足。 3.看到一个笑话&#xff1a;你属什么&#xff1f;我属 于你。 2010/2/9号 星期二 1.减少页面中独立的请求数&…

轻松建站网站建设图片如何加载

本文将综合运用 C++11 中的新的基础设施(主要是多线程、锁、条件变量)来阐述一个经典问题——生产者消费者模型,并给出完整的解决方案。 生产者消费者问题是多线程并发中一个非常经典的问题,相信学过操作系统课程的同学都清楚这个问题的根源。本文将就四种情况分析并介绍生产…

Java文件上传和其他参数一起提交的案例

Java文件上传和其他参数一起提交的案例package cn.daenx.demo.controller;import cn.daenx.demo.Aoidj; import cn.hutool.core.util.ObjectUtil; import org.springframework.web.bind.annotation.*; import org.spri…

Attention复杂度解析与改进方向

Attention复杂度解析与改进方向Attention复杂度解析与改进方向 摘要/引言 在大规模语言模型(LLM)浪潮中,扩展模型上下文窗口长度被认为是提升模型能力和应用范围的关键方向。然而,现代Transformer结构中的自注意力…

实用指南:Qt的数据库模块介绍,Qt访问SQLite详细示例

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

网站后台程序设计常用语言 技术的分析比较注册小规模公司流程及费用

1.AKConv原理介绍 地址:2311.11587 (arxiv.org) 摘要:基于卷积运算的神经网络在深度学习领域取得了令人瞩目的成果,但标准卷积运算存在两个固有的缺陷。一方面,卷积运算仅限于局部窗口,无法捕获其他位置的信息, 并且它的采样形状是固定的。 另一方面,卷积核的大小固定为…

泉州快速建站模板免费推广网站

作者 | 阚俊宝 阿里巴巴技术专家参与文末留言互动&#xff0c;即有机会获得赠书福利&#xff01;导读&#xff1a;云原生存储详解系列文章将从云原生存储服务的概念、特点、需求、原理、使用及案例等方面&#xff0c;和大家一起探讨云原生存储技术新的机遇与挑战。本文为该系列…

三维模型非结构化网格生成

三维非结构化网格生成方案,包含多种算法和MATLAB实现。 1. 基础类和数据结构 classdef Mesh3D < handleproperties% 网格基本数据nodes % 节点坐标 (N3)elements % 单元连接关系 (M4 四面体 或 M8 …

Windows 环境变量配置

在实际项目开发中,往往需要配置各种各样的环境变量。在使用部分windows电脑时,无法获取Admin权限,使用页面配置环境变量会受到限制。这时我们可以使用CMD命令行进行环境变量配置,具体配置步骤如下: 1.打开CMD窗口…

React自定义同步状态Hook - 详解

React自定义同步状态Hook - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco&qu…

Playwright MCP浏览器自动化指南 - 详解

Playwright MCP浏览器自动化指南 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Mo…

完整教程:【C++】STL简介+编码表+string引入

完整教程:【C++】STL简介+编码表+string引入pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &qu…

付费的网站推广该怎么做合肥网络公司 网站建设

摘要 spark的调度一直是我想搞清楚的东西&#xff0c;以及有向无环图的生成过程、task的调度、rdd的延迟执行是怎么发生的和如何完成的&#xff0c;还要就是RDD的compute都是在executor的哪个阶段调用和执行我们定义的函数的。这些都非常的基础和困难。花一段时间终于弄白了其中…