Почему мой SqlCommand возвращает строку, когда она должна быть int? - PullRequest
4 голосов
/ 19 мая 2009

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

Мы получили несколько случайных исключений FormatException, которые мы отследили до нескольких запросов к базе данных. После некоторой дополнительной регистрации я обнаружил, что этим утром запрос ниже вернул строку "гладиатор". Website.PkID - это столбец типа int, который работает большую часть времени, но в некоторых случаях он с треском проваливается и возвращает либо int, который уже есть (больше, чем любой действующий WebsiteID), либо случайную строку.

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

Не думаю, что проблема связана с этим запросом. Я видел похожие FormatException (из-за неожиданного результата), поступающие также из запросов LINQ. Мы также обнаружили некоторые из этих ошибок примерно в одно и то же время:

Произошла ошибка транспортного уровня при отправке запроса на сервер. (провайдер: провайдер TCP, ошибка: 0 - существующее соединение было принудительно закрыто удаленным хостом.

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

Оскорбляющий запрос:

public static int GetActiveWebSiteID(string storeID, string statusID)
{
    int retval;

    string sql = @"SELECT isnull(MAX(PkID),0) FROM WebSite 
                   WHERE StoreID = @StoreID 
                   AND WebSiteStatusID = @WebSiteStatusID";

    SqlConnection conn = new SqlConnection(Settings.ConnString);
    SqlCommand cmd = new SqlCommand(sql, conn);
    cmd.CommandType = CommandType.Text;
    cmd.Parameters.AddWithValue("@StoreID", (object)storeID ?? DBNull.Value);
    cmd.Parameters.AddWithValue("@WebSiteStatusID", (object)statusID ?? DBNull.Value);

    conn.Open();
    using(conn)
    {
        var scalar = cmd.ExecuteScalar(); // <-- This value returned here should only ever be an int, but randomly is a string

        retval = Convert.ToInt32(scalar);
    }
    return retval;
}

Вышеупомянутый запрос работал хорошо в течение многих лет до недавнего времени. Теперь у нас есть куча дополнительных запросов LINQ в приложении (не уверен, что это изменит). У нас работает .Net 3.5.

Ответы [ 9 ]

9 голосов
/ 22 сентября 2009

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

Мы наблюдали за запросами в Profiler и смогли убедиться, что плохие результаты всегда были связаны с одним и тем же spid, и что каждый плохой результат всегда был на один запрос позади фактического оператора SQL, который запрашивался. Это было похоже на то, что набор результатов пропущен, и любой набор результатов, следующий за spid (из другого соединения в том же пуле), был возвращен. Сумасшедший.

Методом проб и ошибок мы в конечном итоге отследили несколько запросов SqlCommand или LINQ, чье SqlConnection не было закрыто сразу после использования. Вместо этого из-за неаккуратного программирования, возникшего из-за неправильного понимания соединений LINQ, объекты DataContext были расположены (и соединения закрыты) только в конце запроса, а не сразу.

Как только мы реорганизовали эти методы, чтобы немедленно закрыть соединение с помощью блока C # «using» (освобождая этот пул для следующего запроса), мы больше не получали ошибок. Несмотря на то, что мы до сих пор не знаем причину, по которой пул соединений может быть настолько запутан, мы смогли устранить все ошибки этого типа. Эта проблема была решена в связи с другой подобной ошибкой, которую я разместил и обнаружил здесь: Что является причиной «фатальных ошибок внутреннего соединения»?

1 голос
/ 19 мая 2009

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

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

using(SqlConnection conn = new SqlConnection("hard-coded connection string"))
{
    using (SqlCommand cmd = new SqlCommand(sql, conn))
    {
        // more init
        object scalar = cmd.ExecuteScalar();

        // process result
    }
 }

Меня не удивит, что есть два экземпляра базы данных, и в одном PkID - это int, в другом - varchar.


Взгляните на SQL Profiler, чтобы узнать, сможете ли вы поймать возвращение "гладиатора". В другом случае, с которым я работал, SQL Profiler вообще ничего не показывал, что указывало на то, что фактический запрос направлялся в другую базу данных.

1 голос
/ 19 мая 2009

Я думаю, что вы думали о sqlCommand.ExecuteNonQuery, который возвращает количество строк, затронутых в значении int ...

Это определение метода ExecuteScalar:

public override object ExecuteScalar()
Member of System.Data.SqlClient.SqlCommand

Резюме:

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

Возвращает:

Первый столбец первой строки в наборе результатов или нулевая ссылка (Ничего в Visual Basic), если набор результатов пуст.

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

1 голос
/ 19 мая 2009

Тип возврата функции ExecuteScalar () - object, и вы объявляете свою переменную результата с ключевым словом var. Это не очень хорошая комбинация, потому что вы оказываете большое давление на систему, чтобы сделать правильный вывод типа.

0 голосов
/ 20 мая 2009

Я предполагаю, что Settings.ConnString читается из Web.Config или реестра и повторно использует другие статические подпрограммы. Возможно ли, что существует проблема синхронизации, когда второй метод выполняется перед вызовом cmd.ExecuteScalar () в вашей подпрограмме, который изменяет cmd.CommandText в соединении?

Надеюсь, это поможет,

Bill

0 голосов
/ 19 мая 2009

Я думаю, что ни ваш опубликованный запрос, ни LINQ не являются проблемой.

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

Выбор не нарушен.

0 голосов
/ 19 мая 2009

Есть ли сходство с тем, когда он не может вернуть int?

Поскольку ваш запрос только когда-либо возвращает один столбец в одной строке, что вы получите, если используете более безопасный тип ExecuteReader и примете значение первого столбца?

Всегда ли возвращается строка? Если предложение WHERE заставляет его не возвращать строки (скажем, ваши параметры не соответствуют вашим ожиданиям), ваш ISNULL не вступает в силу - строк вообще нет, а ExecuteScalar должен вернуть NULL.

0 голосов
/ 19 мая 2009

Может быть несколько таблиц WebSite. Можете ли вы квалифицировать таблицу с именем схемы:

ВЫБРАТЬ isnull (МАКС. (PkID), 0) С YourSchema.WebSite ГДЕ StoreID = @StoreID AND WebSiteStatusID = @ WebSiteStatusID

0 голосов
/ 19 мая 2009

Поле «PkID» представляет собой varchar / char в таблице «WebSite».

Если часть запроса «ISNULL» имеет значение true, тогда она вернет целое число (0), иначе вернет строку со значением «PkID»

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...