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

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

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

Ответ 1

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;
    }
}

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

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

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

Ответ 2

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

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; 
}

Ответ 3

Я думаю, что вам лучше всего сначала вызвать 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") может использовать внутреннюю реализацию полей этого провайдера, чтобы проверить псевдоним каждого столбца для GetOrdinal("EmpName") имени.

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

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

Ответ 4

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

Ответ 5

Вот рабочий пример идеи Jasmin:

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

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

}

Ответ 6

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

bool hasColumnName = reader.GetSchemaTable().AsEnumerable().Any(c => c["ColumnName"] == "YOUR_COLUMN_NAME");

Ответ 7

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

 bool hasMyColumn = (reader.GetSchemaTable().Select("ColumnName = 'MyColumnName'").Count() == 1);

Ответ 8

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

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

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

public static class DataReaderExtensions
{
    /// <summary>
    /// Checks if a column 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 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().Columns в DataReader возвращает столбцы BS:

Calling GetSchemeTable in a DataReader

Ответ 9

Я писал для пользователей 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

Ответ 10

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

Enumerable.Range(0, reader.FieldCount).Any(i => reader.GetName(i) == "COLUMN_NAME_GOES_HERE")

Ответ 11

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

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

Ответ 12

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);
    }

Ответ 13

Этот код исправляет проблемы, которые Levitikon имел с их кодом: (адаптировано из: [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;
}

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

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

EDIT: исправленный код, который возвращает имя всех столбцов, но вы не можете использовать 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
}

Ответ 14

Я тоже не работал 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

Ответ 15

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

    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

Ответ 16

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

Ответ 17

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 нечувствителен к регистру btw.

Ответ 18

Эти ответы уже размещены здесь. Просто немного:

bool b = reader.GetSchemaTable().Rows
                                .Cast<DataRow>()
                                .Select(x => (string)x["ColumnName"])
                                .Contains(colName, StringComparer.OrdinalIgnoreCase);
//or

bool b = Enumerable.Range(0, reader.FieldCount)
                   .Select(reader.GetName)
                   .Contains(colName, StringComparer.OrdinalIgnoreCase);

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

Ответ 19

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

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

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

Ответ 20

Мой класс доступа к данным должен быть обратно совместимым, поэтому я могу попытаться получить доступ к столбцу в выпуске, где он еще не существует в базе данных. Мы возвращаем несколько довольно больших наборов данных, поэтому я не являюсь большим поклонником метода расширения, который должен итерировать коллекцию столбцов 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;
    }
}

Ответ 21

TLDR:

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

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

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

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

Чтобы уяснить это, я не верю, что есть много указаний относительно исключений CATCHING. У Microsoft есть некоторые рекомендации относительно исключений THROWING. Там они заявляют:

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

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

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

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

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

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

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

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

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

Вот результаты, полученные с помощью LinqPad: Results - Series 1 is Loop, 2 is Exception

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

Более узкие результирующие наборы - это хороший выбор. Однако метод 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; }


}

Ответ 24

Как насчет

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

Вероятно, это было бы не так эффективно в цикле

Ответ 25

Несмотря на то, что нет открытого метода, метод существует во внутреннем классе 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, поэтому мы -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));
   }

}

Ответ 26

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

public static class DataRecordExtensions
{
        public static bool HasColumn(IDataReader dataReader, string columnName)
        {
            dataReader.GetSchemaTable().DefaultView.RowFilter = $"ColumnName= '{columnName}'";
            return (dataReader.GetSchemaTable().DefaultView.Count > 0);
        }
}