在池化连接时正确地重用SqlCommand和SqlParameter
本文关键字:SqlCommand SqlParameter 正确地 连接 | 更新日期: 2023-09-27 18:23:35
我正在进行一个安装,其中一个可扩展的WCF服务组件连接到一个MS SQL Server数据库。RESTful服务允许用户将数据保存到DB中,也可以从中获取数据
在实现一个处理数据库连接/方法的类时,我开始努力正确地重用准备好的SqlCommands
和连接。我在MSDN上读到了关于连接池以及如何使用SqlCommand
和SqlParameter
的内容。
我的类的初始版本是这样的:
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
语句调用了SqlConnection
的Dispose()
方法,连接池技术也会保持连接打开。
使用这种方法的方法是在GetData
和SaveData
方法中有一个using(SqlConnection connection = new SqlConnection(connectionString))
。但是,至少根据我的直觉,我还需要在GetData
/SaveData
方法中创建SqlCommands。还是不?我找不到任何关于如何以这种方式重用命令的文档。此外,如果每次进入GetData
/SaveData
方法时都需要准备一个新命令,那么对SqlCommand.Prepare()
的调用不是没有意义吗?
如何正确实现SqlRepository
类?按照现在的方式,我相信如果连接中断(可能是因为DB服务器停机一段时间并重新启动),那么SqlRepository
类将而不是自动恢复并正常工作。据我所知,这类故障保存场景是在池技术中处理的。
感谢您的想法和反馈!Christian
不要重用SqlCommand实例。
您正在同步数据库访问。
在您的实现中,您正在重新使用一个小对象(即使有数千个对象,GC也没有问题)来交换并发的DB操作。
- 卸下同步锁
- 为每个数据库操作创建新的SqlCommands实例
- 不要调用Prepare。Prepare加快了数据库操作的速度,但在具有
CommandType = Text
且参数数为非零的SqlCommand
上执行ExecuteReader()
后,该命令在内部是未准备的