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单例实例:
- ADO.NET下的所有数据库连接实例默认都不是线程安全的,
Microsoft.Data.Sqlite也不例外,单个已打开的连接实例同一时间只能处理一条命令,哪怕是纯读操作也不支持并发调用。 - 原生ADO.NET场景下没复现问题,本质是写原生实现时没有跨线程共享同一个打开的连接,而是每次操作新建连接、用完释放,没有触发资源竞争。
- 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
相关产品推荐
相关产品推荐

