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

并发AJAX调用时SqlConnection.Open()触发空引用异常求助

问题分析与解决方案

问题根源

你遇到的Object reference not set to an instance of an object异常,核心原因是DbHelper类中使用了静态的_conn字段——在15个并发AJAX调用的场景下,多个请求会共享这个全局连接对象,导致连接状态混乱,甚至出现_conn被重置为null的情况,最终在调用Open()时抛出空引用异常。

具体问题点拆解

  1. 静态连接的线程不安全:DbHelper中的protected static SqlConnection _conn = null;是静态成员,所有并发请求都会共用同一个连接实例。当多个请求同时调用Connect()方法时,会互相覆盖_conn的值,导致某个请求拿到的连接可能被其他请求释放或重置。
  2. 连接管理逻辑错误:你的业务函数里用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:41:52