在合并连接时正确重用 SqlCommand 和 SqlParameter
Correctly reusing SqlCommand and SqlParameter whilst pooling the connection
我正在处理一个可扩展的 WCF 服务组件连接到单个 MS SQL 服务器数据库的设置。 RESTful 服务允许用户将数据保存到数据库中以及从中获取数据。
在实现 class 处理数据库连接/方法的同时,我开始努力正确地重用准备好的 SqlCommands
和连接。我在 MSDN 上阅读了有关连接池以及如何使用 SqlCommand
和 SqlParameter
.
的信息
我的 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)
{
}
}
}
有几个重要方面需要了解:
- 我正在使用
SqlCommand.Prepare()
来加速执行命令的过程
- 重复使用这些命令可避免在每次调用 GetData 和 SaveData 方法时创建新对象,从而不会导致垃圾收集器出现问题
- 只有一个
SqlRepository
class 实例被 WCF 服务使用。
- 此服务每分钟调用很多次,因此保持与数据库的连接打开是我想要的。
现在我阅读了更多关于连接池的内容以及强烈建议在 using
语句中使用 SqlConnection
对象以确保处理的事实。据我了解,即使 SqlConnection
的 Dispose()
方法已被 using
语句调用,连接池技术仍会保持连接打开。
使用它的方法是在 GetData
和 SaveData
方法中有一个 using(SqlConnection connection = new SqlConnection(connectionString))
。但是,然后 - 至少根据我的直觉 - 我还需要在 GetData
/ SaveData
方法中创建 SqlCommands。或不?我找不到任何关于如何以这种方式重用命令的文档。如果我每次进入 GetData
/ SaveData
方法时都需要准备一个新命令,那么对 SqlCommand.Prepare()
的调用也不会毫无意义吗?
如何正确实施 SqlRepository
class?现在的方式我相信如果连接中断(可能是因为数据库服务器关闭一段时间并重新启动),那么 SqlRepository
class 将 not 自动恢复并运行。据我所知,这种故障保存场景是在池技术中处理的。
感谢您的想法和反馈!
克里斯蒂安
不要重复使用 SqlCommand 实例。
您正在同步数据库访问。
通过您的实施,您正在重新使用一个小对象(即使有数千个,这对 GC 来说也没有问题)来交换并发数据库操作。
- 删除同步锁。
- 为每个数据库操作创建新的 SqlCommands 实例。
- 不要调用准备。准备加速数据库操作,但是在
SqlCommand
上执行 ExecuteReader()
和 CommandType = Text
并且参数数量非零后,命令在内部未准备。
我正在处理一个可扩展的 WCF 服务组件连接到单个 MS SQL 服务器数据库的设置。 RESTful 服务允许用户将数据保存到数据库中以及从中获取数据。
在实现 class 处理数据库连接/方法的同时,我开始努力正确地重用准备好的 SqlCommands
和连接。我在 MSDN 上阅读了有关连接池以及如何使用 SqlCommand
和 SqlParameter
.
我的 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)
{
}
}
}
有几个重要方面需要了解:
- 我正在使用
SqlCommand.Prepare()
来加速执行命令的过程 - 重复使用这些命令可避免在每次调用 GetData 和 SaveData 方法时创建新对象,从而不会导致垃圾收集器出现问题
- 只有一个
SqlRepository
class 实例被 WCF 服务使用。 - 此服务每分钟调用很多次,因此保持与数据库的连接打开是我想要的。
现在我阅读了更多关于连接池的内容以及强烈建议在 using
语句中使用 SqlConnection
对象以确保处理的事实。据我了解,即使 SqlConnection
的 Dispose()
方法已被 using
语句调用,连接池技术仍会保持连接打开。
使用它的方法是在 GetData
和 SaveData
方法中有一个 using(SqlConnection connection = new SqlConnection(connectionString))
。但是,然后 - 至少根据我的直觉 - 我还需要在 GetData
/ SaveData
方法中创建 SqlCommands。或不?我找不到任何关于如何以这种方式重用命令的文档。如果我每次进入 GetData
/ SaveData
方法时都需要准备一个新命令,那么对 SqlCommand.Prepare()
的调用也不会毫无意义吗?
如何正确实施 SqlRepository
class?现在的方式我相信如果连接中断(可能是因为数据库服务器关闭一段时间并重新启动),那么 SqlRepository
class 将 not 自动恢复并运行。据我所知,这种故障保存场景是在池技术中处理的。
感谢您的想法和反馈! 克里斯蒂安
不要重复使用 SqlCommand 实例。
您正在同步数据库访问。
通过您的实施,您正在重新使用一个小对象(即使有数千个,这对 GC 来说也没有问题)来交换并发数据库操作。
- 删除同步锁。
- 为每个数据库操作创建新的 SqlCommands 实例。
- 不要调用准备。准备加速数据库操作,但是在
SqlCommand
上执行ExecuteReader()
和CommandType = Text
并且参数数量非零后,命令在内部未准备。