2015-11-19 4 views
0

Я работаю над установкой, в которой масштабируемый компонент службы 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

+0

Хранилища предназначены для работы над устройством отображения данных (например, OR/M). –

+0

Я лично не думаю, что вы заметите что-нибудь, удалив Prepare(). Я также предпочитаю наличие локального DbConnection в GetData(), SaveData() из-за проблем с повторным подключением. Насколько я знаю, пул соединений ADO.Net прозрачен. Если вы создадите новый DbConnection, ADO.Net получит один из своего внутреннего пула и может повторно использовать «старый». Лично я бы не стал чрезмерно оптимизировать здесь, кроме как вы действительно сталкиваетесь с проблемами. Чтобы протестировать это, вы можете написать некоторые модульные тесты или тестовое приложение и просто попробовать. – Marc

+0

Существует нулевое преимущество повторного использования объекта SqlCommand в том виде, в котором вы работаете. Если что-то еще хуже, просто создавая новый объект для каждого исполнения. Вы, конечно же, не должны открывать SqlConnection в конструкторе и оставлять его открытым до тех пор, пока не будет размещен репозиторий! Держите соединение открытым как можно меньше времени. – GarethD

ответ

1

Не используйте повторно экземпляры SqlCommand.

Вы синхронизируете доступ к базе данных.

С вашей реализацией вы повторно используете небольшой объект (который не представляет проблемы для GC, даже если есть тысячи) в обмен на одновременные операции с БД.

  1. Удалить блокировки синхронизации.
  2. Создайте новые экземпляры SqlCommands для каждой операции с базой данных.
  3. Не вызывайте Подготовку. Подготовьте ускорение операций db, но после выполнения ExecuteReader() на SqlCommand с CommandType = Text и с ненулевым числом параметров команда неподготовлена ​​внутренне.
+0

Спасибо, я сделаю это вместо этого. – Christian

Смежные вопросы