Проверьте имя столбца в объекте SqlDataReader

212

Как проверить, существует ли столбец в SqlDataReaderобъекте? На уровне доступа к данным я создал метод, который создает один и тот же объект для нескольких вызовов хранимых процедур. Одна из хранимых процедур имеет дополнительный столбец, который не используется другими хранимыми процедурами. Я хочу изменить метод, чтобы приспособиться к каждому сценарию.

Моя заявка написана на C #.

Майкл Книскерн
источник

Ответы:

332
public static class DataRecordExtensions
{
    public static bool HasColumn(this IDataRecord dr, string columnName)
    {
        for (int i=0; i < dr.FieldCount; i++)
        {
            if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
                return true;
        }
        return false;
    }
}

Использование Exceptions для логики управления, как и в некоторых других ответах, считается плохой практикой и приводит к снижению производительности. Он также отправляет ложные срабатывания профилировщику # сгенерированных исключений, и Бог поможет любому, кто настроит свой отладчик на прерывание сгенерированных исключений.

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

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

Чад Грант
источник
Что делать, если используется псевдоним? Сравнение имен не удастся.
Murphybro2
Это спорно , что использование потока исключений является плохой практикой. Когда-то это считалось плохим, потому что это относительно дорого для других операторов, но незначительно в подключенном приложении. Skeet измерял 40-118 исключений в мс в зависимости от глубины стека в 2006 году. Stackoverflow.com/a/891230/852208 . Кроме того, без тестирования возможно, что этот код на самом деле медленнее, так как в среднем он проверяет половину всех столбцов (хотя в приложении, связанном с БД, он все еще тривиален). Я бы отредактировал этот ответ, включив в него только средний абзац, так как два других - это мнения.
b_levitt
3
@b_levitt это не подлежит обсуждению, это дерьмовый код, и вы не должны полагаться на исключения для потока управления
Чад Грант
Как и два предложения, на которые я указывал, это еще одно мнение, которое не подкрепляется никаким обоснованием, кроме производительности, в чисто вычислительном приложении. Я позволю вам настроить ваш отладчик так, чтобы он работал со всеми исключениями и отключил только мой код, и вы увидите, насколько даже фреймворк и другие библиотеки уже делают это. Проблема с вашим советом состоит в том, что он заставляет разработчиков возвращать коды, которые наиболее согласен, это плохая модель: stackoverflow.com/questions/99683/… . Такая методология не проходит проверку на «яму успеха».
b_levitt
С точки зрения кода, ваш ответ правильный. Но ваше мнение, пытающееся оценить его как превосходящий ответ на ответ с помощью try / catch (который также обрабатывает псевдонимы), здесь неуместно.
b_levitt
66

Гораздо лучше использовать эту логическую функцию:

r.GetSchemaTable().Columns.Contains(field)

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

ПРИМЕЧАНИЕ: в комментариях ниже мы поняли это ... правильный код на самом деле это:

public static bool HasColumn(DbDataReader Reader, string ColumnName) { 
    foreach (DataRow row in Reader.GetSchemaTable().Rows) { 
        if (row["ColumnName"].ToString() == ColumnName) 
            return true; 
    } //Still here? Column not found. 
    return false; 
}
жасмин
источник
5
@ Жасмин: я говорил слишком рано! Ваш код проверяет столбец в таблице схемы, а не набор результатов. Вам необходимо сравнить «поле» (при условии, что «поле» является именем столбца) со значением поля «ColumnName» каждой строки. Перерыв, когда вы найдете его, верните false, если нет.
Стив Дж
4
@ Стив Дж .: Когда в наборе результатов НЕ будет столбца в GetSchemaTable?
Благослови Яху
1
Чтобы кто-то еще запутался, ЭТО НЕ РАБОТАЕТ. См. Ответ ниже о получении строки ColumnName из таблицы схемы и ее использовании.
Джейсон Джексон
3
Да, это не работает. Кто проголосовал за это так много раз ??? Это сэкономило бы мне много времени на отладку позже, если бы этого ответа не было!
c00000fd
1
@ Жасмин, они оба работают? На самом деле, нет. Пожалуйста, удалите первую часть вашего ответа. Я бы сделал сам, но за ваш последний комментарий!
Nawfal
33

Я думаю, что вам лучше всего позвонить в GetOrdinal ("columnName") в вашем DataReader и перехватить исключение IndexOutOfRangeException в случае, если столбец отсутствует.

На самом деле, давайте сделаем метод расширения:

public static bool HasColumn(this IDataRecord r, string columnName)
{
    try
    {
        return r.GetOrdinal(columnName) >= 0;
    }
    catch (IndexOutOfRangeException)
    {
        return false;
    }
}

редактировать

Хорошо, этот пост начинает набирать несколько голосов в последнее время, и я не могу удалить его, потому что это принятый ответ, поэтому я собираюсь обновить его и (надеюсь) попытаться оправдать использование обработки исключений как контролировать поток.

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

Я могу вспомнить одну ситуацию, в которой метод try / GetOrdinal / catch будет работать там, где цикл не работает. Однако сейчас это совершенно гипотетическая ситуация, поэтому это очень неубедительное оправдание. В любом случае, терпите меня и посмотрите, что вы думаете.

Представьте себе базу данных, которая позволяет вам «псевдоним» столбцы в таблице. Представьте себе, что я могу определить таблицу со столбцом с именем «EmployeeName», но также дать ей псевдоним «EmpName», и выполнение выбора для любого имени вернет данные в этом столбце. Со мной так далеко?

А теперь представьте, что для этой базы данных есть поставщик ADO.NET, и они для нее кодировали реализацию IDataReader, которая учитывает псевдонимы столбцов.

Теперь dr.GetName(i)(как используется в ответе Чада) можно вернуть только одну строку, поэтому он должен возвращать только один из «псевдонимов» в столбце. Тем не мение,GetOrdinal("EmpName") можно использовать внутреннюю реализацию полей этого провайдера, чтобы проверить псевдоним каждого столбца для искомого имени.

В этой гипотетической ситуации с «псевдонимами столбцов» метод try / GetOrdinal / catch будет единственным способом убедиться, что вы проверяете каждую вариацию имени столбца в наборе результатов.

Flimsy? Конечно. Но стоит подумать. Честно говоря, я бы предпочел «официальный» метод HasColumn для IDataRecord.

Мэтт Гамильтон
источник
15
используя исключения для логики управления? нет нет нет
Чад Грант
28
Есть одна маленькая вещь, которую все упускают из виду, когда я первоначально разместил этот вопрос ... Я задал вопрос 12/8/08, а Мэтт опубликовал свой ответ 17/12/08. Каждый вонял от ловли исключения для логики управления, но не предлагал надежного альтернативного решения до 1/5/09. Вот почему это было первоначально отмечено как ответ. Я все еще использую это решение сегодня.
Майкл Книскерн
19
Это приведет к снижению производительности только в том случае, если столбца не было. Другие описанные методы будут иметь снижение производительности и увеличение производительности каждый раз. Хотя, как правило, не рекомендуется использовать обработку исключений для потока управления, это решение не следует исключать без предварительного рассмотрения вопроса о том, работает ли оно в вашем случае.
Ник Харрисон
5
+1. Я согласен с «Не использовать исключение для логики управления» в качестве общего правила проектирования. Это не означает «избегать этого любой ценой». Ответ - это хорошо документированный обходной путь, и, как говорит @Nick, снижение производительности (если оно есть ..) происходит только тогда, когда столбец не существует.
Ларри
2
Использование исключений в качестве логики управления также делает отладку более трудоемкой в ​​моем опыте. Вы должны снять флажок «Брошено» в «Общих исключениях времени выполнения», а затем, когда вы получите реальное исключение, оно может где-то сломаться в обработчике, а не в строке, в которой возникла проблема.
Cedd
30

В одной строке используйте это после поиска DataReader:

var fieldNames = Enumerable.Range(0, dr.FieldCount).Select(i => dr.GetName(i)).ToArray();

Затем,

if (fieldNames.Contains("myField"))
{
    var myFieldValue = dr["myField"];
    ...

редактировать

Гораздо эффективнее однострочник, который не требует загрузки схемы:

var exists = Enumerable.Range(0, dr.FieldCount).Any(i => string.Equals(dr.GetName(i), fieldName, StringComparison.OrdinalIgnoreCase));
Larry
источник
Вы перечисляете имена полей несколько раз / выделяете другой массив для сканирования с содержимым, это будет гораздо менее производительно в коде с высоким трафиком.
Чад Грант
@ChadGrant, конечно, именно поэтому Linq one liner гораздо эффективнее, поскольку он выполняет только одну итерацию.
Ларри
18

Вот рабочий образец идеи Жасмин:

var cols = r.GetSchemaTable().Rows.Cast<DataRow>().Select
    (row => row["ColumnName"] as string).ToList(); 

if (cols.Contains("the column name"))
{

}
Крис Джи
источник
1
Только если вы
обернетесь
Вы можете упростить эту идею с помощью: reader.GetSchemaTable (). Columns.Contains ("myFiled")
Лев Z
использование GetSchemaTable () является чрезмерным (разумное распределение) для простого поиска имени столбца. Проверьте источник github.com/microsoft/referencesource/blob/…
Чад Грант
12

это работает для меня:

bool hasColumnName = reader.GetSchemaTable().AsEnumerable().Any(c => c["ColumnName"] == "YOUR_COLUMN_NAME");
Виктор Лабастида
источник
использование GetSchemaTable () является чрезмерным (разумное распределение) для простого поиска имени столбца. И это реализовано не во всех версиях ядра dotnet. Проверьте источник github.com/microsoft/referencesource/blob/…
Чад Грант
10

Следующее просто и работает для меня:

 bool hasMyColumn = (reader.GetSchemaTable().Select("ColumnName = 'MyColumnName'").Count() == 1);
Пауло Лиссабон
источник
использование GetSchemaTable () является чрезмерным (разумное распределение) для простого поиска имени столбца. Проверьте источник github.com/microsoft/referencesource/blob/…
Чад Грант
8

Если вы прочитали вопрос, Майкл спросил о DataReader, а не о DataRecord. Получите ваши объекты правильно.

Используя r.GetSchemaTable().Columns.Contains(field) в DataRecord работает, но возвращает столбцы BS (см. Скриншот ниже.)

Чтобы увидеть, существует ли столбец данных И содержит ли данные в DataReader, используйте следующие расширения:

public static class DataReaderExtensions
{
    /// <summary>
    /// Checks if a column's value is DBNull
    /// </summary>
    /// <param name="dataReader">The data reader</param>
    /// <param name="columnName">The column name</param>
    /// <returns>A bool indicating if the column's value is DBNull</returns>
    public static bool IsDBNull(this IDataReader dataReader, string columnName)
    {
        return dataReader[columnName] == DBNull.Value;
    }

    /// <summary>
    /// Checks if a column exists in a data reader
    /// </summary>
    /// <param name="dataReader">The data reader</param>
    /// <param name="columnName">The column name</param>
    /// <returns>A bool indicating the column exists</returns>
    public static bool ContainsColumn(this IDataReader dataReader, string columnName)
    {
        /// See: http://stackoverflow.com/questions/373230/check-for-column-name-in-a-sqldatareader-object/7248381#7248381
        try
        {
            return dataReader.GetOrdinal(columnName) >= 0;
        }
        catch (IndexOutOfRangeException)
        {
            return false;
        }
    }
}

Использование:

    public static bool CanCreate(SqlDataReader dataReader)
    {
        return dataReader.ContainsColumn("RoleTemplateId") 
            && !dataReader.IsDBNull("RoleTemplateId");
    }

Вызов r.GetSchemaTable().ColumnsDataReader возвращает столбцы BS:

Вызов GetSchemeTable в DataReader

Levitikon
источник
см. комментарии под ответом
Matts
Что вы подразумеваете под DataRecord работает , но он возвращает столбцы BS ? Вы имеете в виду, что он работает (и дает неправильные результаты)?
Nawfal
2
"Получите ваши объекты правильно." - но IDataReaderреализует IDataRecord. Это разные интерфейсы одного и того же объекта - как ICollection<T>и IEnumerable<T>разные интерфейсы List<T>. IDataReaderпозволяет перейти к следующей записи, в то время как IDataRecordпозволяет читать из текущей записи. Методы, которые используются в этом ответе, все исходят из IDataRecordинтерфейса. См. Stackoverflow.com/a/1357743/221708 для объяснения того, почему объявление параметра IDataRecordявляется предпочтительным.
Даниэль Шиллинг
Upvote для показа, почему r.GetSchemaTable().Columnsэто абсолютно неправильный ответ на этот вопрос.
Даниэль Шиллинг
GetName () наследуется от интерфейса IDataRecord в IDataReader. Ориентация на базовый интерфейс - правильный код.
Чад Грант
7

Я написал для пользователей Visual Basic:

Protected Function HasColumnAndValue(ByRef reader As IDataReader, ByVal columnName As String) As Boolean
    For i As Integer = 0 To reader.FieldCount - 1
        If reader.GetName(i).Equals(columnName) Then
            Return Not IsDBNull(reader(columnName))
        End If
    Next

    Return False
End Function

Я думаю, что это более мощный и использование:

If HasColumnAndValue(reader, "ID_USER") Then
    Me.UserID = reader.GetDecimal(reader.GetOrdinal("ID_USER")).ToString()
End If
HoLyVieR
источник
4

Вот версия принятого ответа в виде одной строки linq:

Enumerable.Range(0, reader.FieldCount).Any(i => reader.GetName(i) == "COLUMN_NAME_GOES_HERE")
милостивый
источник
сравнение с учетом регистра ... почему?
Чад Грант
4

Здесь решение от Жасмин в одной строке ... (еще один, простой!):

reader.GetSchemaTable().Select("ColumnName='MyCol'").Length > 0;
spaark
источник
использование GetSchemaTable () является чрезмерным (разумное распределение) для простого поиска имени столбца. Проверьте источник github.com/microsoft/referencesource/blob/…
Чад Грант
@ChadGrant Возможно. Я думаю, что нужно выбирать мудро в зависимости от контекста и частоты, которую необходимо использовать ...
Spaark
3
Hashtable ht = new Hashtable();
    Hashtable CreateColumnHash(SqlDataReader dr)
    {
        ht = new Hashtable();
        for (int i = 0; i < dr.FieldCount; i++)
        {
            ht.Add(dr.GetName(i), dr.GetName(i));
        }
        return ht;
    }

    bool ValidateColumn(string ColumnName)
    {
        return ht.Contains(ColumnName);
    }
Дипак
источник
3

TLDR:

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

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

Полный ответ:

Код для некоторых лучших ответов работает, но здесь есть основополагающая дискуссия о «лучшем» ответе, основанном на принятии обработки логики исключений и связанной с этим производительности.

Чтобы уяснить это, я не верю, что есть много рекомендаций относительно исключений CATCHING. У Microsoft есть некоторые рекомендации относительно БРОНИРОВАНИЯ исключений. Там они заявляют:

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

Первое примечание - снисходительность «если возможно». Что еще более важно, описание дает этот контекст:

framework designers should design APIs so users can write code that does not throw exceptions

Это означает, что если вы пишете API, который может использоваться кем-то другим, дайте ему возможность перемещаться по исключению без попытки / улова. Например, предоставьте TryParse свой метод Parse, генерирующий исключения. Однако нигде это не говорит о том, что вы не должны ловить исключение.

Кроме того, как указывает другой пользователь, уловы всегда допускают фильтрацию по типу и в последнее время позволяют дополнительную фильтрацию с помощью предложения when . Это кажется пустой тратой языковых возможностей, если мы не должны их использовать.

Можно сказать, что за выброшенное исключение есть НЕКОТОРЫЕ затраты, и они МОГУТ влиять на производительность в тяжелой петле. Однако можно также сказать, что стоимость исключения будет незначительной в «связанном приложении». Фактическая стоимость была исследована более десяти лет назад: https://stackoverflow.com/a/891230/852208 Другими словами, стоимость соединения и запроса к базе данных, вероятно, будет меньше, чем стоимость брошенного исключения.

Помимо всего прочего, я хотел определить, какой метод действительно быстрее. Как и следовало ожидать, нет конкретного ответа.

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

Взяв ответы Чеда Гранта и Мэтта Гамильтона, я запустил оба метода с количеством столбцов до 20 и частотой ошибок до 50% (ОП указал, что он использовал этот два теста между разными процессами, поэтому я предположил, что их всего два) ,

Вот результаты, полученные с помощью LinqPad: Результаты - серия 1 - петля, 2 - исключение

Зигзаги здесь - это частота отказов (столбец не найден) в пределах каждого столбца.

Более узкие результирующие наборы - это хороший выбор. Тем не менее, метод GetOrdinal / Exception не так чувствителен к количеству столбцов и начинает опережать метод зацикливания около 11 столбцов.

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

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

Вот тест в форме linqpad. Не стесняйтесь делать репосты собственным методом:

void Main()
{
    var loopResults = new List<Results>();
    var exceptionResults = new List<Results>();
    var totalRuns = 10000;
    for (var colCount = 1; colCount < 20; colCount++)
    {
        using (var conn = new SqlConnection(@"Data Source=(localdb)\MSSQLLocalDb;Initial Catalog=master;Integrated Security=True;"))
        {
            conn.Open();

            //create a dummy table where we can control the total columns
            var columns = String.Join(",",
                (new int[colCount]).Select((item, i) => $"'{i}' as col{i}")
            );
            var sql = $"select {columns} into #dummyTable";
            var cmd = new SqlCommand(sql,conn);
            cmd.ExecuteNonQuery();

            var cmd2 = new SqlCommand("select * from #dummyTable", conn);

            var reader = cmd2.ExecuteReader();
            reader.Read();

            Func<Func<IDataRecord, String, Boolean>, List<Results>> test = funcToTest =>
            {
                var results = new List<Results>();
                Random r = new Random();
                for (var faultRate = 0.1; faultRate <= 0.5; faultRate += 0.1)
                {
                    Stopwatch stopwatch = new Stopwatch();
                    stopwatch.Start();
                    var faultCount=0;
                    for (var testRun = 0; testRun < totalRuns; testRun++)
                    {
                        if (r.NextDouble() <= faultRate)
                        {
                            faultCount++;
                            if(funcToTest(reader, "colDNE"))
                                throw new ApplicationException("Should have thrown false");
                        }
                        else
                        {
                            for (var col = 0; col < colCount; col++)
                            {
                                if(!funcToTest(reader, $"col{col}"))
                                    throw new ApplicationException("Should have thrown true");
                            }
                        }
                    }
                    stopwatch.Stop();
                    results.Add(new UserQuery.Results{
                        ColumnCount = colCount, 
                        TargetNotFoundRate = faultRate,
                        NotFoundRate = faultCount * 1.0f / totalRuns, 
                        TotalTime=stopwatch.Elapsed
                    });
                }
                return results;
            };
            loopResults.AddRange(test(HasColumnLoop));

            exceptionResults.AddRange(test(HasColumnException));

        }

    }
    "Loop".Dump();
    loopResults.Dump();

    "Exception".Dump();
    exceptionResults.Dump();

    var combinedResults = loopResults.Join(exceptionResults,l => l.ResultKey, e=> e.ResultKey, (l, e) => new{ResultKey = l.ResultKey, LoopResult=l.TotalTime, ExceptionResult=e.TotalTime});
    combinedResults.Dump();
    combinedResults
        .Chart(r => r.ResultKey, r => r.LoopResult.Milliseconds * 1.0 / totalRuns, LINQPad.Util.SeriesType.Line)
        .AddYSeries(r => r.ExceptionResult.Milliseconds * 1.0 / totalRuns, LINQPad.Util.SeriesType.Line)
        .Dump();
}
public static bool HasColumnLoop(IDataRecord dr, string columnName)
{
    for (int i = 0; i < dr.FieldCount; i++)
    {
        if (dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase))
            return true;
    }
    return false;
}

public static bool HasColumnException(IDataRecord r, string columnName)
{
    try
    {
        return r.GetOrdinal(columnName) >= 0;
    }
    catch (IndexOutOfRangeException)
    {
        return false;
    }
}

public class Results
{
    public double NotFoundRate { get; set; }
    public double TargetNotFoundRate { get; set; }
    public int ColumnCount { get; set; }
    public double ResultKey {get => ColumnCount + TargetNotFoundRate;}
    public TimeSpan TotalTime { get; set; }


}
b_levitt
источник
1
У вас явно есть какая-то странная одержимость исключениями. Лучшим подходом было бы просто кэшировать местоположение столбца в статическом поиске производительности и использовать целочисленный поиск
Чад Грант
Еще одна проблема, связанная с использованием исключений в качестве потока управления, заключается в том, что они отображаются в профилировщике как число исключений, выдаваемых, когда в предложенном вами коде они являются преднамеренными ... а не исключениями. Не говоря уже о настройке вашего отладчика на прерывание при возникновении исключений. По сути сообщения об ошибках, которые не являются ошибками. Вы не должны делать это.
Чад Грант
1
Есть также счетчики для окончаний / сек и фильтров / сек. Это тоже плохо? Я бы назвал это возможным предупреждением - первым настоящим предложением, которое вы предоставили. Счетчики - это просто информация. Они ничего не значат, если они не соответствуют проблеме производительности - и в этом случае я уже показал точку, где исключения имеют ЛУЧШУЮ производительность. Я также указал, что фреймворк и библиотеки уже создают много исключений. У меня есть пример визуальной студии, которая сейчас бросает 60 экз / с. Исключения не являются ошибками, если они не являются необученными.
b_levitt
Отличный анализ. Я использовал его результаты в своем новом ответе.
yazanpro
1

Этот код исправляет проблемы, которые были у Левитикона с их кодом: (адаптировано из: [1]: http://msdn.microsoft.com/en-us/library/system.data.datatablereader.getschematable.aspx )

public List<string> GetColumnNames(SqlDataReader r)
{
    List<string> ColumnNames = new List<string>();
    DataTable schemaTable = r.GetSchemaTable();
    DataRow row = schemaTable.Rows[0];
    foreach (DataColumn col in schemaTable.Columns)
    {
        if (col.ColumnName == "ColumnName") 
        { 
            ColumnNames.Add(row[col.Ordinal].ToString()); 
            break; 
        }
    }
    return ColumnNames;
}

Причина получения всех этих бесполезных имен столбцов, а не имени столбца из вашей таблицы ... в том, что вы получаете имя столбца схемы (т. Е. Имена столбцов для таблицы схемы)

ПРИМЕЧАНИЕ: похоже, это возвращает только имя первого столбца ...

РЕДАКТИРОВАТЬ: исправленный код, который возвращает имя всех столбцов, но вы не можете использовать SqlDataReader для этого

public List<string> ExecuteColumnNamesReader(string command, List<SqlParameter> Params)
{
    List<string> ColumnNames = new List<string>();
    SqlDataAdapter da = new SqlDataAdapter();
    string connection = ""; // your sql connection string
    SqlCommand sqlComm = new SqlCommand(command, connection);
    foreach (SqlParameter p in Params) { sqlComm.Parameters.Add(p); }
    da.SelectCommand = sqlComm;
    DataTable dt = new DataTable();
    da.Fill(dt);
    DataRow row = dt.Rows[0];
    for (int ordinal = 0; ordinal < dt.Columns.Count; ordinal++)
    {
        string column_name = dt.Columns[ordinal].ColumnName;
        ColumnNames.Add(column_name);
    }
    return ColumnNames; // you can then call .Contains("name") on the returned collection
}
NeoH4x0r
источник
Или в одну строчку return r.GetSchemaTable().Rows.Cast<DataRow>().Select(x => (string)x["ColumnName"]).ToList();:)
nawfal
использование GetSchemaTable () является чрезмерным (разумное распределение) для простого поиска имени столбца. Проверьте источник github.com/microsoft/referencesource/blob/…
Чад Грант
1

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

    Public Module Extensions

        <Extension()>
        Public Function HasColumn(r As SqlDataReader, columnName As String) As Boolean

            Return If(String.IsNullOrEmpty(columnName) OrElse r.FieldCount = 0, False, Enumerable.Range(0, r.FieldCount).Select(Function(i) r.GetName(i)).Contains(columnName, StringComparer.OrdinalIgnoreCase))

        End Function

    End Module
Майкл Б
источник
0

Я тоже не получил GetSchemaTable к работе, пока не нашел этот путь .

В основном я делаю это:

Dim myView As DataView = dr.GetSchemaTable().DefaultView
myView.RowFilter = "ColumnName = 'ColumnToBeChecked'"

If myView.Count > 0 AndAlso dr.GetOrdinal("ColumnToBeChecked") <> -1 Then
  obj.ColumnToBeChecked = ColumnFromDb(dr, "ColumnToBeChecked")
End If
Дэвид Андерссон
источник
0
public static bool DataViewColumnExists(DataView dv, string columnName)
{
    return DataTableColumnExists(dv.Table, columnName);
}

public static bool DataTableColumnExists(DataTable dt, string columnName)
{
    string DebugTrace = "Utils::DataTableColumnExists(" + dt.ToString() + ")";
    try
    {
        return dt.Columns.Contains(columnName);
    }
    catch (Exception ex)
    {
        throw new MyExceptionHandler(ex, DebugTrace);
    }
}

Columns.Contains кстати, нечувствителен к регистру.

RBAFF79
источник
Contains () не генерирует исключения, этот код не имеет смысла. Вы бы только ловили исключения нулевого указателя.
Чад Грант
0

В вашей конкретной ситуации (все процедуры имеют одинаковые столбцы, кроме 1, который имеет дополнительный 1 столбец), будет лучше и быстрее проверить читателя. Свойство FieldCount, чтобы различать их.

const int NormalColCount=.....
if(reader.FieldCount > NormalColCount)
{
// Do something special
}

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

pkrzemo
источник
Пожалуйста, назовите решение, на которое вы ссылаетесь. Какие два решения должны быть смешаны?
Пабло Джомер
0

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

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

private List<string> _lstString;

public void GetValueByParameter<T>(IDataReader dr, string parameterName, out T returnValue)
{
    returnValue = default(T);

    if (!_lstString.Contains(parameterName))
    {
        Logger.Instance.LogVerbose(this, "missing parameter: " + parameterName);
        return;
    }

    try
    {
        if (dr[parameterName] != null && [parameterName] != DBNull.Value)
            returnValue = (T)dr[parameterName];
    }
    catch (Exception ex)
    {
        Logger.Instance.LogException(this, ex);
    }
}

/// <summary>
/// Reset the global list of columns to reflect the fields in the IDataReader
/// </summary>
/// <param name="dr">The IDataReader being acted upon</param>
/// <param name="NextResult">Advances IDataReader to next result</param>
public void ResetSchemaTable(IDataReader dr, bool nextResult)
{
    if (nextResult)
        dr.NextResult();

    _lstString = new List<string>();

    using (DataTable dataTableSchema = dr.GetSchemaTable())
    {
        if (dataTableSchema != null)
        {
            foreach (DataRow row in dataTableSchema.Rows)
            {
                _lstString.Add(row[dataTableSchema.Columns["ColumnName"]].ToString());
            }
        }
    }
}

Тогда я могу просто назвать свой код так

using (var dr = ExecuteReader(databaseCommand))
{
    int? outInt;
    string outString;

    Utility.ResetSchemaTable(dr, false);        
    while (dr.Read())
    {
        Utility.GetValueByParameter(dr, "SomeColumn", out outInt);
        if (outInt.HasValue) myIntField = outInt.Value;
    }

    Utility.ResetSchemaTable(dr, true);
    while (dr.Read())
    {
        Utility.GetValueByParameter(dr, "AnotherColumn", out outString);
        if (!string.IsNullOrEmpty(outString)) myIntField = outString;
    }
}
Tresto
источник
0

Ключ ко всей проблеме здесь :

if (-1 == index) {
    throw ADP.IndexOutOfRange(fieldName);
}

Если указанные три строки (в настоящее время строки 72, 73 и 74) удалены, то вы можете легко проверить -1, чтобы определить, не существует ли столбец.

Единственный способ обойти это при обеспечении собственной производительности - это использовать Reflectionоснованную реализацию, например:

Usings:

using System;
using System.Data;
using System.Reflection;
using System.Data.SqlClient;
using System.Linq;
using System.Web.Compilation; // I'm not sure what the .NET Core equivalent to BuildManager.cs

Метод расширения на основе отражения:

/// Gets the column ordinal, given the name of the column.
/// </summary>
/// <param name="reader"></param>
/// <param name="name">The name of the column.</param>
/// <returns> The zero-based column ordinal. -1 if the column does not exist.</returns>
public static int GetOrdinalSoft(this SqlDataReader reader, string name)
{
    try
    {
        // Note that "Statistics" will not be accounted for in this implemenation
        // If you have SqlConnection.StatisticsEnabled set to true (the default is false), you probably don't want to use this method
        // All of the following logic is inspired by the actual implementation of the framework:
        // https://referencesource.microsoft.com/#System.Data/fx/src/data/System/Data/SqlClient/SqlDataReader.cs,d66096b6f57cac74
        if (name == null)
            throw new ArgumentNullException("fieldName");

        Type sqlDataReaderType = typeof(SqlDataReader);
        object fieldNameLookup = sqlDataReaderType.GetField("_fieldNameLookup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(reader);
        Type fieldNameLookupType;
        if (fieldNameLookup == null)
        {
            MethodInfo checkMetaDataIsReady = sqlDataReaderType.GetRuntimeMethods().First(x => x.Name == "CheckMetaDataIsReady" && x.GetParameters().Length == 0);
            checkMetaDataIsReady.Invoke(reader, null);
            fieldNameLookupType = BuildManager.GetType("System.Data.ProviderBase.FieldNameLookup", true, false);
            ConstructorInfo ctor = fieldNameLookupType.GetConstructor(new[] { typeof(SqlDataReader), typeof(int) });
            fieldNameLookup = ctor.Invoke(new object[] { reader, sqlDataReaderType.GetField("_defaultLCID", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(reader) });
        }
        else
            fieldNameLookupType = fieldNameLookup.GetType();

        MethodInfo indexOf = fieldNameLookupType.GetMethod("IndexOf", BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(string) }, null);

        return (int)indexOf.Invoke(fieldNameLookup, new object[] { name });
    }
    catch
    {
        // .NET Implemenation might have changed, revert back to the classic solution.
        if (reader.FieldCount > 11) // Performance observation by b_levitt
        {
            try
            {
                return reader.GetOrdinal(name);
            }
            catch
            {
                return -1;
            }
        }
        else
        {
            var exists = Enumerable.Range(0, reader.FieldCount).Any(i => string.Equals(reader.GetName(i), name, StringComparison.OrdinalIgnoreCase));
            if (exists)
                return reader.GetOrdinal(name);
            else
                return -1;
        }
    }
}
yazanpro
источник
-1

Вы также можете вызвать GetSchemaTable () в вашем DataReader, если вам нужен список столбцов, и вы не хотите получать исключение ...

Дейв Маркл
источник
2
Есть некоторые споры относительно того, работает ли это: stackoverflow.com/questions/373230/…
bzlm
-1

Как насчет

if (dr.GetSchemaTable().Columns.Contains("accounttype"))
   do something
else
   do something

Это, вероятно, не будет столь эффективным в цикле

Skadoosh
источник
Посмотрите ответ Левитикон, чтобы увидеть, что именно dr.GetSchemaTable().Columnsсодержит в себе - это не то, что вы ищете.
Даниэль Шиллинг
-1

Несмотря на то, что нет открытого метода, метод существует во внутреннем классе, System.Data.ProviderBase.FieldNameLookupкоторыйSqlDataReader опирается.

Чтобы получить к нему доступ и получить собственную производительность, вы должны использовать ILGenerator для создания метода во время выполнения. Следующий код даст вам прямой доступ к int IndexOf(string fieldName)в System.Data.ProviderBase.FieldNameLookupклассе, а также выполнять бухгалтерский учет , что SqlDataReader.GetOrdinal()делает так , что нет никакого побочного эффекта. Сгенерированный код отражает существующее, SqlDataReader.GetOrdinal()за исключением того, что он вызывает FieldNameLookup.IndexOf()вместо FieldNameLookup.GetOrdinal(). GetOrdinal()Метод вызывает к IndexOf()функции и генерирует исключение , если -1возвращается, поэтому мы обойти это поведение.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
using System.Reflection.Emit;

public static class SqlDataReaderExtensions {

   private delegate int IndexOfDelegate(SqlDataReader reader, string name);
   private static IndexOfDelegate IndexOf;

   public static int GetColumnIndex(this SqlDataReader reader, string name) {
      return name == null ? -1 : IndexOf(reader, name);
   }

   public static bool ContainsColumn(this SqlDataReader reader, string name) {
      return name != null && IndexOf(reader, name) >= 0;
   }

   static SqlDataReaderExtensions() {
      Type typeSqlDataReader = typeof(SqlDataReader);
      Type typeSqlStatistics = typeSqlDataReader.Assembly.GetType("System.Data.SqlClient.SqlStatistics", true);
      Type typeFieldNameLookup = typeSqlDataReader.Assembly.GetType("System.Data.ProviderBase.FieldNameLookup", true);

      BindingFlags staticflags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Static;
      BindingFlags instflags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance;

      DynamicMethod dynmethod = new DynamicMethod("SqlDataReader_IndexOf", typeof(int), new Type[2]{ typeSqlDataReader, typeof(string) }, true);
      ILGenerator gen = dynmethod.GetILGenerator();
      gen.DeclareLocal(typeSqlStatistics);
      gen.DeclareLocal(typeof(int));

      // SqlStatistics statistics = (SqlStatistics) null;
      gen.Emit(OpCodes.Ldnull);
      gen.Emit(OpCodes.Stloc_0);
      // try {
      gen.BeginExceptionBlock();
      //    statistics = SqlStatistics.StartTimer(this.Statistics);
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Call, typeSqlDataReader.GetProperty("Statistics", instflags | BindingFlags.GetProperty, null, typeSqlStatistics, Type.EmptyTypes, null).GetMethod);
      gen.Emit(OpCodes.Call, typeSqlStatistics.GetMethod("StartTimer", staticflags | BindingFlags.InvokeMethod, null, new Type[] { typeSqlStatistics }, null));
      gen.Emit(OpCodes.Stloc_0); //statistics
      //    if(this._fieldNameLookup == null) {
      Label branchTarget = gen.DefineLabel();
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.GetField));
      gen.Emit(OpCodes.Brtrue_S, branchTarget);
      //       this.CheckMetaDataIsReady();
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Call, typeSqlDataReader.GetMethod("CheckMetaDataIsReady", instflags | BindingFlags.InvokeMethod, null, Type.EmptyTypes, null));
      //       this._fieldNameLookup = new FieldNameLookup((IDataRecord)this, this._defaultLCID);
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_defaultLCID", instflags | BindingFlags.GetField));
      gen.Emit(OpCodes.Newobj, typeFieldNameLookup.GetConstructor(instflags, null, new Type[] { typeof(IDataReader), typeof(int) }, null));
      gen.Emit(OpCodes.Stfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.SetField));
      //    }
      gen.MarkLabel(branchTarget);
      gen.Emit(OpCodes.Ldarg_0); //this
      gen.Emit(OpCodes.Ldfld, typeSqlDataReader.GetField("_fieldNameLookup", instflags | BindingFlags.GetField));
      gen.Emit(OpCodes.Ldarg_1); //name
      gen.Emit(OpCodes.Call, typeFieldNameLookup.GetMethod("IndexOf", instflags | BindingFlags.InvokeMethod, null, new Type[] { typeof(string) }, null));
      gen.Emit(OpCodes.Stloc_1); //int output
      Label leaveProtectedRegion = gen.DefineLabel();
      gen.Emit(OpCodes.Leave_S, leaveProtectedRegion);
      // } finally {
      gen.BeginFaultBlock();
      //    SqlStatistics.StopTimer(statistics);
      gen.Emit(OpCodes.Ldloc_0); //statistics
      gen.Emit(OpCodes.Call, typeSqlStatistics.GetMethod("StopTimer", staticflags | BindingFlags.InvokeMethod, null, new Type[] { typeSqlStatistics }, null));
      // }
      gen.EndExceptionBlock();
      gen.MarkLabel(leaveProtectedRegion);
      gen.Emit(OpCodes.Ldloc_1);
      gen.Emit(OpCodes.Ret);

      IndexOf = (IndexOfDelegate)dynmethod.CreateDelegate(typeof(IndexOfDelegate));
   }

}
Дерек Зимба
источник
1
Внутренний код делает почти то же самое, что и мой ответ, без необходимости этого странного отражения / делегата. Это кэширование поиска для каждого экземпляра объекта, что не было бы выгодно, так как в реальном мире вы хотите кэшировать ординалы при первом запуске запроса и использовать этот кеш на протяжении всей жизни приложения, а не создавать новый кеш для каждого запроса.
Чад Грант
-1

эта работа для меня

public static class DataRecordExtensions
{
        public static bool HasColumn(IDataReader dataReader, string columnName)
        {
            dataReader.GetSchemaTable().DefaultView.RowFilter = $"ColumnName= '{columnName}'";
            return (dataReader.GetSchemaTable().DefaultView.Count > 0);
        }
}
Джошуа Сауседо
источник