在合并连接时正确重用 SqlCommand 和 SqlParameter

Correctly reusing SqlCommand and SqlParameter whilst pooling the connection

我正在处理一个可扩展的 WCF 服务组件连接到单个 MS SQL 服务器数据库的设置。 RESTful 服务允许用户将数据保存到数据库中以及从中获取数据。

在实现 class 处理数据库连接/方法的同时,我开始努力正确地重用准备好的 SqlCommands 和连接。我在 MSDN 上阅读了有关连接池以及如何使用 SqlCommandSqlParameter.

的信息

我的 class 初始版本如下所示:

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)
        {
        }
    }
}

有几个重要方面需要了解:

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

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

如何正确实施 SqlRepository class?现在的方式我相信如果连接中断(可能是因为数据库服务器关闭一段时间并重新启动),那么 SqlRepository class 将 not 自动恢复并运行。据我所知,这种故障保存场景是在池技术中处理的。

感谢您的想法和反馈! 克里斯蒂安

不要重复使用 SqlCommand 实例。

您正在同步数据库访问。

通过您的实施,您正在重新使用一个小对象(即使有数千个,这对 GC 来说也没有问题)来交换并发数据库操作。

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