在池化连接时正确地重用SqlCommand和SqlParameter

本文关键字:SqlCommand SqlParameter 正确地 连接 | 更新日期: 2023-09-27 18:23:35

我正在进行一个安装,其中一个可扩展的WCF服务组件连接到一个MS SQL Server数据库。RESTful服务允许用户将数据保存到DB中,也可以从中获取数据

在实现一个处理数据库连接/方法的类时,我开始努力正确地重用准备好的SqlCommands和连接。我在MSDN上读到了关于连接池以及如何使用SqlCommandSqlParameter的内容。

我的类的初始版本是这样的:

public class SqlRepository : IDisposable
{
    private object syncRoot = new object();
    private SqlConnection connection;
    private SqlCommand saveDataCommand;
    private SqlCommand getDataCommand;
    public SqlRepository(string connectionString)
    {
        // establish sql connection
        connection = new SqlConnection(connectionString);
        connection.Open();
        // save data
        saveDataCommand = new SqlCommand("INSERT INTO Table (Operation, CustomerId, Data, DataId, CreationDate, ExpirationDate) VALUES (@Operation, @CustomerId, @Data, @DataId, @CreationDate, @ExpirationDate)", connection);
        saveDataCommand.Parameters.Add(new SqlParameter("Operation", SqlDbType.NVarChar, 20));
        saveDataCommand.Parameters.Add(new SqlParameter("CustomerId", SqlDbType.NVarChar, 50));
        saveDataCommand.Parameters.Add(new SqlParameter("Data", SqlDbType.NVarChar, 50));
        saveDataCommand.Parameters.Add(new SqlParameter("DataId", SqlDbType.NVarChar, 50));
        saveDataCommand.Parameters.Add(new SqlParameter("CreationDate", SqlDbType.DateTime));
        saveDataCommand.Parameters.Add(new SqlParameter("ExpirationDate", SqlDbType.DateTime));
        saveDataCommand.Prepare();
        // get data
        getTripCommand = new SqlCommand("SELECT TOP 1 Data FROM Table WHERE CustomerId = @CustomerId AND DataId = @DataId AND ExpirationDate > @ExpirationDate ORDER BY CreationDate DESC", connection);
        getTripCommand.Parameters.Add(new SqlParameter("CustomerId", SqlDbType.NVarChar, 50));
        getTripCommand.Parameters.Add(new SqlParameter("DataId", SqlDbType.NVarChar, 50));
        getTripCommand.Parameters.Add(new SqlParameter("ExpirationDate", SqlDbType.DateTime));
        getTripCommand.Prepare();
    }
    public void SaveData(string customerId, string dataId, string operation, string data, DateTime expirationDate)
    {
        lock (syncRoot)
        {
            saveDataCommand.Parameters["Operation"].Value = operation;
            saveDataCommand.Parameters["CustomerId"].Value = customerId;
            saveDataCommand.Parameters["CreationDate"].Value = DateTime.UtcNow;
            saveDataCommand.Parameters["ExpirationDate"].Value = expirationDate;
            saveDataCommand.Parameters["Data"].Value = data;
            saveDataCommand.Parameters["DataId"].Value = dataId;
            saveDataCommand.ExecuteNonQuery();
        }
    }
    public string GetData(string customerId, string dataId)
    {
        lock (syncRoot)
        {
            getDataCommand.Parameters["CustomerId"].Value = customerId;
            getDataCommand.Parameters["DataId"].Value = dataId;
            getDataCommand.Parameters["ExpirationDate"].Value = DateTime.UtcNow;
            using (var reader = getDataCommand.ExecuteReader())
            {
                if (reader.Read())
                {
                    string data = reader.GetFieldValue<string>(0);
                    return data;
                }
                else
                {
                    return null;
                }
            }
        }
    }
    public void Dispose()
    {
        try
        {
            if (connection != null)
            {
                connection.Close();
                connection.Dispose();
            }
            DisposeCommand(saveDataCommand);
            DisposeCommand(getDataCommand);
        }
        catch { }
    }
    private void DisposeCommand(SqlCommand command)
    {
        try
        {
            command.Dispose();
        }
        catch (Exception)
        {
        }
    }
}

有几个方面需要了解:

  • 我正在使用SqlCommand.Prepare()来加快执行命令的过程
  • 重复使用这些命令可以避免每次调用GetData和SaveData方法时都创建新对象,从而避免垃圾收集器出现问题
  • 只有一个SqlRepository类的实例,由WCF服务使用
  • 每分钟有很多次对该服务的调用,所以保持与数据库的连接是我想要的

现在,我阅读了更多关于连接池的内容,以及强烈建议在using语句中使用SqlConnection对象以确保处理的事实。据我所知,即使using语句调用了SqlConnectionDispose()方法,连接池技术也会保持连接打开。

使用这种方法的方法是在GetDataSaveData方法中有一个using(SqlConnection connection = new SqlConnection(connectionString))。但是,至少根据我的直觉,我还需要在GetData/SaveData方法中创建SqlCommands。还是不?我找不到任何关于如何以这种方式重用命令的文档。此外,如果每次进入GetData/SaveData方法时都需要准备一个新命令,那么对SqlCommand.Prepare()的调用不是没有意义吗?

如何正确实现SqlRepository类?按照现在的方式,我相信如果连接中断(可能是因为DB服务器停机一段时间并重新启动),那么SqlRepository类将而不是自动恢复并正常工作。据我所知,这类故障保存场景是在池技术中处理的。

感谢您的想法和反馈!Christian

在池化连接时正确地重用SqlCommand和SqlParameter

不要重用SqlCommand实例。

您正在同步数据库访问。

在您的实现中,您正在重新使用一个小对象(即使有数千个对象,GC也没有问题)来交换并发的DB操作。

  1. 卸下同步锁
  2. 为每个数据库操作创建新的SqlCommands实例
  3. 不要调用Prepare。Prepare加快了数据库操作的速度,但在具有CommandType = Text且参数数为非零的SqlCommand上执行ExecuteReader()后,该命令在内部是未准备的