C #: я должен использовать «если» вместо «пока», когда Reader имеет только один элемент? - PullRequest
7 голосов
/ 15 декабря 2011

Посмотрите на это:

myReader = cmd.ExecuteReader();
if (myReader.HasRows)
{
    while (myReader.Read())
    {
        info = myReader.GetString("info");
        ...
    }
 }
.....

Ну, я не знаю почему, но когда я компилирую его и использую на другом ПК, он взрывается из-за , а . Но если я сделаю:

myReader = cmd.ExecuteReader();
if (myReader.HasRows)
{
    if (myReader.Read())    <------------------- NOTICE THE IF
    {
        info = myReader.GetString("info");
        ...
    }
 }
.....

... будет работать. Обратите внимание, что я знаю, что у меня будет только один элемент каждый раз из-за логики программы.

Это нормально делать это? Это выглядит ужасно и, возможно, не является хорошей практикой (? ). Я прошу о вашей ориентации.

EDIT : Опубликуем еще немного кода, потому что может помочь в поиске моих ошибок:

 while (myReader.Read())
 {
    info = myReader.GetString("info");
    ...


     /**
      * Write the relevant info in the LOGS
      */
      connection.Close();     <----------   :S  
      connection.Open();      <---------- if I don´t do this I got some problems with the connection!!!!
      string queryLog = "INSERT INTO ....;
      MySqlCommand cmdLog = new MySqlCommand(queryLog, connection);
      cmdLog.ExecuteNonQuery();
 }

Ответы [ 7 ]

13 голосов
/ 15 декабря 2011

Я делаю это всякий раз, когда ожидаю только одну строку , в этом нет ничего плохого.

Если я ожидаю одно значение, вместо этого я использую ExecuteScalar.Это просто вернет значение, хранящееся в первом столбце первой строки возвращенного набора результатов, обычно в наборе есть только один столбец и одна строка, поэтому вы не склонны это замечать.

Также я склоненне беспокоиться о if (myReader.HasRows), поскольку if (myReader.Read()) охватывает это.

Я понятия не имею, почему while вызывает у вас проблемы ... Я просто предлагаю свое мнение об использовании if поверхwhile если вы не ожидаете нескольких строк.

3 голосов
/ 19 декабря 2011

Вы действительно можете использовать if, но вы также должны иметь возможность использовать while в таком случае без ошибок. Просто измените его на if, но если вы сделаете то же самое в случае, когда у вас может быть более одной строки, вы получите ту же ошибку. Поэтому важно понять, почему произошло исключение.

Давайте рассмотрим ваш код:

while (myReader.Read())
 {
    info = myReader.GetString("info");
    ...

Пока все хорошо.

     /**
      * Write the relevant info in the LOGS
      */
      connection.Close();     <----------   :S  
      connection.Open();      <---------- if I don´t do this I got some problems with the connection!!!!

Ой! Вы устранили свои «некоторые проблемы», но знаете ли вы, что это были за «некоторые проблемы»? Это на самом деле здесь, что вы сломали свой while.

В тот момент, когда я сказал «пока все хорошо», вот что у вас происходит:

  1. У вас есть объект подключения. Это "открыто".
  2. У вас есть объект для чтения данных. Извлекает данные из соединения.

Теперь, согласно спецификации IDbCommand.ExecuteReader (http://msdn.microsoft.com/en-us/library/68etdec0.aspx),, соединение используется для этого считывателя. Вы не можете ничего делать с этим соединением, кроме как закрывать его, пока вы не вызовете Close() для считывателя, либо явно, либо через Dispose(), что произойдет, если читатель будет назначен в блоке using.

Теперь строго это не всегда так. Данная реализация всегда может дать вам больше, чем обещания интерфейса (в зависимости от подписи интерфейса или в соответствии с документацией). Многие реализации «освобождают» соединение для повторного использования, когда Read() возвращает false (единственное, что я когда-либо реализовывал), SQLServer 2005 имеет опцию MultipleActiveResultSets=True, которая позволяет одному и тому же соединению одновременно поддерживать более одного считывателя данных одновременно минусы). Тем не менее, за исключением этих нарушений правил, единственное, что мы можем определенно сделать с нашим соединением прямо сейчас, это позвонить по номеру Close().

По этой причине, когда вы пытались вызвать cmdLog.ExecuteNonQuery(), у вас появились "некоторые проблемы" в виде сообщения об ошибке, потому что вы пытались использовать соединение, которое использовалось для чего-то другого.

Итак, вы «исправили» это, выполнив единственное, что вы могли сделать с соединением - закрыв его - и затем открыв снова. Для всех намерений и целей вам понадобится совершенно новое соединение!

Теперь, когда вы возвращаетесь к while, он снова вызывает myReader.Read(). Этот метод считывает из потока данных, поступающих из соединения, либо возвращает false (если результатов больше нет), либо создает набор полей на основе того, что он читает из этого потока.

Но это не так, потому что вы закрыли эту связь. Так что он «взрывается» с исключением, потому что вы пытаетесь читать из закрытого соединения, а это просто невозможно.

Теперь, когда вы заменили while на if, вы никогда не возвращались к операции Read(), которую вы взломали, поэтому код снова работает.

Но что вы собираетесь делать, если вам когда-нибудь понадобится сделать это с набором результатов, который имеет более одной строки?

Вы могли бы использовать MARS. Смотрите http://msdn.microsoft.com/en-us/library/h32h3abf%28v=vs.80%29.aspx для этого. Честно говоря, я бы избегал этого, если что-то от этого не выиграет; у этого есть некоторые тонкие значения, которые могут действительно сбить с толку, если вы ударите их.

Вы можете отложить следующую операцию до закрытия набора читателей. В этом случае это будет просто небольшая экономия, если убрать ненужные Open() и Close():

using(myReader = cmd.ExecuteReader())
{
  while(myReader.Read())
  {
    string info = myReader.GetString("info");
    /* Do stuff */
    using(SqlConnection logConn = new SqlConnection(...))
    {
      logConn.Open();
      string queryLog = "INSERT INTO ....;
      MySqlCommand cmdLog = new MySqlCommand(queryLog, connection);
      cmdLog.ExecuteNonQuery();
    }
  }
}

Теперь может показаться расточительным иметь два соединения, а не одно здесь. Однако:

  1. Объединение в пул делает создание соединений довольно дешевым.
  2. Мы выпускаем его обратно в пул, как только выполним ExecuteNonQuery(), поэтому, если это будет происходить во многих потоках, общее число подключений на ходу, скорее всего, будет вдвое меньше. количество потоков. С другой стороны, если это делает только один поток, тогда есть только одно дополнительное соединение, так что кого это волнует.
  3. Закрытие соединения, когда оно находилось на полпути через предоставление устройства чтения данных, возможно, дало ему дополнительную очистку перед возвратом в пул, так что вы все равно можете использовать два соединения (я не знаю о SQLServer,но я знаю, что некоторым базам данных требуется больше работы, прежде чем они смогут вернуться в пул в таких случаях).

Таким образом, вы не можете использовать соединение без его повторного использования, этоошибкаОбычно вы не можете одновременно выполнять более одной операции с одним и тем же соединением.Использование опции MultipleActiveResultSets с SQLServer2005 позволяет вам сделать это, хотя и имеет свои сложности.Как правило, если вы хотите выполнять более одной операции с БД одновременно, вам следует использовать более одного соединения.

При этом вы все равно можете использовать if вместо while, но сделайте это.потому что это то, что имеет смысл в то время, а не исправлять ошибку, которую вы не понимаете, и возвращать ее когда-нибудь, когда if не вариант.

3 голосов
/ 15 декабря 2011

Я думаю, использование if дает понять читателям вашего кода, что вы ожидаете ровно один элемент. Вы можете добавить assert после конца, чтобы перехватывать логические ошибки, например:

if (myReader.Read())  {
    info = myReader.GetString("info");
    ...
}
Debug.Assert(!myReader.Read(), "Reader has more elements than expected.");
3 голосов
/ 15 декабря 2011

Без использования while вы не сможете обнаружить непредвиденное состояние, если в ридере больше элементов, чем вы думаете. Со временем системы меняются, и предположения типа «у читателя будет только один элемент» имеют тенденцию становиться неверными по мере развития системы.

Если это важно для функции системы или функции кода, который вы пишете, в котором заложено это предположение, я все равно использовал бы while и включал бы некоторый код только для отладочной сборки, который подтверждает действительно получил только один элемент.

1 голос
/ 15 декабря 2011

Правильно ли вы утилизируете соединение и ридер? Используйте Using, например:

using (myReader = cmd.ExecuteReader())
{
    if (myReader.HasRows)   
    {   
        while (myReader.Read())   
        {   
            info = myReader.GetString("info");   
            ...   
        }   
    }
}
0 голосов
/ 15 декабря 2011

Я думаю, что вы все равно должны использовать while, даже если вы ожидаете только одну строку, тогда используйте linq для выражения ожидания одной строки при вызове.Вы можете использовать сопрограмму, чтобы выразить это красиво.

public IEnumerable<string> GetInfo() 
{
    // make command and reader...
    while(reader.Read() 
    {
        yield return reader.GetString("info");
    }
 }

Тогда вызывающий должен использовать метод Single () для выражения ожидания только одной строки.

0 голосов
/ 15 декабря 2011

этот код вы должны добавить следующее, чтобы он возвращал или не возвращал строки

  myReader = cmd.ExecuteReader();
  myReader.Read();
  if (myReader.HasRows)
  {     
     while (myReader.Read())
     { 
        info = myReader.GetString("info");
     }
  } 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...