Я работаю над установкой, в которой масштабируемый компонент службы WCF подключается к одной базе данных MS SQL Server. Служба RESTful позволяет пользователям сохранять данные в БД, а также получать данные от него.Правильное повторное использование SqlCommand и SqlParameter при объединении соединения
При внедрении класса, обрабатывающего соединения/методы базы данных, я начал бороться с правильным повторным использованием подготовленного 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. - Существует много вызовов в минуту для этой службы, поэтому сохранение соединения с БД открытым - это то, что я хочу.
Теперь я дочитал немного больше о пулах соединений и тот факт, что настоятельно рекомендуется использовать SqlConnection
объект в using
заявлении обеспечить утилизацию. Насколько я понимаю, технология объединения пулов заботится о том, чтобы оставить соединение открытым, хотя Dispose()
метод SqlConnection
был вызван оператором using
.
Способ использования будет состоять из using(SqlConnection connection = new SqlConnection(connectionString))
внутри методов GetData
и SaveData
. Однако, тогда, по крайней мере, до моей интуиции, мне нужно было бы создать SqlCommands внутри методов GetData
/SaveData
. Или нет? Я не мог найти документацию о том, как использовать команды таким образом. Также не будет ли вызов SqlCommand.Prepare()
бессмысленным, если мне нужно подготовить новую команду каждый раз, когда я попаду в методы GetData
/SaveData
?
Как правильно реализовать класс SqlRepository
? Теперь я считаю, что если соединение ломается (возможно, из-за того, что сервер БД на какое-то время опускается и перезагружается), тогда класс SqlRepository
будет не автоматически восстанавливаться и функционировать. Насколько я знаю, подобные сценарии отказоустойчивости обрабатываются в технологии объединения.
Спасибо за идеи и отзывы! Christian
Хранилища предназначены для работы над устройством отображения данных (например, OR/M). –
Я лично не думаю, что вы заметите что-нибудь, удалив Prepare(). Я также предпочитаю наличие локального DbConnection в GetData(), SaveData() из-за проблем с повторным подключением. Насколько я знаю, пул соединений ADO.Net прозрачен. Если вы создадите новый DbConnection, ADO.Net получит один из своего внутреннего пула и может повторно использовать «старый». Лично я бы не стал чрезмерно оптимизировать здесь, кроме как вы действительно сталкиваетесь с проблемами. Чтобы протестировать это, вы можете написать некоторые модульные тесты или тестовое приложение и просто попробовать. – Marc
Существует нулевое преимущество повторного использования объекта SqlCommand в том виде, в котором вы работаете. Если что-то еще хуже, просто создавая новый объект для каждого исполнения. Вы, конечно же, не должны открывать SqlConnection в конструкторе и оставлять его открытым до тех пор, пока не будет размещен репозиторий! Держите соединение открытым как можно меньше времени. – GarethD