views:

1935

answers:

12

I frequently have problems dealing with DataRows returned from SqlDataAdapters. When I try to fill in an object using code like this:

DataRow row = ds.Tables[0].Rows[0];
string value = (string)row;

What is the best way to deal with DBNull's in this type of situation.

A: 

Use Nullable Types: http://msdn.microsoft.com/en-us/library/1t3y8s4s(VS.80).aspx

Vaibhav
+9  A: 

Nullable types are good, but only for types that are not nullable to begin with.

To make a type "nullable" append a question mark to the type, for example:

int? value = 5;

I would also recommend using the "as" keyword instead of casting. You can only use the "as" keyword on nullable types, so make sure you're casting things that are already nullable (like strings) or you use nullable types as mentioned above. The reasoning for this is

  1. It's faster than casting
  2. If a type is nullable, the "as" keyword returns null.

I'd recommend doing something like this

DataRow row = ds.Tables[0].Rows[0];
string value = row as string;

Of course, this may make it harder to debug if you're receiving the wrong type in the first place, but at least DBNull will become just plain null

Dan Herbert
+1  A: 

You can also test with Convert.IsDBNull (MSDN).

Pascal Paradis
+1  A: 

Add a reference to System.Data.DataSetExtensions, that adds Linq support for querying data tables.

This would be something like:

string value = (
    from row in ds.Tables[0].Rows
    select row.Field<string>(0) ).FirstOrDefault();
Keith
+1  A: 

I usually write my own ConvertDBNull class that wraps the built-in Convert class. If the value is DBNull it will return null if its a reference type or the default value if its a value type. Example: - ConvertDBNull.ToInt64(object obj) returns Convert.ToInt64(obj) unless obj is DBNull in which case it will return 0.

Manu
+11  A: 

If you aren't using nullable types, the best thing to do is check to see if the column's value is DBNull. If it is DBNull, then set your reference to what you use for null/empty for the corresponding datatype.

DataRow row = ds.Tables[0].Rows[0];
string value;

if (row["fooColumn"] == DBNull.Value)
{
   value = string.Empty;
}
else 
{
   value = Convert.ToString(row["fooColumn"]);
}

As Manu said, you can create a convert class with an overloaded convert method per type so you don't have to pepper your code with if/else blocks.

I will however stress that nullable types is the better route to go if you can use them. The reasoning is that with non-nullable types, you are going to have to resort to "magic numbers" to represent null. For example, if you are mapping a column to an int variable, how are you going to represent DBNull? Often you can't use 0 because 0 has a valid meaning in most programs. Often I see people map DBNull to int.MinValue, but that could potentially be problematic too. My best advice is this:

  • For columns that can be null in the database, use nullable types.
  • For columns that cannot be null in the database, use regular types.

Nullable types were made to solve this problem. That being said, if you are on an older version of the framework or work for someone who doesn't grok nullable types, the code example will do the trick.

Daniel Auger
The line if(row["fooColumn"] == DBNull.Value) works, but isn't correct. It isn't defined DBNull.Value should be implemented as a Singleton pattern. A better line would be: if(row["fooColumn"] is DBNull)
doekman
@doekman - Actually, DBNull *is* a singleton class. To quote MSDN: "DBNull is a singleton class, which means only this [DBNull.Value] instance of this class can exist." http://msdn.microsoft.com/en-us/library/system.dbnull.value.aspx
Greg
+1  A: 

For some reason I've had problems with doing a check against DBNull.Value, so I've done things slightly different and leveraged a property within the DataRow object:

if (row.IsNull["fooColumn"])
{
   value = string.Empty();
}
{
else
{
   value = row["fooColumn"].ToString;
}
Dillie-O
+2  A: 

If you have control of the query that is returning the results, you can use ISNULL() to return non-null values like this:

SELECT 
  ISNULL(name,'') AS name
  ,ISNULL(age, 0) AS age
FROM 
  names

If your situation can tolerate these magic values to substitute for NULL, taking this approach can fix the issue through your entire app without cluttering your code.

Steve Schoon
+5  A: 

I always found it clear, concise, and problem free using a version of the If/Else check, only with the ternary operator. Keeps everything on one row, including assigning a default value if the column is null.

So, assuming a nullable Int32 column named "MyCol", where we want to return -99 if the column is null, but return the integer value if the column is not null:

return row["MyCol"] == DBNull.Value ? -99 : Convert.ToInt32(Row["MyCol"]);

It is the same method as the If/Else winner above - But I've found if you're reading multiple columns in from a datareader, it's a real bonus having all the column-read lines one under another, lined up, as it's easier to spot errors:

Object.ID = DataReader["ID"] == DBNull.Value ? -99 : Convert.ToInt32(DataReader["ID"]);
Object.Name = DataReader["Name"] == DBNull.Value ? "None" : Convert.ToString(DataReader["Name"]);
Object.Price = DataReader["Price"] == DBNull.Value ? 0.0 : Convert.ToFloat(DataReader["Price"]);
Meff
A: 

If you are concerned with getting DBNull when expecting strings, one option is to convert all the DBNull values in the DataTable into empty string.

It is quite simple to do it but it would add some overhead especially if you are dealing with large DataTables. Check this link that shows how to do it if you are interested

dDejan
Someties it is important to know the difference between a Null and an empty string though. For example setting a value to an empty string as opposed to a value just never being set.
Mykroft
+2  A: 

Brad Abrams posted something related just a couple of days ago http://blogs.msdn.com/brada/archive/2009/02/09/framework-design-guidelines-system-dbnull.aspx

In Summary "AVOID using System.DBNull. Prefer Nullable instead."

And here is my two cents (of untested code :) )

// Or if (row["fooColumn"] == DBNull.Value)
if (row.IsNull["fooColumn"])
{
   // use a null for strings and a Nullable for value types 
   // if it is a value type and null is invalid throw a 
   // InvalidOperationException here with some descriptive text. 
   // or dont check for null at all and let the cast exception below bubble  
   value = null;
}
else
{
   // do a direct cast here. dont use "as", "convert", "parse" or "tostring"
   // as all of these will swallow the case where is the incorect type.
   // (Unless it is a string in the DB and really do want to convert it)
   value = (string)row["fooColumn"];
}

And one question... Any reason you are not using an ORM?

Simon
We are using ORM now. At the time we weren't
Mykroft
A: 

DBNull implements .ToString() like everything else. No need to do anything. Instead of the hard cast, call the object's .ToString() method.

DataRow row = ds.Tables[0].Rows[0];
string value;

if (row["fooColumn"] == DBNull.Value)
{
   value = string.Empty;
}
else 
{
   value = Convert.ToString(row["fooColumn"]);
}

this becomes:

DataRow row = ds.Tables[0].Rows[0];
string value = row.ToString()

DBNull.ToString() returns string.Empty

I would imagine this is the best practice you're looking for

This doesn't work if the value isn't a string though. I was looking for a general case answer.
Mykroft
I agree yeah it's good if it's strings keeps it simple but other types wouldn't work e.g. tryng to do bool.Parse(row["fooColumn"].ToString()).
PeteT