Проверьте имя столбца в объекте SqlDataReader - PullRequest
200 голосов
/ 17 декабря 2008

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

Мое приложение написано на C #.

Ответы [ 22 ]

314 голосов
/ 02 мая 2009

Использование Exception s для логики управления, как и в некоторых других ответах, считается плохой практикой и приводит к снижению производительности.

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

Более подходящий способ сделать это:

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;
    }
}
64 голосов
/ 02 мая 2009

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

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; 
}
32 голосов
/ 17 декабря 2008

Я думаю, что вам лучше всего позвонить 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.

25 голосов
/ 09 декабря 2013

В одной строке используйте это после поиска 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));
19 голосов
/ 06 марта 2012

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

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

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

}
13 голосов
/ 03 сентября 2015

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

bool hasColumnName = reader.GetSchemaTable().AsEnumerable().Any(c => c["ColumnName"] == "YOUR_COLUMN_NAME");
11 голосов
/ 25 июня 2015

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

 bool hasMyColumn = (reader.GetSchemaTable().Select("ColumnName = 'MyColumnName'").Count() == 1);
9 голосов
/ 30 августа 2011

Если вы прочитали вопрос, Майкл спросил о 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: /341442/proverte-imya-stolbtsa-v-obekte-sqldatareader
        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().Columns в DataReader возвращает столбцы BS:

Calling GetSchemeTable in a DataReader

7 голосов
/ 11 мая 2009

Я написал для пользователей 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
5 голосов
/ 24 апреля 2014

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

reader.GetSchemaTable().Select("ColumnName='MyCol'").Length > 0;
...