惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
F
Fortinet All Blogs
Engineering at Meta
Engineering at Meta
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
The Exploit Database - CXSecurity.com
H
Hackread – Cybersecurity News, Data Breaches, AI and More
I
Intezer
P
Privacy & Cybersecurity Law Blog
M
MIT News - Artificial intelligence
MyScale Blog
MyScale Blog
P
Privacy International News Feed
MongoDB | Blog
MongoDB | Blog
Project Zero
Project Zero
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Tenable Blog
Security Latest
Security Latest
Stack Overflow Blog
Stack Overflow Blog
L
Lohrmann on Cybersecurity
V
Vulnerabilities – Threatpost
Microsoft Azure Blog
Microsoft Azure Blog
NISL@THU
NISL@THU
T
Threat Research - Cisco Blogs
L
LangChain Blog
Simon Willison's Weblog
Simon Willison's Weblog
WordPress大学
WordPress大学
SecWiki News
SecWiki News
博客园 - 三生石上(FineUI控件)
Forbes - Security
Forbes - Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
G
GRAHAM CLULEY
K
Kaspersky official blog
W
WeLiveSecurity
A
Arctic Wolf
TaoSecurity Blog
TaoSecurity Blog
Recorded Future
Recorded Future
AI
AI
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Last Week in AI
Last Week in AI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
雷峰网
雷峰网
GbyAI
GbyAI
S
SegmentFault 最新的问题
N
News and Events Feed by Topic
C
CXSECURITY Database RSS Feed - CXSecurity.com
Google Online Security Blog
Google Online Security Blog
博客园 - Franky
罗磊的独立博客

博客园 - 东邪独孤

【ASP.NET Core】在 Windows Forms 中运行 Web 服务器 【EF Core】级联删除行为 【EF Core】继承策略——TPC 【EF Core】继承策略——TPT 【EF Core】继承策略——TPH 【EF Core】使用自定义的值比较器 【EF Core】值转换器 【EF Core】直接更新数据 【EF Core】实体追踪——Entry中记录的数据 【EF Core】实体状态与变更追踪 【EF Core】将一个实体映射到多个表的正确方法 【EF Core】“Code First”方案下以编程方式生成迁移 【EF Core】“DB First”方案下用编程方式生成数据库模型代码 【EF Core】三种方法记录生成的 SQL 语句 【EF Core】未定义实体类的数据库模型 【EF Core】“多对多”关系与跳跃导航 【EF Core】FromExpression 方法有什么用? 【EF Core】通过 DbContext 选项扩展框架 【EF Core】再谈普通实体关系与 Owned 关系的区别 【EF Core】实体类的依赖注入 【EF Core】优化后的模型 【EF Core】使用外部 Model 【EF Core】聊聊“复合”属性 【EF Core】为 DatabaseFacade 扩展“创建”与“删除”数据表功能 【EF Core】带主键实体与无主键实体 【EF Core】框架是如何识别实体类的属性和主键的
【EF Core】框架底层的数据库连接管理
东邪独孤 · 2025-09-24 · via 博客园 - 东邪独孤

在开始水文章前,老周补充说明一下,在前一篇水文中,老周扯了跟 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 obsolete
            conn.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 实例使用。

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