You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Dapper搭配SQLite调用Query方法并发查询抛出空引用异常

测试环境
  • 框架版本:.NET 6
  • Microsoft.Data.Sqlite 版本:6.0.6
  • Dapper 版本:2.0.123
问题现象

仅执行单条查询时无异常,执行多条并发查询时触发空引用报错。

对照验证

不使用Dapper的conn.Query方法,改用原生ADO.NET编写同等逻辑代码时,不会出现该问题。

可稳定复现问题的简易示例代码如下:

using System.Data;
using Dapper;
using Microsoft.Data.Sqlite;

// SQLite数据库文件
string connStr = $"Data Source={AppDomain.CurrentDomain.BaseDirectory}test.db";

// SQLite连接(共享单例连接) 
using SqliteConnection conn = new SqliteConnection(connStr);
conn.Open();
 
bool existError = false;
while (true)
{
    if (existError == true)
    {
        break;
    }

    // 执行并发读取
    Parallel.For(0, 100, index =>
    {
        try
        {
            // 测试SQL
            string sql_test = " select * from T_Account where AccountId='ab001' ";

            // 执行数据查询(可能抛出空引用异常)
            var t = conn.Query(sql_test);
        }
        catch (Exception ex)
        {
            existError = true;

            // 测试输出:未将对象引用设置到对象的实例
            Console.WriteLine($"Read error ({index}): {ex.Message}");
        }
    });

    Console.WriteLine($"{DateTime.Now}-------- split line --------");
}
问题根因

核心问题是跨线程共享了非线程安全的SqliteConnection单例实例:

  1. ADO.NET下的所有数据库连接实例默认都不是线程安全的,Microsoft.Data.Sqlite也不例外,单个已打开的连接实例同一时间只能处理一条命令,哪怕是纯读操作也不支持并发调用。
  2. 原生ADO.NET场景下没复现问题,本质是写原生实现时没有跨线程共享同一个打开的连接,而是每次操作新建连接、用完释放,没有触发资源竞争。
  3. Dapper的Query方法内部会依次执行连接状态校验、创建DbCommand、执行数据读取、结果映射、资源释放等多步操作,多线程同时抢占同一个连接的内部资源时,会出现连接内部状态被意外修改、字段引用为空的情况,最终抛出空引用异常。
可行解决方案

不要全局持有、跨线程共享已打开的SqliteConnection实例,按以下规则调整即可:

  • 每次执行数据库操作前新建连接实例,操作完成后自动释放。Microsoft.Data.Sqlite底层默认开启连接池,新建连接的开销极低,连接池会自动管理底层连接的复用,完全不需要担心性能损耗。
  • 修正后的代码示例:
using System.Data;
using Dapper;
using Microsoft.Data.Sqlite;

// SQLite数据库文件
string connStr = $"Data Source={AppDomain.CurrentDomain.BaseDirectory}test.db";
 
bool existError = false;
while (true)
{
    if (existError == true)
    {
        break;
    }

    // 执行并发读取
    Parallel.For(0, 100, index =>
    {
        try
        {
            // 测试SQL
            string sql_test = " select * from T_Account where AccountId='ab001' ";
            // 每次操作新建连接,用完自动释放,交由连接池管理底层复用
            using var conn = new SqliteConnection(connStr);
            // 执行数据查询
            var t = conn.Query(sql_test);
        }
        catch (Exception ex)
        {
            existError = true;
            Console.WriteLine($"Read error ({index}): {ex.Message}");
        }
    });

    Console.WriteLine($"{DateTime.Now}-------- split line --------");
}
  • 特殊场景(比如使用SQLite内存库必须保持长连接)如果必须复用单连接,需要在所有连接操作外层加锁保证同一时间只有一个线程访问连接,但这种方案会让所有查询串行执行,性能远低于连接池方案,非必要不使用。

内容的提问来源于stack exchange,提问作者CHB_LT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 07:57:20