views:

8193

answers:

9

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#.

+1  A: 

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.

Matt Hamilton
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.
RandomNoob
using exceptions for control logic? no no no
Chad Grant
A: 

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
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
Well, it's always worked for me.
Dave Markle
A: 

bool exists = false;
int count = dr.FieldCount;
for(int i=0; i < count; i++)
{
   // do a case insensitive comparison here.
   if (dr.GetFieldName(i) == MYFIELDNAME)
   {
       exists = true;
       break;
    }
}

MYFIELDNAME could be the field name, you wish to look for. It might require case-insensitive comparison.

shahkalpesh
There is no GetFieldName in SqlDataReader or IDataRecord and your comment is wrong since you are actually doing a case sensitive compare. FYI in case someone wants to copy/paste your code.
Chad Grant
A: 

Try this:

Type x = (Type?)(returnData.GetSchemaTable().Columns.Contains("column_name_here")?(Type?)returnData["column_name_here"]:null)

where returnData is your sqlDataReader and Type is your nullable data type for tested column (eg. DateTime? or int? ).

twk
+8  A: 

It's much better to use this boolean function:

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

One call - no exceptions. It might throw exceptions internally, but I don't think so.

Jasmine
@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
@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
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
@Steve J: When would the resultset NOT have a column in the GetSchemaTable?
Bless Yahu
@Jasmine @Steve So does this method work at all?
bzlm
Did not work for me, unfortunately. But with a modification I found it did, see my answer.
David Andersson
+30  A: 

In the accepted answer, using Exceptions for control logic is considered bad practice and has performance costs.

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

The more appropriate way to do this is:

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;
    }
}
Chad Grant
Thank you for a solution that makes a lot of sense, I could not bring myself to beleive the catching a exception is the best way to find if a column does exist.
Rihan Meij
Same here - I was like eh? catch the exception?
Harry
+2  A: 

I wrote for VB 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 tihnk this is more powerful and usage is

If HasColumnAndValue(reader, "ID_USER") Then
    Me.UserID = reader.GetDecimal(reader.GetOrdinal("ID_USER")).ToString()
End If
A: 

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
A: 

This works great

public static class DataRecordExtensions { public static bool HasColumn(this IDataRecord dr, string columnName) { for (int i=0; i