Как определить, имеет ли SqlConnection встроенный SqlDataReader?

Это нечто более любопытное, чем реальная цель. Если вы открыли SqlConnection и приложили к нему SqlDataReader, а затем попробуйте запустить другой запрос с использованием того же SqlConnection, то он выкинет ошибку. Мой вопрос в том, как SqlConnection знает, что к нему привязан читатель. Для HasDataReader нет общедоступной собственности или чего-то еще, так как класс SqlConnection знает?


Оригинальный вопрос: (что уже не актуально)

Привет, я создаю небольшую вещь для пула соединений и более распространенных ошибок, которые мы имеем (это всегда легкое исправление, но мы просто не можем вспомнить reader.Close()!), когда мы имеют соединение, которое используется множеством классов/методов, и один метод открывает считыватель данных и забывает закрыть его. Это не так уж плохо, потому что много раз все, что вам нужно сделать, - это пойти в отладчик и подняться на один уровень и увидеть функцию до того, как это произошло, и проверить, есть ли у него незакрытый считыватель данных.

Теперь, вот большая проблема. В этом соединении пул, если datareader открыт, тогда он не известен, пока поток не получит соединение и не попытается его использовать, и вещь, которая первоначально открыла устройство чтения данных, может уже не быть живым.

Так просто, как вы можете обнаружить, открыт ли считыватель данных в соединении, и есть ли способ закрыть читатель, не закрывая соединение?

Ответ 1

как SqlConnection знает, что к нему прикреплен читатель

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

Разумное использование Reflector показывает, что объект SQLConnection имеет частное поле типа DBConnectionInternal, которое заполняется одним из ряда конкретных реализаций этого абстрактного класса. Когда вы пытаетесь добавить второго живого читателя к соединению, метод "ValidateConnectionForExecute" вызывается во внутреннем соединении, и это прослеживается при рассмотрении внутреннего "ReferenceCollection". Когда это обнаруживает существующего живого читателя, исключается исключение.

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

Ответ 2

Чтобы убедиться, что вы закрываете свои datareaders (и соединения с базой данных), всегда открывайте их в используемом блоке, например:

using (SqlDataReader rdr = MySqlCommandObject.ExecuteReader())
{
    while (rdr.Read())
    {
        //...
    }
} // The SqlDataReader is guaranteed to be closed here, even if an exception was thrown.

Ответ 3

Никто на самом деле не ответил на вопрос. ( "Почему вы так делаете?" - это не ответ.) Я думаю, что ответ заключается в том, что вы не можете сказать, связано ли соединение с открытым считывателем данных, связанным с ним, просто глядя на самое соединение. Соединение не предоставляет никакого свойства, которое вам скажет. Открытие соединения устанавливает его свойство State в ConnectionState.Open. Открытие устройства чтения данных не изменяет состояние соединения. Значения состояний, такие как ConnectionState.Fetching, используются только тогда, когда выполняются операции с данными, такие как SqlDataReader.Read(). Когда соединение просто сидит между Reads, состояние соединения просто открыто. Поэтому, чтобы определить, когда открытый читатель использует соединение, вам нужно проверить состояния читателей, которые могут его использовать.

Ответ 4

ничего себе. Многие люди не отвечают на вопрос! То, о чем никто не упоминает, - многопоточные приложения. Я думаю, что каждый здесь получает тот факт, что вы должны закрыть читателя, но то, что я, кажется, не видел, что кто-то обращается, - это тот факт, что читатель может не закончиться, когда приходит следующий запрос. Например.. У меня есть таблица который заполняется через отдельный поток, так что я сохраняю взаимодействие с пользовательским интерфейсом. Было бы неплохо, если бы второй третий и четвертый потоки ожидают, пока соединение используется. Затем, когда он освобождает, делайте это в бизнесе. Без чистого пути для определения того, подключено ли к нему устройство чтения, я должен потратить несколько минут на создание какой-то статической логической системы флагов для каждого читателя в каждом классе, который МОЖЕТ хотеть использовать соединение. Намного сложнее, чем нужно

Ответ 5

а затем попробуйте запустить другой запрос, используя тот же SqlConnection, после чего он выдает ошибку.

конечно, вы можете включить Несколько активных наборов результатов - тогда он не бросает. Конечно, есть некоторые ограничения (не всегда ли?), Но это сработает. Конечно, это предназначено только для операций вложенности. Если проблема в том, что вы случайно оставили что-то открытое (что вы должны были уже закрыть), тогда ответ (как уже было сказано) using.

Ответ 6

Чтобы этого избежать, оберните свой DataReader в блок использования, это гарантирует, что он устанавливает соединение следующим образом:

using (IDataReader reader = command.ExecuteReader())
{
      //do stuff
}

Там свойство в IDataReader называется IsClosed, которое сообщит вам его состояние.

Ответ 7

Проверьте, открыта ли она, и если да, закройте ее. Heads-up, если вы используете класс SqlHelper, это ошибка - она ​​не закрывает соединение в некоторых сценариях. Решение состоит в том, чтобы использовать либо try/catch, либо использовать блоки в вашем коде, в зависимости от того, вы до 2.0 или нет.

Ответ 8

Вы также можете использовать делегаты, если по какой-либо причине вы не можете использовать предложение clausule, вот пример того, как это сделать:

public delegate void TransactionRunner(DbConnection sender, DbTransaction trans, object state);

public void RunTransaction(TransactionRunner runner, object state)
    {
        RunTransaction(runner, IsolationLevel.ReadCommitted, state);
    }

public void RunTransaction(TransactionRunner runner, IsolationLevel il, object state)
    {

        DbConnection cn = GetConnection from pool
        DbTransaction trans = null;

        try
        {  
            trans = cn.BeginTransaction(il);
            runner(cn, trans, state);
            trans.Commit();
        }
        catch (Exception err)
        {
            if (trans != null)
                trans.Rollback();
            throw err;
        }
        finally
        {
            //Here you can close anything that was left open
        }
    }

Затем, когда вам нужно использовать это, просто используйте функцию и передайте функцию как

public void DoStuff(){
    TransactionRunner tr = new TransactionRunner(MyFunction);
    RunTransaction(tr, <a parameter>);
}
public void DoStuffInternal(DbConnection cn, DbTransaction trans, object state){
    //Do Stuff and Im sure that the transaction will commit or rollback
}

Теперь это похоже на переизбыток в .Net 3.5, но это было так, как мы это делали тогда .Net 1.0... Надеюсь, это поможет...

Ответ 9

Сегодня я также столкнулся с той же ситуацией, но... нет удачи в Интернете.

Итак, я написал код ниже, чтобы узнать, открыт ли читатель в соединении или вообще найти, будет ли соединение готово к использованию:

private bool IsConnectionReady(SqlConnection Connection)
{
    bool nRet = true;

    try
    {
        String sql = "SELECT * FROM dummy_table";

        using (SqlCommand cmd = new SqlCommand(sql, Connection))
        {
            using (SqlDataReader rdr = cmd.ExecuteReader())
            { }
        }
    }
    catch (Exception ex)
    {
        nRet = false;
    }

    return nRet;
}

"dummy_table" - это пустая фиктивная таблица в моем db для проверки доступности.

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

Итак, я надеюсь, что это поможет вам.

Ответ 10

В соответствии с статья, вы должны всегда закрывать читателя после того, как вы закончите, даже если вы используете используемый блок. Блок использования закрывает соединение, но не закрывает его. Почему непоследовательность? Меня бьет.

Ответ 12

Создайте новый объект команды с тем же соединением и новым считывателем данных с новым созданным объектом команды. Это будет нормально работать.