Перевірте ім'я стовпця в об'єкті 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 . Далі без тестування можливо, що цей код насправді повільніше, оскільки середній випадок реєструє половину всіх стовпців (хоча все-таки тривіально в додатку, підключеному до db). Я б відредагував цю відповідь, щоб включити лише середній абзац, оскільки інші два - це думки.
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
@Jasmine: Я говорив занадто рано! Ваш код перевіряє наявність стовпця в таблиці схем, а не набір результатів. Вам потрібно порівняти "поле" (припустимо, що "поле" є ім'ям стовпця) зі значенням поля "Ім'я стовпця" кожного рядка. Перервіться, коли знайдете його, поверніть помилкове, якщо цього не зробите.
Стів Дж

4
@Steve J: Коли в наборі результатів НЕ буде стовпець у GetSchemaTable?
Благословіть Яху

1
Щоб хтось хто не заплутався, ЦЕ НЕ ПРАЦЮЄ. Див. Відповідь нижче про отримання рядка ColumnName з таблиці схеми та його використання.
Джейсон Джексон

3
Так, це НЕ ПРАЦЮЄ. Хто стільки разів протистояв цьому ??? Це врятувало б мені багато часу налагодження пізніше, якби цієї відповіді не було тут!
c00000fd

1
@Jasmine вони обоє працюють? Не зовсім. Будь ласка, видаліть першу частину своєї відповіді. Я б зробив сам, але для вашого останнього коментаря!
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 був би єдиним способом бути впевненим у тому, що ви перевіряєте наявність усіх змін імені стовпця в наборі результатів.

Надуманий? Звичайно. Але варто подумати. Чесно кажучи, я набагато скоріше "офіційний" метод HasColumn на IDataRecord.


15
використовуючи винятки для логіки управління? ні ні ні
Чад Грант

28
Є одна невелика річ, яку всі оглядають, коли я спочатку опублікував це запитання ... Я задав це питання 8.12.08, а Метт опублікував свою відповідь 17.12.08. Усі задумувались над тим, щоб знайти виняток для керуючої логіки, але не запропонували надійного альтернативного рішення до 5/1/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));

Ви перераховуєте імена полів кілька разів / виділяєте інший масив для сканування з вмістом, це буде набагато менш ефективно в коді з високим трафіком.
Чад Грант

@ChadGrant, звичайно, саме тому один вкладиш Linq набагато ефективніший, оскільки він виконує лише одну ітерацію.
Ларрі

18

Ось робочий зразок ідеї Жасміна:

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

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

}

1
Тільки якщо ви
обернете

Ви можете спростити цю ідею за допомогою: reader.GetSchemaTable (). Стовпці.Контейнери ("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/…
Чад Грант


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().Columnsна DataReader повертає стовпці BS:

Виклик GetSchemeTable у DataReader


дивіться коментарі у відповіді
Маттса

Що ви розумієте під DataRecord працює , але він повертає стовпці BS ? Ви маєте на увазі, що вона працює (і дає неправильні результати)?
nawfal

2
"Налаштуйте свої об'єкти правильно." - але IDataReaderзнаряддя IDataRecord. Вони є різними інтерфейсами одного і того ж об'єкта - так само, як ICollection<T>і IEnumerable<T>є різними інтерфейсами List<T>. IDataReaderдозволяє переходити до наступного запису, при цьому IDataRecordдозволяє читати з поточного запису. Методи, які використовуються у цій відповіді, походять з IDataRecordінтерфейсу. Дивіться stackoverflow.com/a/1357743/221708 для пояснення того, чому декларування параметру IDataRecordбажано.
Даніель Шиллінг

Оновлення для показу, чому 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

4

Ось одна версія linq linq прийнятої відповіді:

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

порівняльне врахування регістру ... чому?
Чад Грант

4

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

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

використання 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. Корпорація Майкрософт має деякі рекомендації щодо викидання винятків. Там вони заявляють:

НЕ використовуйте винятки для нормального потоку управління, якщо це можливо.

Перша примітка - поблажливість "якщо можливо". Що ще важливіше, опис дає такий контекст:

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

Це означає, що якщо ви пишете API, який може споживати хтось інший, надайте їм можливість орієнтуватися на виняток без спроби / лову. Наприклад, надайте TryParse методом Parse, який викидає виключення. Ніде це не говорить, хоча ви не повинні виловлювати виняток.

Крім того, як вказує інший користувач, уловки завжди дозволяли фільтрувати за типом, і дещо недавно дозволяють подальше фільтрування за допомогою пункту "коли" . Це здається марною мовою, якщо ми не повинні ними користуватися.

Можна сказати, що для кинутого винятку є ДЕЯКІ витрати, і це МОЖЕ вплинути на ефективність у важкому циклі. Однак можна також сказати, що вартість винятку буде "незначною" у "підключеній програмі". Фактична вартість була досліджена більше десятиліття тому: 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; }


}

1
У вас явно є якась дивна одержимість винятками. Кращим підходом було б просто кешувати розташування стовпців у статичному пошуку для продуктивності та використовувати цілий пошук
Чад Грант

Інша проблема використання винятків як контрольного потоку - це те, що вони відображаються в профілі як # винятків, викинутих, коли у запропонованому вами коді вони навмисні ... не винятки. Не кажучи вже про те, щоб налаштувати налагоджувальний механізм на перерву на викинуті винятки. По суті повідомлення про помилки, які не є помилками. Ти не повинен цього робити.
Чад Грант

1
Існують також лічильники для остаточних / сек і фільтрів / сек. Це теж погано? Я б назвав це можливим застереженням - першим справжнім, який ви надали. Лічильники - це лише інформація. Вони нічого не означають, якщо вони не відповідають питанням продуктивності - і в цьому випадку я вже показав точку, коли винятки мають ПОСЛІДНІшу продуктивність. Я також зазначив, що рамки та бібліотеки вже кидають багато винятків. Зараз у мене є екземпляр візуальної студії, яка кидає 60 екс / с. Винятки не є помилками, якщо вони не знайдені.
b_levitt

Чудовий аналіз. Я використав її результати у своїй новій відповіді.
язанпро

1

Цей код виправляє проблеми, які виникли у 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;
}

Причина отримання всіх цих непотрібних назв стовпців, а не ім'я стовпця зі своєї таблиці ... Це тому, що ви отримуєте ім'я стовпця схеми (тобто назви стовпців таблиці таблиці)

ПРИМІТКА: це, здається, повертає лише назву першого стовпця ...

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
}

Або в один рядок 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 є нечутливим до регістру btw.


Містить () не викидає винятків, цей код є безглуздим. Ви б тільки збирали нульові винятки вказівника.
Чад Грант

0

У вашій конкретній ситуації (у всіх процедурах є однакові стовпці, крім 1, у яких є додатково 1 стовпець), краще і швидше перевірити читача. Властивість FieldCount розрізняти їх.

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

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


Будь ласка, назвіть рішення, на яке ви посилаєтесь. Які два розчини слід змішати?
Пабло Джомер

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

0

Ключ до всієї проблеми тут :

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

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

Єдиний спосіб подолати це при забезпеченні домашньої продуктивності - використовувати Reflectionбазовану реалізацію, як-от наступне:

Вживання:

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


-1

Як щодо

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

Напевно, це було б не так ефективно в циклі


Дивіться відповідь Левітікона, щоб побачити те, що 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);
        }
}

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.