240

How do I check to see if a column exists in a SqlDataReader object? In my data access layer, I have create a method that builds the same object for multiple stored procedures calls. One of the stored procedures has an additional column that is not used by the other stored procedures. I want to modified the method to accommodate for every scenario.

My application is written in C#.

Michael Kniskern
  • 24,792
  • 68
  • 164
  • 231

27 Answers27

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

Using Exceptions for control logic like in some other answers is considered bad practice and has performance costs. It also sends false positives to the profiler of # exceptions thrown and god help anyone setting their debugger to break on exceptions thrown.

GetSchemaTable() is also another suggestion in many answers. This would not be a preffered way of checking for a field's existance as it is not implemented in all versions (it's abstract and throws NotSupportedException in some versions of dotnetcore). GetSchemaTable is also overkill performance wise as it's a pretty heavy duty function if you check out the source.

Looping through the fields can have a small performance hit if you use it a lot and you may want to consider caching the results.

Chad Grant
  • 44,326
  • 9
  • 65
  • 80
  • What if an alias is used? The name comparison will fail. – Murphybro2 Mar 24 '17 at 10:54
  • 1
    It's debatable that using the exception flow is bad practice. It was once thought bad because it is RELATIVELY expensive to other operators, but negligible in a connected application. Skeet measured 40-118 exceptions per ms depending on stack depth all the way back in 2006. https://stackoverflow.com/a/891230/852208. Further without testing, it's possible that this code is actually slower with it's average case checking half of all columns (though still trivial in a db connected app). I would edit this answer to only include the middle paragraph as the other two are opinions. – b_levitt May 21 '19 at 16:44
  • 6
    @b_levitt it is not debatable, it's crap code and you shouldn't rely on exceptions for control flow – Chad Grant Aug 13 '19 at 01:26
  • Like the two sentences i pointed to, that is yet another opinion that is not supported with any rationale beyond performance in purely computational application. I dare you to set your debugger to break on all exceptions and disable just my code and you'll see how much even the framework and other libraries are already doing this.The problem with your advice is that it pushes developers to return codes which most agree are an inferior pattern: https://stackoverflow.com/questions/99683/which-and-why-do-you-prefer-exceptions-or-return-codes. Such methodology fails the "pit of success" test. – b_levitt Aug 14 '19 at 18:23
  • From a code perspective, your answer is a valid one. But your opinion trying to weight it as a superior answer to the answer with the try/catch (which also handles aliases) is out of place here. – b_levitt Aug 14 '19 at 18:26
  • In C# 6, exception filters were added. Furthermore, we have had different types of exceptions built into the core system libraries for a long time. If it was the intention of the progenitors of the language that exceptions were not to be used for control, these would not be features. – Broom Aug 14 '19 at 19:35
  • Nothing in my example requires developers to return codes? It returns a boolean! Should brush up on some basic coding guidelines: "DO NOT use exceptions for the normal flow of control, if possible." https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/exception-throwing – Chad Grant Aug 28 '19 at 22:04
  • Strawman. I never said your example requires developers to return codes. Your example is indeed "an answer", but your opinion on exception logic does drive people away from exceptions into inferior patterns. – b_levitt Sep 17 '19 at 21:35
  • 3
    In then end, all I'm saying is stick with the facts. Leave your top voted snip of code. But drop the opinions - that's not what S.O. is for. Most people are upvoting your answer because it's working code, they are not saying "I agree". But then somebody inexperienced comes along and reads "that is bad practice", misinterprets a few hundred upvotes as "I agree", and now I have to spend extra hours debugging code that that's throwing an error 5 lines too late because somebody wrote a DoSomething method that returns false when it's fails but forgot to check it with a bloody "if". – b_levitt Sep 17 '19 at 21:53
  • This answer is faster than the GetOrdinal/catch method when total columns returned is less than 11. See my answer for a full explanation and test code. – b_levitt Sep 18 '19 at 21:50
  • 2
    @b_levitt `Most people are upvoting your answer because it's working code, they are not saying "I agree".` You know this...how? – LarsTech Sep 20 '19 at 21:35
  • Extrapolated from the comments for and against on the GetOrdinal answer. – b_levitt Sep 20 '19 at 22:09
  • Subject of [this meta post](https://meta.stackexchange.com/questions/367383/what-to-do-if-a-poster-is-resistant-to-an-edit). – Peter Mortensen Jul 16 '21 at 11:35
  • This is a VB.Net version of this: Public Function HasColumn(ByVal dr As IDataRecord, columnName As String) As Boolean For i = 0 To dr.FieldCount - 1 If dr.GetName(i).Equals(columnName, StringComparison.InvariantCultureIgnoreCase) Then Return True End If Next Return False End Function – Rob Kraft May 16 '22 at 23:10
65

The correct code is:

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; 
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jasmine
  • 4,003
  • 2
  • 29
  • 39
  • @Jasmine: Thank you for your suggestion. BTW, this will work for an OracleDataReader, as well! @Chad Grant: I agree, using Exceptions for control flow is a NO NO! Unless you're not concerned about writing enterprise-ready (i.e. professional) code. – Steve J Jun 15 '09 at 15:06
  • 5
    @Jasmine: I spoke too soon! Your code checks for a column in the schema table, not your result set. You need to compare "field" (assuming "field" is the column name) to the value of each row's "ColumnName" field. Break when you find it, return false if you don't. – Steve J Jun 15 '09 at 21:46
  • Yeah you're right. I ended up writing something like that for my program, which is why I was over here looking at this in the first place. It is an ugly looping thing... 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; } – Jasmine Jun 23 '09 at 19:58
  • 4
    @Steve J: When would the resultset NOT have a column in the GetSchemaTable? – Bless Yahu Aug 07 '09 at 16:13
  • Did not work for me, unfortunately. But with a modification I found it did, see my answer. – David Andersson Apr 22 '10 at 07:10
  • 1
    To anyone else confused, THIS DOES NOT WORK. See The answer below about retrieving the ColumnName row from the schema table and using it. – Jason Jackson Dec 17 '12 at 17:30
  • 5
    Yes, this DOES NOT WORK. Who upvoted it so many times??? It would've saved me a lot of debugging time later if this answer wasn't here! – c00000fd Apr 15 '13 at 10:11
  • Possibly because the answer is actually in the comments. I will edit the answer to include the solution in the main part. – Jasmine Apr 15 '13 at 21:27
  • Why not edit your answer to remove the part which doesn't work? I'm still confused as to what actually works and what doesn't within your answer. – NickG Aug 08 '13 at 16:01
  • Nick, they both work, but this is not the accepted answer. There's actually five correct answers here, and they are all slightly different. – Jasmine Aug 08 '13 at 19:45
  • 1
    @Jasmine they both work? Not really. Kindly remove the first part of your answer. I would have done myself, but for your last comment! – nawfal Dec 12 '13 at 13:49
  • This is a good answer, but it cleans up even more with Linq. See the answer provided by Victor Labastida if interested. – Hagelt18 Dec 10 '15 at 17:36
  • See the [GetSchemaTable documentation](https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqldatareader.getschematable?view=netframework-4.7.2) for why the original part of this answer doesn't work. Each column that you're looking for is represented as a row, not a column, in the `GetSchemaTable` result. – Daniel Schilling Aug 29 '18 at 18:19
  • using GetSchemaTable() is excessive (allocation wise) for just finding a column name. And it's not implemented in dotnet core. Check out the source https://github.com/microsoft/referencesource/blob/24a17c0c3b23ddeac0926acc13b13c54b650d3be/System.Data/System/Data/SqlClient/SqlDataReader.cs#L422 – Chad Grant Oct 04 '19 at 17:54
42

In one line, use this after your DataReader retrieval:

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

Then,

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

Edit

Much more efficient one-liner that does not requires to load the schema:

var exists = Enumerable.Range(0, dr.FieldCount).Any(i => string.Equals(dr.GetName(i), fieldName, StringComparison.OrdinalIgnoreCase));
Larry
  • 17,605
  • 9
  • 77
  • 106
  • 1
    You're enumerating the field names multiple times / allocating another array to scan with a contains, this would be much less performant in high traffic code. – Chad Grant Oct 04 '19 at 17:44
  • @ChadGrant of course, that's why the Linq one liner is much more efficient since it performs only one iteration. – Larry Oct 05 '19 at 22:31
  • This works well and is simple. Don't see much of a performance hit either. – Michael Jul 13 '21 at 18:22
  • 1
    Both of these options (`fieldNames.Contains` and `Enumerable.Any`) are linear time. So, if you want to check n columns, you will have to traverse the array n^2 times. It would be more efficient to save the results in a hashset, which is constant time lookup. – Oleksiy Apr 14 '22 at 21:28
35

I think your best bet is to call GetOrdinal("columnName") on your DataReader up front, and catch an IndexOutOfRangeException in case the column isn't present.

In fact, let's make an extension method:

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

Edit

Ok, this post is starting to garner a few down-votes lately, and I can't delete it because it's the accepted answer, so I'm going to update it and (I hope) try to justify the use of exception handling as control flow.

The other way of achieving this, as posted by Chad Grant, is to loop through each field in the DataReader and do a case-insensitive comparison for the field name you're looking for. This will work really well, and truthfully will probably perform better than my method above. Certainly I would never use the method above inside a loop where performace was an issue.

I can think of one situation in which the try/GetOrdinal/catch method will work where the loop doesn't. It is, however, a completely hypothetical situation right now so it's a very flimsy justification. Regardless, bear with me and see what you think.

Imagine a database that allowed you to "alias" columns within a table. Imagine that I could define a table with a column called "EmployeeName" but also give it an alias of "EmpName", and doing a select for either name would return the data in that column. With me so far?

Now imagine that there's an ADO.NET provider for that database, and they've coded up an IDataReader implementation for it which takes column aliases into account.

Now, dr.GetName(i) (as used in Chad's answer) can only return a single string, so it has to return only one of the "aliases" on a column. However, GetOrdinal("EmpName") could use the internal implementation of this provider's fields to check each column's alias for the name you're looking for.

In this hypothetical "aliased columns" situation, the try/GetOrdinal/catch method would be the only way to be sure that you're checking for every variation of a column's name in the resultset.

Flimsy? Sure. But worth a thought. Honestly I'd much rather an "official" HasColumn method on IDataRecord.

Community
  • 1
  • 1
Matt Hamilton
  • 200,371
  • 61
  • 386
  • 320
  • 1
    I was going to suggest the same thing, GetOrdinal is great because the lookup is case insensitive, if it fails, it does a case sensitive look up. – Ta01 Dec 17 '08 at 00:16
  • 17
    using exceptions for control logic? no no no – Chad Grant May 01 '09 at 23:42
  • 30
    There is one small thing that everyone overlooks when I originally posted this question...I asked the question on 12/8/08 and Matt posted his answer on 12/17/08. Everyone made a stink about catching an exception for control logic but did not provide a solid alternative solution until 5/1/09. That is why it was originally marked as the answer. I am still using this solution today. – Michael Kniskern Dec 14 '10 at 15:32
  • 22
    This will have a performance hit only if the column was not there. The other methods described will have a performance hit, and a larger performance hit, every single time. While it is generally bad practice to avoid using exception handling for flow of control, this solution should not be ruled out without first considering if it works in your case. – Nick Harrison May 05 '11 at 15:22
  • You also have to catch other exceptions that mean *"column does not exist"* (e.g. `ArgumentException`) – Ian Boyd Jul 20 '12 at 14:19
  • 7
    +1. I'm ok with "Don't use exception for control logic" as a broad design rule. It does not mean "avoid it at all cost". The answer is a very well documented workaround, and as @Nick says, the performance hit (if any..) only occurs when the column does not exists. – Larry Nov 04 '13 at 13:26
  • @IanBoyd it's not necessary. Other exceptions will be thrown and bubbled up anyway. – Larry Aug 05 '14 at 19:46
  • 2
    Using Exceptions as control logic also makes debugging more cumbersome in my experience. You have to untick "Thrown" on "Common Language Runtime Exceptions" and then when you get a real exception it might break in a handler somewhere and not on the line that has the problem. – cedd Dec 23 '14 at 10:32
  • I don't see anything wrong with this code. Worrying about exception cost in a connected application is like worrying about the aerodynamics of a radio antenna on a car. https://stackoverflow.com/a/891230/852208 – b_levitt May 21 '19 at 16:46
  • 2
    I'm beginning to understand why this answer gets downvoted. When reading https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/exception-throwing it has the summary of "DO NOT use exceptions for the normal flow of control, if possible" This article is about THROWING exceptions. The code in this example would only be violating that rule if it was doing something like throw FalseException in the case of the column not being found. – b_levitt Sep 17 '19 at 21:57
  • after testing, this method is faster for more than 11 columns. See my answer at the bottom. – b_levitt Sep 18 '19 at 21:44
  • How some even allow their tongue or finger move so to suggest to use exceptions for checking algorithm? Oh dear ... no no no. – Artfaith Oct 06 '20 at 20:29
  • @Angel I don't know where you get the idea that this isn't practical code. I find no evidence regarding exceptions to the contrary. Any evidence that people think they have turns out to be out of context. I think it's far worse to ask people to follow a dogma without explanation, leading them into bad programming practices. At the end of the day this is a very functional answer that can perform BETTER and with fewer side effects than most of the answers presented. – b_levitt Jul 20 '21 at 21:20
  • https://web.archive.org/web/20140430044213/http://c2.com/cgi-bin/wiki?DontUseExceptionsForFlowControl, https://softwareengineering.stackexchange.com/q/189222/219582, https://stackoverflow.com/q/729379/5113030 https://wiki.c2.com/?DontUseExceptionsForFlowControl – Artfaith Jul 21 '21 at 03:56
  • @angel the example from of these articles is try { for (int i = 0; /*wot no test?*/ ; i++) array[i]++; } catch (ArrayIndexOutOfBoundsException e) {}. This is a great example to use a for loop wrong and I've never seen anybody do that before so it seems like a solution in search of a problem. Is there anything that objectively demonstrates issues with exceptions? – b_levitt Jul 21 '21 at 18:47
  • for the other article i will refute with the author's own answer: https://stackoverflow.com/a/729573/852208 – b_levitt Jul 21 '21 at 18:48
18

Here is a working sample for Jasmin's idea:

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

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

}
Chris Ji
  • 189
  • 1
  • 2
  • 1
    Only if you wrap a try/catch around it – Donald.Record Jun 25 '15 at 15:59
  • You can simplify this idea with: reader.GetSchemaTable().Columns.Contains("myFiled") – Lev Z Nov 24 '16 at 16:24
  • using GetSchemaTable() is excessive (allocation wise) for just finding a column name. Check out the source https://github.com/microsoft/referencesource/blob/24a17c0c3b23ddeac0926acc13b13c54b650d3be/System.Data/System/Data/SqlClient/SqlDataReader.cs#L422 – Chad Grant Oct 04 '19 at 17:52
14

This works for me:

bool hasColumnName = reader.GetSchemaTable().AsEnumerable().Any(c => c["ColumnName"] == "YOUR_COLUMN_NAME");
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • using GetSchemaTable() is excessive (allocation wise) for just finding a column name. And it's not implemented in all versions of dotnet core. Check out the source https://github.com/microsoft/referencesource/blob/24a17c0c3b23ddeac0926acc13b13c54b650d3be/System.Data/System/Data/SqlClient/SqlDataReader.cs#L422 – Chad Grant Oct 04 '19 at 17:58
13

The following is simple and worked for me:

 bool hasMyColumn = (reader.GetSchemaTable().Select("ColumnName = 'MyColumnName'").Count() == 1);
Paulo Lisboa
  • 139
  • 1
  • 2
  • using GetSchemaTable() is excessive (allocation wise) for just finding a column name. Check out the source https://github.com/microsoft/referencesource/blob/24a17c0c3b23ddeac0926acc13b13c54b650d3be/System.Data/System/Data/SqlClient/SqlDataReader.cs#L422 – Chad Grant Oct 04 '19 at 17:49
10

I wrote this for Visual Basic users:

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

I think this is more powerful and the usage is:

If HasColumnAndValue(reader, "ID_USER") Then
    Me.UserID = reader.GetDecimal(reader.GetOrdinal("ID_USER")).ToString()
End If
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
9

If you read the question, Michael asked about DataReader, not DataRecord folks. Get your objects right.

Using a r.GetSchemaTable().Columns.Contains(field) on a DataRecord does work, but it returns BS columns (see screenshot below.)

To see if a data column exists AND contains data in a DataReader, use the following extensions:

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

Usage:

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

Calling r.GetSchemaTable().Columns on a DataReader returns BS columns:

Calling GetSchemeTable in a DataReader

Levitikon
  • 7,749
  • 9
  • 56
  • 74
  • 1
    see comments under Matts answer – nawfal Aug 08 '13 at 04:49
  • 1
    What do you mean by *DataRecord **does work**, but it returns BS columns*? You mean it runs (and gives wrong results)? – nawfal Dec 12 '13 at 14:01
  • 2
    "Get your objects right." - but `IDataReader` implements `IDataRecord`. They are different interfaces of the same object - just like `ICollection` and `IEnumerable` are different interfaces of `List`. `IDataReader` allows advancing to the next record, while `IDataRecord` allows reading from the current record. The methods that are being used in this answer all come from the `IDataRecord` interface. See https://stackoverflow.com/a/1357743/221708 for an explanation of why declaring the parameter as `IDataRecord` is preferable. – Daniel Schilling Aug 29 '18 at 17:55
  • Upvote for showing why `r.GetSchemaTable().Columns` is an absolutely wrong answer to this question. – Daniel Schilling Aug 29 '18 at 17:59
  • GetName() is inheritied from the IDataRecord interface into IDataReader. Targeting the base interface is the correct code. – Chad Grant Oct 04 '19 at 17:56
  • shouldn't use exceptions for control-flow. This is bad design. – Dave Black Nov 02 '21 at 18:43
6

TLDR:

There are lots of answers with claims about performance and bad practice, so I clarify that here.

The exception route is faster for higher numbers of returned columns, the loop route is faster for lower number of columns, and the crossover point is around 11 columns. Scroll to the bottom to see a graph and test code.

Full answer:

The code for some of the top answers work, but there is an underlying debate here for the "better" answer based on the acceptance of exception handling in logic and its related performance.

To clear that away, I do not believe there is much guidance regarding catching exceptions. Microsoft does have some guidance regarding throwing exceptions. There they do state:

Do not use exceptions for the normal flow of control, if possible.

The first note is the leniency of "if possible". More importantly, the description gives this context:

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

That means, if you are writing an API, that might be consumed by somebody else, give them the ability to navigate an exception without a try/catch. For example, provide a TryParse with your exception-throwing Parse method. Nowhere does this say though that you shouldn't catch an exception.

Further, as another user points out, catches have always allowed filtering by type and somewhat recently allow further filtering via the when clause. This seems like a waste of language features if we're not supposed to be using them.

It can be said that there is some cost for a thrown exception, and that cost may impact performance in a heavy loop. However, it can also be said that the cost of an exception is going to be negligible in a "connected application". Actual cost was investigated over a decade ago: How expensive are exceptions in C#?

In other words, the cost of a connection and query of a database is likely to dwarf that of a thrown exception.

All that aside, I wanted to determine which method truly is faster. As expected there is no concrete answer.

Any code that loops over the columns becomes slower as the number of columns increase. It can also be said that any code that relies on exceptions will slow depending on the rate in which the query fails to be found.

Taking the answers of both Chad Grant and Matt Hamilton, I ran both methods with up to 20 columns and up to a 50% error rate (the OP indicated he was using this two test between different stored procedures, so I assumed as few as two).

Here are the results, plotted with LINQPad:

Results - Series 1 is Loop, 2 is Exception

The zigzags here are fault rates (column not found) within each column count.

Over narrower result sets, looping is a good choice. However, the GetOrdinal/Exception method is not nearly as sensitive to number of columns and begins to outperform the looping method right around 11 columns.

That said, I don't really have a preference performance wise as 11 columns sounds reasonable as an average number of columns returned over an entire application. In either case we're talking about fractions of a millisecond here.

However, from a code simplicity aspect, and alias support, I'd probably go with the GetOrdinal route.

Here is the test in LINQPad form. Feel free to repost with your own method:

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
  • 7,059
  • 2
  • 41
  • 56
  • 2
    You clearly have some sort of weird obsession with exceptions. A better approach would just to cache the column location in a static lookup for performance and use the integer lookup – Chad Grant Sep 20 '19 at 18:11
  • another issue with using exceptions as control flow is they show up in the profiler as # of exceptions thrown when in your suggested code they are intentional ... not exceptions. Not to mention setting your debugger to break on exceptions thrown. Essentially reporting errors that are not errors. You shouldn't be doing this. – Chad Grant Oct 04 '19 at 17:33
  • 1
    There are also counters for finallys / sec, and filters / sec. Are those bad too? I would call it a possible caveat- the first real one you have provided. Counter's are just information. They don't mean anything unless they correspond to a performance issue - and in this case I've already shown the point where exceptions has BETTER performance. I've also indicated that the framework and libraries already throw a lot of exceptions. I've got an instance of visual studio throwing 60 ex/s right now. Exceptions are not errors unless they are uncaught. – b_levitt Oct 07 '19 at 13:54
  • Great analysis. I used its results in my new answer. – yazanpro Nov 13 '19 at 19:46
  • (The sample code is inconsistently formatted.) – Peter Mortensen Jul 16 '21 at 11:49
  • @PeterMortensen - Elaborate and I may fix. – b_levitt Jul 16 '21 at 15:38
4
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);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Deepak
  • 59
  • 1
4

Here is a one-liner LINQ version of the accepted answer:

Enumerable.Range(0, reader.FieldCount).Any(i => reader.GetName(i) == "COLUMN_NAME_GOES_HERE")
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Clement
  • 3,990
  • 4
  • 43
  • 44
4

Here is the solution from Jasmine in one line... (one more, though simple!):

reader.GetSchemaTable().Select("ColumnName='MyCol'").Length > 0;
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
spaark
  • 701
  • 10
  • 17
  • using GetSchemaTable() is excessive (allocation wise) for just finding a column name. Check out the source https://github.com/microsoft/referencesource/blob/24a17c0c3b23ddeac0926acc13b13c54b650d3be/System.Data/System/Data/SqlClient/SqlDataReader.cs#L422 – Chad Grant Oct 04 '19 at 17:52
  • @ChadGrant Possible. I guess one has to choose wisely depending on the context and the frequency it is necessary to use this... – spaark Oct 18 '19 at 08:50
2

To keep your code robust and clean, use a single extension function, like this:

C#

public static class Extensions
{
    public static bool HasColumn(this SqlDataReader r, string columnName)
    {
        return !string.IsNullOrEmpty(columnName) && r.FieldCount > 0 && Enumerable.Range(0, r.FieldCount).Select((i) => r.GetName(i)).Contains(columnName, StringComparer.OrdinalIgnoreCase);
    }
}

VB:

Public Module Extensions

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

        Return Not String.IsNullOrEmpty(columnName) AndAlso r.FieldCount > 0 AndAlso Enumerable.Range(0, r.FieldCount).Select(Function(i) r.GetName(i)).Contains(columnName, StringComparer.OrdinalIgnoreCase)

    End Function

End Module
Michael B
  • 119
  • 5
1

This code corrects the issues that Levitikon had with their code: (adapted from: [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;
}

The reason for getting all of those useless column names and not the name of the column from your table... Is because your are getting the name of schema column (i.e. the column names for the Schema table)

NOTE: this seems to only return the name of the first column...

EDIT: corrected code that returns the name of all columns, but you cannot use a SqlDataReader to do it

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
}
nawfal
  • 70,104
  • 56
  • 326
  • 368
NeoH4x0r
  • 311
  • 2
  • 6
  • Or in one line `return r.GetSchemaTable().Rows.Cast().Select(x => (string)x["ColumnName"]).ToList();` :) – nawfal Dec 12 '13 at 14:03
  • using GetSchemaTable() is excessive (allocation wise) for just finding a column name. Check out the source https://github.com/microsoft/referencesource/blob/24a17c0c3b23ddeac0926acc13b13c54b650d3be/System.Data/System/Data/SqlClient/SqlDataReader.cs#L422 – Chad Grant Oct 04 '19 at 17:52
0

In your particular situation (all procedures has the same columns except one which has an additional one column), it will be better and faster to check the reader's FieldCount property to distinguish between them.

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

You can also (for performance reasons) mix this solution with the solution iterating solution.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0

Neither did I get GetSchemaTable to work, until I found this way.

Basically I do this:

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
David Andersson
  • 1,246
  • 2
  • 11
  • 17
0

My data access class needs to be backward compatible, so I might be trying to access a column in a release where it doesn't exist in the database yet. We have some rather large data sets being returned so I'm not a big fan of an extension method that has to iterate the DataReader column collection for each property.

I have a utility class that creates a private list of columns and then has a generic method that attempts to resolve a value based on a column name and output parameter type.

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

Then I can just call my code like so

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
  • 151
  • 1
  • 12
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 is case-insensitive btw.

LPL
  • 16,827
  • 6
  • 51
  • 95
RBAFF79
  • 9
  • 1
  • Contains() doesn't throw exceptions, this code is pointless. You would only be catching null pointer exceptions. – Chad Grant Oct 04 '19 at 18:03
0

The key to the whole problem is here:

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

If the referenced three lines (currently lines 72, 73, and 74) are taken out, then you can easily check for -1 in order to determine if the column doesn't exist.

The only way around this while ensuring native performance is to use a Reflection based implementation, like the following:

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

The Reflection based extension method:

/// 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
  • 4,512
  • 6
  • 44
  • 66
0

I would recommend using try{} catch{} for this simple issue. However, I would not recommend handling exception in catch.

try 
{
  if (string.IsNullOrEmpty(reader["Name"].ToString())) 
  {
    name = reader["Name"].ToString();
  }
}
catch
{
  //Do nothing
}
SomeBody
  • 7,515
  • 2
  • 17
  • 33
esenkaya
  • 99
  • 4
0

This is a pretty old thread, but I wanted to provide my two cents.

The challenge with most of the proposed solutions is that it requires you to enumerate over all fields every time for every row for every column you're checking.

Others are using the GetSchemaTable method which is not globally supported.

Personally, I have no issue with throwing and catching exceptions to check if a field exists. In fact, I think it's probably the most straightforward solution from a programming perspective and the easiest to debug and create an extension for. I've noticed no negative performance hits on swallowing exceptions except where there is some other transaction involved or weird rollback logic.

Implementation using a try-catch block

using System;
using System.Collections.Generic;
using System.Data.SqlClient;

public class MyModel {
    public int ID { get; set; }
    public int UnknownColumn { get; set; }
}


public IEnumerable<MyModel> ReadData(SqlCommand command) {
    using (SqlDataReader reader = command.ExecuteReader()) {
        try {
            while (reader.Read()) {
                // init the row
                MyModel row = new MyModel();

                // bind the fields
                row.ID = reader.IfDBNull("ID", row.ID);
                row.UnknownColumn = reader.IfDBNull("UnknownColumn", row.UnknownColumn);

                // return the row and move forward
                yield return row;
            }
        } finally {
            // technically the disposer should handle this for you
            if (!reader.IsClosed) reader.Close();
        }
    }
}

// I use a variant of this class everywhere I go to help simplify data binding
public static class IDataReaderExtensions {
    // clearly separate name to ensure I don't accidentally use the wrong method
    public static T IfDBNull<T>(this IDataReader reader, string name, T defaultValue) {
        T value;
        try {
            // attempt to read the value
            // will throw IndexOutOfRangeException if not available
            object objValue = reader[name];

            // the value returned from SQL is NULL
            if (Convert.IsDBNull(objValue)) {
                // use the default value
                objValue = defaultValue;
            }
            else if (typeof(T) == typeof(char)) {
                // chars are returned from SQL as strings
                string strValue = Convert.ToString(objValue);

                if (strValue.Length > 0) objValue = strValue[0];
                else objValue = defaultValue;
            }

            value = (T)objValue;
        } catch (IndexOutOfRangeException) {
            // field does not exist
            value = @defaultValue;
        } catch (InvalidCastException, ex) {
            // The type we are attempting to bind to is not the same as the type returned from the database
            // Personally, I want to know the field name that has the problem
            throw new InvalidCastException(name, ex);
        }

        return value;
    }

    // clearly separate name to ensure I don't accidentally use the wrong method
    // just overloads the other method so I don't need to pass in a default
    public static T IfDBNull<T>(this IDataReader reader, string name) {
        return IfDBNull<T>(reader, name, default(T));
    }
}

If you want to avoid exception handling, I'd recommend saving your results to a HashSet<string> when you initialize your reader, then checking back to it for the columns you want. Alternatively for a micro-optimization, you can implement your columns as a Dictionary<string, int> to prevent a duplicate resolution from Name to ordinal by the SqlDataReader object.

Implementation using HashSet<string>

using System;
using System.Collections.Generic;
using System.Data.SqlClient;

public class MyModel {
    public int ID { get; set; }
    public int UnknownColumn { get; set; }
}

public IEnumerable<MyModel> ReadData(SqlCommand command) {
    using (SqlDataReader reader = command.ExecuteReader()) {
        try {
            // first read
            if (reader.Read()) {
                // use whatever *IgnoreCase comparer that you're comfortable with
                HashSet<string> columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

                // init the columns HashSet<string, int>
                for (int i = 0; i < reader.FieldCount; i++) {
                    string fieldName = reader.GetName(i);
                    columns.Add(fieldName);
                }

                // implemented as a do/while since we already read the first row
                do {
                    // init a new instance of your class
                    MyModel row = new MyModel();

                    // check if column exists
                    if (columns.Contains("ID") &&
                        // ensure the value is not DBNull
                        !Convert.IsDBNull(reader["ID"])) {
                        // bind value
                        row.ID = (int)reader["ID"];
                    }

                    // check if column exists
                    if (columns.Contains("UnknownColumn") &&
                        // ensure the value is not DBNull
                        !Convert.IsDBNull(reader["UnknownColumn"])) {
                        // bind value
                        row.UnknownColumn = (int)reader["UnknownColumn"];
                    }

                    // return the row and move forward
                    yield return row;
                } while (reader.Read());
            }
        } finally {
            // technically the disposer should handle this for you
            if (!reader.IsClosed) reader.Close();
        }
    }
}

Implementation using Dictionary<string, int>

using System;
using System.Collections.Generic;
using System.Data.SqlClient;

public class MyModel {
    public int ID { get; set; }
    public int UnknownColumn { get; set; }
}

public IEnumerable<MyModel> ReadData(SqlCommand command) {
    using (SqlDataReader reader = command.ExecuteReader()) {
        try {
            // first read
            if (reader.Read()) {
                // use whatever *IgnoreCase comparer that you're comfortable with
                Dictionary<string, int> columns = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

                // init the columns Dictionary<string, int>
                for (int i = 0; i < reader.FieldCount; i++) {
                    string fieldName = reader.GetName(i);
                    columns[fieldName] = i;
                }

                // implemented as a do/while since we already read the first row
                do {
                    // init a new instance of your class
                    MyModel row = new MyModel();

                    // stores the resolved ordinal from your dictionary
                    int ordinal;

                    // check if column exists
                    if (columns.TryGetValue("ID", out ordinal) &&
                        // ensure the value is not DBNull
                        !Convert.IsDBNull(reader[ordinal])) {
                        // bind value
                        row.ID = (int)reader[ordinal];
                    }

                    // check if column exists
                    if (columns.TryGetValue("UnknownColumn", out ordinal) &&
                        // ensure the value is not DBNull
                        !Convert.IsDBNull(reader[ordinal])) {
                        // bind value
                        row.UnknownColumn = (int)reader[ordinal];
                    }

                    // return the row and move forward
                    yield return row;
                } while (reader.Read());
            }
        } finally {
            // technically the disposer should handle this for you
            if (!reader.IsClosed) reader.Close();
        }
    }
}
Kom N
  • 63
  • 4
-1

You can also call GetSchemaTable() on your DataReader if you want the list of columns and you don't want to have to get an exception...

Dave Markle
  • 95,573
  • 20
  • 147
  • 170
  • 2
    There is some debate as to whether this works: http://stackoverflow.com/questions/373230/check-for-column-name-in-a-sqldatareader-object/813713#813713 – bzlm Apr 08 '10 at 06:40
-1

Use:

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

It probably would not be as efficient in a loop.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Skadoosh
  • 2,575
  • 8
  • 40
  • 53
  • See [Levitikon's answer](https://stackoverflow.com/a/7248381/221708) to see the kind of thing that `dr.GetSchemaTable().Columns` contains - it's not what you're looking for. – Daniel Schilling Aug 29 '18 at 18:06
-1

Although there is no publicly exposed method, a method does exist in the internal class System.Data.ProviderBase.FieldNameLookup which SqlDataReader relies on.

In order to access it and get native performance, you must use the ILGenerator to create a method at runtime. The following code will give you direct access to int IndexOf(string fieldName) in the System.Data.ProviderBase.FieldNameLookup class as well as perform the book keeping that SqlDataReader.GetOrdinal()does so that there is no side effect. The generated code mirrors the existing SqlDataReader.GetOrdinal() except that it calls FieldNameLookup.IndexOf() instead of FieldNameLookup.GetOrdinal(). The GetOrdinal() method calls to the IndexOf() function and throws an exception if -1 is returned, so we bypass that behavior.

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

}
Derek Ziemba
  • 2,467
  • 22
  • 22
  • 1
    The internal code does almost the exact same thing my answer is doing, without the need for this strange reflection / delegate. It's caching the lookup per object instance which would not be beneficial as in the real world you wanna cache the ordinals the first time the query is run and use that cache over the lifetime of the app, not build a new cache on every query. – Chad Grant Oct 04 '19 at 18:34
-1

This works to me:

public static class DataRecordExtensions
{
    public static bool HasColumn(IDataReader dataReader, string columnName)
    {
        dataReader.GetSchemaTable().DefaultView.RowFilter = $"ColumnName= '{columnName}'";
        return (dataReader.GetSchemaTable().DefaultView.Count > 0);
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
-1

Use:

if(Enumerable.Range(0,reader.FieldCount).Select(reader.GetName).Contains("columName"))
{
     employee.EmployeeId= Utility.ConvertReaderToLong(reader["EmployeeId"]);
}

You can get more details from Can you get the column names from a SqlDataReader?.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
A.Muditha
  • 99
  • 1
  • 4