SQL Server 为什么在此代码中未关闭连接

2wnc66cl  于 2023-02-03  发布在  其他
关注(0)|答案(3)|浏览(154)

我正在维护以前的开发人员的工作。
这是数据库层类,它有.......

public class Database 
{
        private string mConnString;
        private SqlConnection mConn;
        private SqlDataAdapter mAdapter;
        private SqlCommand mCmd;
        private SqlTransaction mTransaction;
        private bool disposed = false;

        public Database() : this(Web.GetWebConfigValue("ConnectionString"))
        {
        }

        public Database(string connString)
        {
            mConnString = connString;
            mConn = new SqlConnection(mConnString);
            mConn.Open();
            mAdapter = new SqlDataAdapter();
            mCmd = new SqlCommand();
            mCmd.CommandType = CommandType.StoredProcedure;
            mCmd.Connection = mConn;
        }

        public void CloseConnection()
        {
            mConn.Close();
        }

        public void BeginTransaction()
        {
            mTransaction = mConn.BeginTransaction();
            mCmd.Transaction = mTransaction;
        }

        public void CommitTransaction()
        {
            mTransaction.Commit();
        }

        public void RollbackTransaction()
        {
            mTransaction.Rollback();
        }

        public void AddParam(string name, SqlDbType type, object value)
        {
            SqlParameter parameter = new SqlParameter('@' + name, type);
            parameter.Value = value;
            mCmd.Parameters.Add(parameter);
        }

        public void ChangeParam(string name, object value)
        {
            mCmd.Parameters['@' + name].Value = value;
        }

        public void DeleteParam(string name)
        {
            mCmd.Parameters.RemoveAt('@' + name);
        }

        public void AddReturnParam()
        {
            SqlParameter parameter = new SqlParameter();
            parameter.ParameterName = "return";
            parameter.Direction = ParameterDirection.ReturnValue;
            mCmd.Parameters.Add(parameter);
        }

        public void AddOutputParam(string name, SqlDbType type, int size)
        {
            SqlParameter parameter = new SqlParameter('@' + name, type);
            parameter.Direction = ParameterDirection.Output;
            parameter.Size = size;
            mCmd.Parameters.Add(parameter);
        }

        public int GetReturnParam()
        {
            return (int)mCmd.Parameters["return"].Value;
        }

        public object GetOutputParam(string name)
        {
            return mCmd.Parameters['@' + name].Value;
        }

        public void ClearParams()
        {
            mCmd.Parameters.Clear();
        }

        public void ExecNonQuery(string cmdText)
        {
            if(mConn.State==ConnectionState.Closed)
                mConn.Open();

            mCmd.CommandText = cmdText;
            mCmd.ExecuteNonQuery();
        }

        public DataSet GetDataSet(string cmdText)
        {
            mCmd.CommandText = cmdText;
            mAdapter.SelectCommand = mCmd;
            DataSet ds = new DataSet();
            mAdapter.Fill(ds);
            return ds;
        }

        public IDataReader GetDataReader(string cmdText)
        {
            mCmd.CommandText = cmdText;

            if(mConn.State==ConnectionState.Closed)
                mConn.Open();

            return mCmd.ExecuteReader(CommandBehavior.CloseConnection);
        }

        public DataTable GetDataTable(string cmdText)
        {
            return GetDataSet(cmdText).Tables[0];
        }

        public DataTable GetDataTable(string cmdText,string SQL)
        { 
            SqlCommand cmd = new SqlCommand();
            cmd.CommandText = cmdText;
            mAdapter.SelectCommand = cmd;
            cmd.Connection = mConn;
            DataSet ds = new DataSet();
            mAdapter.Fill(ds);
            return ds.Tables[0];
        }

        public DataRow GetDataRow(string cmdText)
        {
            DataTable dt = GetDataTable(cmdText);
            DataRow dr;

            if(dt.Rows.Count > 0)
                dr = dt.Rows[0];
            else
                dr = null;

            return dr;
        }

        public object GetScalar(string cmdText)
        {
            mCmd.CommandText = cmdText;
            return mCmd.ExecuteScalar();
        }

        public void SetCommandType(CommandType type)
        {
            mCmd.CommandType = type;
        }

        ~Database()
        {
            this.Dispose(false);
        }
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        private void Dispose(bool disposing)
        {
            if(!this.disposed)
            {
                if(disposing)
                {
                    if(mConn.State == ConnectionState.Open)
                        mConn.Close();
                    this.mCmd.Dispose();
                    this.mAdapter.Dispose();
                    this.mTransaction.Dispose();
                }
            }
            disposed = true;         
        }

    }

你能帮我找出在使用这个类的所有情况下,连接可能不会关闭的地方吗?

pxy2qtax

pxy2qtax1#

连接只在释放DataBase类的示例时关闭,然而,当这个类实现了disposable模式时,它并没有实现IDisposable接口--所以你不能在using语句中使用它。
此外,您必须依赖于使用该类的任何人来释放它。
如果不这样做,连接将不会关闭,直到终结器被调用,这完全超出了开发人员的控制。它甚至可能根本不会被调用-因为垃圾收集器可能不需要在运行时清除内存的任何应用程序正在使用这段代码。
这就是为什么处理数据库连接的正确方法是作为using语句中的局部变量。
您要做的是创建连接并尽可能晚地打开它,然后尽快释放它。
处理数据库调用的正确方法如下所示:

int ExecuteNonQuery(string sql)
{
    using(var con = new SqlConnection(connectionString))
    {
        using(var cmd = new SqlCommand(sql, con))
        {
            con.Open();
            return cmd.ExecueNonQuery();
        }
    }
}

当然,您可能希望添加参数来保存需要传递给数据库的任何参数,并添加一个参数来保存命令类型,但这应该建立在此结构的基础上。
我在GitHub上有一个名为ADONETHelper的项目(由于缺乏空闲时间,我在过去一年左右的时间里一直忽略了这个项目),它是为了减少直接使用ADO.Net时的代码重复而编写的。
我几年前写的,所以现在我当然有改进的想法,但正如我所说的,我没有多余的时间去做它-但总体思路仍然有效和有用。基本上,它有一个Execute方法,看起来像这样:

public T Execute<T>(string sql, CommandType commandType, Func<IDbCommand, T> function, params IDbDataParameter[] parameters)
{
    using (var con = new TConnection())
    {
        con.ConnectionString = _ConnectionString;
        using (var cmd = new TCommand())
        {
            cmd.CommandText = sql;
            cmd.Connection = con;
            cmd.CommandType = commandType;
            if (parameters.Length > 0)
            {
                cmd.Parameters.AddRange(parameters);
            }
            con.Open();
            return function(cmd);
        }
    }
}

然后我添加了几个使用此方法的方法:

public int ExecuteNonQuery(string sql, CommandType commandType, params IDbDataParameter[] parameters)
{
    return Execute<int>(sql, commandType, c => c.ExecuteNonQuery(), parameters);
}

public bool ExecuteReader(string sql, CommandType commandType, Func<IDataReader, bool> populate, params IDbDataParameter[] parameters)
{
    return Execute<bool>(sql, commandType, c => populate(c.ExecuteReader()), parameters);
}

等等。
请随意借用该项目的想法-甚至使用它是-我有几个应用程序使用这个,他们运行得非常好,相当一段时间了。

js81xvg6

js81xvg62#

您没有通过IDisposable接口实现可释放模式,您只有一个Dispose方法,因此您不能在using语句中调用它。

public class Database : IDisposable { ... }

这一切都有点可疑:我的意思是,如果你已经在使用它了,你就不会在using语句中使用它,也不会试图缓存连接,我会完全回避这个问题。
你也有一个析构函数,但是它的用法99%是错误的。

ercv8c1e

ercv8c1e3#

虽然持久层是完全合理的,但是你必须进行不同的设计,你要做的是将一些复杂性打包到仍然做同样事情的方法中,比如ChangeParam()GetDataReader()
通常情况下,您拥有的存储库了解底层的技术细节,并消除了这种复杂性,例如GetAllCustomers()(强调领域术语 customer)。
当你有4或5个这样的仓库时,你就可以开始重构,把复杂性抽象到一个父类中,这样做,你就把复杂性打包到GetDataReader()中,并把它从仓库中提升到一个位于它上面的抽象仓库中。
从您开始的地方开始,您将得到另一个层,它没有抽象几乎一样多,并有太多,往往是不必要的功能。
如果真有那么简单,API早就把它删除了。
你应该做的另一件事是看看这个简单但经常出现的代码片段。它提炼了一些关于IDisposableusing(){}的核心概念。你总是会在正确的代码中遇到它:

string sql = "SELECT * FROM t";

using (SqlConnection con = new SqlConnection(connectionString))
using (SqlCommand cmd = new SqlCommand(sql, con))
{
    SqlDataReader reader;

    con.Open();
    reader = cmd.ExecuteReader();

    while (reader.Read())
    {
        // TODO: consume data
    }
    reader.Close();
}

这是我期望在持久层中看到的东西,而 consume data 部分实际上是最重要的,因为它依赖于域,其余的只是没什么意思的样板代码。

相关问题