并发AJAX调用时SqlConnection.Open()触发空引用异常求助
问题分析与解决方案
问题根源
你遇到的Object reference not set to an instance of an object异常,核心原因是DbHelper类中使用了静态的_conn字段——在15个并发AJAX调用的场景下,多个请求会共享这个全局连接对象,导致连接状态混乱,甚至出现_conn被重置为null的情况,最终在调用Open()时抛出空引用异常。
具体问题点拆解
- 静态连接的线程不安全:
DbHelper中的protected static SqlConnection _conn = null;是静态成员,所有并发请求都会共用同一个连接实例。当多个请求同时调用Connect()方法时,会互相覆盖_conn的值,导致某个请求拿到的连接可能被其他请求释放或重置。 - 连接管理逻辑错误:你的业务函数里用
using块包裹连接,但DbHelper.Connect()返回的是全局静态连接,这会导致using块释放的是所有请求共享的连接,进一步加剧并发冲突。
修复方案
我们需要重构DbHelper,移除静态连接字段,让每个请求拥有独立的数据库连接,同时确保连接被正确管理和释放。
重构后的DbHelper类
using System; using System.Data; using System.Data.SqlClient; using System.Configuration; public class DbHelper : IDisposable { private readonly string _connString; private SqlConnection _conn = null; private SqlTransaction _trans = null; private bool _disposed = false; /// <summary> /// 默认连接字符串,供无自定义连接的实例使用 /// </summary> public static string DefaultConnectionString { get; set; } = ConfigurationManager.AppSettings["ConfigurationString"]; /// <summary> /// 当前事务对象(无事务时为null) /// </summary> public SqlTransaction Transaction => _trans; /// <summary> /// 使用默认连接字符串的构造函数 /// </summary> public DbHelper() { _connString = DefaultConnectionString; _conn = new SqlConnection(_connString); } /// <summary> /// 使用自定义连接字符串的构造函数 /// </summary> /// <param name="connString">当前实例的连接字符串</param> public DbHelper(string connString) { _connString = connString ?? throw new ArgumentNullException(nameof(connString)); _conn = new SqlConnection(_connString); } /// <summary> /// 打开数据库连接(仅当连接未打开时执行) /// </summary> public void OpenConnection() { if (_conn != null && _conn.State != ConnectionState.Open) { _conn.Open(); } } /// <summary> /// 创建配置好的SqlCommand对象 /// </summary> /// <param name="qry">SQL语句或存储过程名</param> /// <param name="type">命令类型</param> /// <param name="args">参数(参数名/值对 或 SqlParameter对象)</param> /// <returns>配置完成的SqlCommand</returns> public SqlCommand CreateCommand(string qry, CommandType type, params object[] args) { if (_conn == null) { throw new InvalidOperationException("数据库连接未初始化"); } SqlCommand cmd = new SqlCommand(qry, _conn); // 关联当前事务(如果存在) if (_trans != null) { cmd.Transaction = _trans; } cmd.CommandType = type; // 构造SQL参数 for (int i = 0; i < args.Length; i++) { if (args[i] is string && i < args.Length - 1) { SqlParameter parm = new SqlParameter { ParameterName = (string)args[i], Value = args[++i] ?? DBNull.Value // 正确处理null值 }; cmd.Parameters.Add(parm); } else if (args[i] is SqlParameter) { cmd.Parameters.Add((SqlParameter)args[i]); } else { throw new ArgumentException("参数数量或类型无效"); } } return cmd; } /// <summary> /// 开始事务 /// </summary> public void BeginTransaction() { OpenConnection(); _trans = _conn.BeginTransaction(); } /// <summary> /// 提交事务 /// </summary> public void CommitTransaction() { _trans?.Commit(); _trans = null; } /// <summary> /// 回滚事务 /// </summary> public void RollbackTransaction() { _trans?.Rollback(); _trans = null; } /// <summary> /// 释放资源 /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { // 释放托管资源 _trans?.Dispose(); if (_conn != null) { if (_conn.State == ConnectionState.Open) { _conn.Close(); } _conn.Dispose(); } } _disposed = true; } } ~DbHelper() { Dispose(false); } }
重构后的业务函数示例
public static DataSet Function1(int arg, string arg2, string arg3, string arg4, int arg5, int arg6) { DataSet ds = new DataSet(); try { // 每个请求创建独立的DbHelper实例,确保连接隔离 using (DbHelper dbHelper = new DbHelper()) { dbHelper.OpenConnection(); using (SqlCommand cmd = dbHelper.CreateCommand("Procedure_Name", CommandType.StoredProcedure, "@arg", arg, "@arg1", arg2, "@arg2", arg3, "@arg3", arg4, "@arg4", arg5)) { cmd.CommandTimeout = 0; using (SqlDataAdapter dataAdaptor = new SqlDataAdapter(cmd)) { dataAdaptor.Fill(ds); } } } ds.Tables[0].TableName = "Test"; } catch (Exception ex) { // 建议添加日志记录,便于排查问题 throw; } return ds; }
额外注意事项
- 连接池复用:.NET SqlClient默认启用连接池,每个请求创建新连接不会导致性能问题,连接池会自动管理连接的复用与回收。
- 参数名匹配:原代码中存在参数名与变量名不匹配的问题(比如
@arg1对应不存在的arg1变量),重构时已修正,需确保参数名与存储过程定义一致。 - 空值处理:创建参数时用
DBNull.Value替代null,避免数据库参数空值错误。
内容的提问来源于stack exchange,提问作者user6885473
相关产品推荐
相关产品推荐

