Моя переменная SqlCommand перегружена? Устало?

В моем тестовом проекте у меня есть статический класс FixtureSetup, который я использую для настройки данных интеграционного тестирования для проверки.

Я использую одни и те же переменные SqlCommand и SqlParameter (а не сам объект) в этом классе, многократно, используя одни и те же ссылки на переменные снова и снова, каждый раз присваивая новые объекты SqlCommand и SqlParameter. Само мое соединение создается один раз и передается в методы, выполняющие настройку, поэтому каждая настройка использует свою собственную отдельную ссылку на подключение, и хотя одно и то же соединение используется несколько раз, оно всегда в линейной последовательности.

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

        cmd = new SqlCommand("INSERT INTO Subscription (User_ID, Name, Active) VALUES (@User_ID, @Name, @Active)", conn);
        parameter = new SqlParameter("@User_ID", TestUserID); cmd.Parameters.Add(parameter);
        parameter = new SqlParameter("@Name", "TestSubscription"); cmd.Parameters.Add(parameter);
        parameter = new SqlParameter("@Active", true); cmd.Parameters.Add(parameter);
        cmd.ExecuteNonQuery();

        cmd = new SqlCommand("SELECT Subscription_ID FROM [Subscription] WHERE Name = 'TestSubscription'", conn);
        parameter = new SqlParameter("@User_ID", TestUserID);
        cmd.Parameters.Add(parameter);
        using (dr = cmd.ExecuteReader())
        {
            while (dr.Read())
            {
                TestSubscriptionID = dr.GetInt32(dr.GetOrdinal("Subscription_ID"));
            }
        }

        cmd = new SqlCommand("INSERT INTO SubscriptionCompany (Subscription_ID, Company_ID) VALUES (@Subscription_ID, @Company_ID)", conn);
        parameter = new SqlParameter("@Subscription_ID", TestSubscriptionID); cmd.Parameters.Add(parameter);
        parameter = new SqlParameter("@Company_ID", KnownCompanyId); cmd.Parameters.Add(parameter);
        cmd.ExecuteNonQuery();

В приведенном выше примере в последней показанной строке, проделав то же самое, что я сделал буквально в десятках других мест (вставьте данные, прочтите столбец идентификатора и запишите его), я получу следующее:

SetUp: System.InvalidOperationException: ExecuteNonQuery требует открытого и доступного соединения. Текущее состояние соединения закрыто. в System.Data.SqlClient.SqlConnection.GetOpenConnection (метод String)

НО - замените cmd на новую переменную myCmd, и все работает нормально!

        SqlCommand myCmd;
        myCmd = new SqlCommand("INSERT INTO Subscription (User_ID, Name, Active) VALUES (@User_ID, @Name, @Active)", conn);
        parameter = new SqlParameter("@User_ID", TestUserID); myCmd.Parameters.Add(parameter);
        parameter = new SqlParameter("@Name", "TestSubscription"); myCmd.Parameters.Add(parameter);
        parameter = new SqlParameter("@Active", true); myCmd.Parameters.Add(parameter);
        myCmd.ExecuteNonQuery();

        myCmd = new SqlCommand("SELECT Subscription_ID FROM [Subscription] WHERE Name = 'TestSubscription'", conn);
        parameter = new SqlParameter("@User_ID", TestUserID);
        myCmd.Parameters.Add(parameter);
        using (dr = myCmd.ExecuteReader())
        {
            while (dr.Read())
            {
                TestSubscriptionID = dr.GetInt32(dr.GetOrdinal("Subscription_ID"));
            }
        }

        myCmd = new SqlCommand("INSERT INTO SubscriptionCompany (Subscription_ID, Company_ID) VALUES (@Subscription_ID, @Company_ID)", conn);
        parameter = new SqlParameter("@Subscription_ID", TestSubscriptionID); myCmd.Parameters.Add(parameter);
        parameter = new SqlParameter("@Company_ID", KnownCompanyId); myCmd.Parameters.Add(parameter);
        myCmd.ExecuteNonQuery();

Что, черт возьми, здесь происходит? Моя команда var просто устала ???

Что подсказало мне «исправление», так это то, что я заметил при трассировке, что в моем блоке «read the id» мой блок cmd.Parameters содержал только ОДИН параметр, второй добавлен, и когда я принудительно использовал первый cmd.Parameters. .Добавьте строку для повторного выполнения, количество параметров в списке упало до 0. Это и побудило меня попробовать метод уровня SqlCommand ... потому что у меня была безумная идея, что мой cmd устал ... Представьте мой шок, когда я видимо оказался прав!

Изменить: я не использую здесь какие-либо объекты - просто ссылку на переменную (статический SqlCommand на уровне класса). Приношу свои извинения за ранее ошибочную формулировку вопроса.


person The Evil Greebo    schedule 26.03.2012    source источник
comment
Да, я просто заметил это сам. Исправление.   -  person The Evil Greebo    schedule 27.03.2012
comment
@marc_s: они просто уменьшились в размерах. нажмите на них правой кнопкой мыши "просмотреть изображение", и они станут читаемыми.   -  person Marc B    schedule 27.03.2012
comment
Слишком поздно - уже заменены фрагментами кода   -  person The Evil Greebo    schedule 27.03.2012
comment
Это локальная статическая переменная для класса FixtureSetup   -  person The Evil Greebo    schedule 27.03.2012
comment
Ни дня не проходит без публикации моего недавнего ответа здесь: stackoverflow.com/questions/9705637/ (Вопрос: ExecuteReader требует открытого и доступного соединения. Текущее состояние соединения - Соединение < / я>)   -  person Tim Schmelter    schedule 27.03.2012
comment
@TimSchmelter +1 за этот ответ, мне нравится   -  person Chris Shain    schedule 27.03.2012
comment
Не думаю, что вы правильно описываете проблему. Вы повторно используете переменные, а не объекты, верно? В первом примере, который демонстрирует проблему, повторно не используется командный объект.   -  person Paul Keister    schedule 27.03.2012
comment
Ты совершенно прав. Я повторно использую только переменную, а не сами объекты. Ну, кроме соединения, которое я создаю и передаю в каждый метод настройки ...   -  person The Evil Greebo    schedule 27.03.2012
comment
Вот - я обновил вопрос, чтобы точно описать, что я повторно использую переменную, а не объект.   -  person The Evil Greebo    schedule 27.03.2012


Ответы (4)


Большое редактирование:

От: http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqldatareader.close.aspx

Вы должны явно вызвать метод Close, когда вы используете SqlDataReader , чтобы использовать связанный SqlConnection для любых других целей.

Метод Close заполняет значения для выходных параметров, возвращаемых значений и RecordsAffected, увеличивая время, необходимое для закрытия SqlDataReader, который использовался для обработки большого или сложного запроса. Когда возвращаемые значения и количество записей, затронутых запросом, не имеют значения, время, необходимое для закрытия SqlDataReader, можно уменьшить, вызвав метод Cancel связанного объекта SqlCommand перед вызовом метода Close.

так что постарайтесь:

using (dr = cmd.ExecuteReader())
{
    while (dr.Read())
    {
        TestSubscriptionID = dr.GetInt32(dr.GetOrdinal("Subscription_ID"));
    }
    dr.Close();
}
person Chris Shain    schedule 26.03.2012
comment
Я передаю соединение. Я перерабатываю ссылку на объект SqlCommand. Я не понимаю, как связан ваш ответ ... - person The Evil Greebo; 27.03.2012
comment
Ага! Теперь эта ссылка помогает прояснить проблему. Полагаю, мне так и надо из-за того, что я ленился с объектами ADO в моем тестовом проекте. - person The Evil Greebo; 27.03.2012
comment
Нет ... он по-прежнему не щелкает - потому что, как Пол указал в комментариях к вопросу, я повторно использую только переменные, а не сами объекты. - person The Evil Greebo; 27.03.2012
comment
Что ж, это хороший момент. Я ошибаюсь, полагая, что размещение SqlDataReader в блоке Using автоматически закрывает программу чтения? - person The Evil Greebo; 27.03.2012
comment
Он автоматически удаляет читателя, и я бы подумал, что он тоже закроет его, но в соответствии с документами, похоже, это не так! - person Chris Shain; 27.03.2012
comment
Хм. Раздражает в тех случаях, когда я нахожу значение и возвращаюсь из блока using. Значит, я должен переделать каждый из них. Сделаю это и дам вам знать через несколько ... - person The Evil Greebo; 27.03.2012
comment
Ага, ты абсолютно прав. using (dr = cmd.ExecuteReader ()) {} НЕ вызывает dr.Close как часть удаления. Как только я убедился, что доктору Клоузе звонят в каждом случае, эта проблема исчезла. - person The Evil Greebo; 27.03.2012
comment
@TheEvilGreebo: На самом деле избавление от SqlDataReader будет закройте его неявно (из отражателя ILSpy вы видите, откуда вызывается close) - person Tim Schmelter; 27.03.2012
comment
@TimSchmelter Я вижу, что используется Dispose, но можете ли вы показать декомпилированный метод Dispose? Тот факт, что его добавление устранило проблему TheEvilGreebo, похоже, говорит об обратном. Может, он используется не во всех путях кода? - person Chris Shain; 27.03.2012
comment
Да, могу. То, что он вроде бы решил вашу проблему, может быть побочным эффектом. - person Tim Schmelter; 27.03.2012
comment
Очень любопытный. Когда я вернусь домой, мне нужно будет посмотреть повнимательнее. - person Chris Shain; 27.03.2012
comment
Хм. Так, возможно, считыватель данных еще не утилизировал, несмотря на вызов Dispose? Это кажется маловероятным ... и до сих пор не объясняет, почему, когда я явно не закрывал и продолжал повторно использовать cmd, это не удалось, но использование нового var myCmd работало (с тем же conn) или почему параметры cmd не складывались в 2 при рассмотрении в сборке до выполнения после использования устройства чтения данных ... Где Алиса, Безумный Шляпник здесь со своим чаем. - person The Evil Greebo; 27.03.2012

используйте одну команду для каждого запроса и вызовите dispose (или, еще лучше, заключите в оператор using). вы не хотите «повторно использовать» компоненты ado.net.

person Jason Meckley    schedule 26.03.2012
comment
Почему нет? cmd - это не что иное, как ссылка на статическую переменную. При каждом повторном использовании я явно заявляю: cmd = new SqlCommand - И я использую его в десятках и десятках других повторных применений в этом же классе. - person The Evil Greebo; 27.03.2012
comment
Проблема не в команде, а в соединении. Прочтите сообщение об ошибке (и мой ответ). - person Chris Shain; 27.03.2012
comment
Я прочитал ваш ответ и прочитал ошибку. Я также проверил ошибку и соединение в то время, когда возникла проблема. Соединение сообщает, что находится в допустимом открытом состоянии, и переход от использования статического cmd к методу local myCmd с использованием того же соединения устранил проблему, поэтому я все еще не вижу, как это соединение, в чем проблема? - person The Evil Greebo; 27.03.2012

Убедитесь, что вы не установили DataReader на CommandBehavior.CloseConnection поскольку вы упомянули, что повторно используете соединение для инициализации теста.

Кроме того, DataReader требует ресурсов, поэтому используйте Dispose

person Brett Veenstra    schedule 26.03.2012
comment
Хорошая мысль, но я это проверил. Я даже попытался явно предоставить DataReader отдельное соединение в какой-то момент ... не имело никакого значения. - person The Evil Greebo; 27.03.2012

Вам действительно нужно создавать новый объект подключения после каждой попытки?

myCmd = new SqlCommand(...)
//your code
myCmd = new SqlCommand(...)
//etc

Вы можете просто сказать:

myCmd.CommandText = "INSERT INTO SubscriptionCompany (Subscription_ID, Company_ID) VALUES (@Subscription_ID, @Company_ID)";

так что вы можете повторно использовать свой командный объект. Кроме того, вы можете сбрасывать свои параметры после каждого звонка. Просто позвоните myCmd.Parameters.Clear().

Кроме того, убедитесь, что вы заключили свой SqlCommand в оператор using, чтобы он был должным образом очищен.

using (SqlCommand cmd = new SqlCommand())
{
    cmd.CommandText = "some proc"
    cmd.Connection = conn;
    //set params, get data, etc
    cmd.CommandText = "another proc"
    cmd.Parameters.Clear();
    //set params, get date, etc.
}
person Bryan Crosby    schedule 26.03.2012
comment
Что ж, да, я могу это сделать, но это не совсем ответ на вопрос, почему существующая переменная cmd SqlCommand, которая постоянно сбрасывается, перестает работать после десятков успешных применений. - person The Evil Greebo; 27.03.2012
comment
@TheEvilGreebo: Попробуйте и посмотрите, происходит ли это по-прежнему. - person Bryan Crosby; 27.03.2012
comment
Не обязательно, я показал, что уже заработал. Вопрос не в том, как это исправить? но почему он не работает как есть? - person The Evil Greebo; 27.03.2012
comment
Ваш SqlConnection объект статичен? Это могло быть проблемой. - person Bryan Crosby; 27.03.2012
comment
Нет, объект подключения создается в моем методе настройки прибора и передается в него. - person The Evil Greebo; 27.03.2012
comment
Обычно это приводит к проблемам с подключением. Чтобы повторить то, что здесь сказали другие, убедитесь, что вы выполняете всю свою работу в рамках блока SqlConnection. Пока соединение открыто, вы можете позволить ADO.NET выполнять свою работу. - person Bryan Crosby; 27.03.2012
comment
На самом деле по этой ссылке - повторное использование самих объектов является ПЛОХОЙ идеей: stackoverflow.com/questions/9705637/ - person The Evil Greebo; 27.03.2012
comment
Позвольте мне пояснить, что я имел в виду под повторным использованием выше, когда вы удаляете соединение через using, ADO.NET не обязательно закрывает соединение с сервером; скорее, он по умолчанию возвращает его в пул. Вы можете открыть одно соединение, выполнить пару sprocs, а затем закрыть ИЛИ это нормально использовать, используя (SqlConnection x = ...) {}, несколькими строками позже, используя (SqlConnection y = ...), конечно, в зависимости от от того, что вы делаете. Это объясняет третий пункт Тима. В вашем случае ваши тесты кажутся относительно небольшими, так что в любом случае не будет особой разницы, так что решать вам. - person Bryan Crosby; 27.03.2012
comment
Все верно. Все еще не имеет отношения к вопросу, но это правда! - person The Evil Greebo; 27.03.2012