为什么在此代码中未关闭Connection

时间:2019-01-16 06:00:20

标签: c# sql-server

我正在维护以前的开发人员工作。

这是db层类,它具有.......

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;         
        }


    }

您能帮我找出在使用此类的所有情况下可能未关闭连接的地方吗?

3 个答案:

答案 0 :(得分:3)

只有在处置DataBase类的实例时,连接才会关闭。 但是,尽管此类正在实现一次性模式,但并未实现IDisposable接口-因此您不能在using语句中使用它。
而且,您必须依靠使用此类的人来处置它。
如果不这样做,则在调用Finalizer之前,连接不会关闭,而这完全不受开发人员的控制。它甚至可能根本不会被调用-因为垃圾收集器可能不需要在使用此代码的任何应用程序的运行期间清除内存。

这就是为什么处理数据库连接的正确方法是在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);
}

以此类推。

可以随时从该项目中借用想法-甚至直接使用它-我有一些应用程序正在使用它,并且它们在相当长的时间内运行得很好。

答案 1 :(得分:2)

您不是通过IDisposable接口实现一次性模式,只是有一个Dispose方法,因此您将无法在using语句中调用它。

public class Database : IDisposable { ... }

这有点让人怀疑,我的意思是,如果您已经在使用它,那么您就不会在using语句中使用它,并且似乎在尝试缓存连接。我会完全避开这个。

您还拥有一个析构函数,但是99%的时候它的用法是错误的

答案 2 :(得分:0)

虽然持久层非常有意义,但是您必须以不同的方式进行设计。您要做的是将一些复杂性打包到仍然可以执行相同操作的方法中,例如ChangeParam()GetDataReader()

通常,您拥有具有底层技术知识的存储库,并且可以消除这种复杂性,例如GetAllCustomers()(着重于域术语 customer )。

当您说了4或5个这样的存储库时,便可以通过将复杂度抽象到父类中来开始重构。这样,您就可以将GetDataReader()等复杂性打包并从存储库提升到位于其上的抽象存储库中。 / p>

从开始的地方开始,您将获得另一层,该层不会抽象得那么多,并且具有太多(通常是不必要的)功能。

如果那么简单,ADO.NET 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();
}

这是我希望在持久层中看到的东西,消耗数据部分实际上是最重要的,因为它是依赖于域的。剩下的只是没有兴趣的样板代码。