views:

352

answers:

6

Let's say I have a custom class like this one:

public class Customer    
{        
 public int CustomerID { get; set; }        
 public string CompanyName { get; set; }        
 public string BusinessAddress { get; set; }        
 public string Phone { get; set; } 
 public int ParentID { get; set; } 
}

I create custom objects from the database using a datareader. Ex:

  while (dr.Read())
  {
    listCustomers.Add(new Customer(
          Convert.ToInt32(dr["CustomerID"]),
          Convert.ToString(dr["CompanyName"]),
          Convert.ToString(dr["BusinessAddress"]),
          Convert.ToString(dr["Phone"]),
          Convert.ToInt32(dr["ParentID"]),
)

ParentID can be null in the database (and I can't change it). When it's null, the conversion obviously fails.

How should I handle null values retrieved from the DB to populate my business objects? Would it be good pratice to use Nullable Types in my custom class? Any other tips?

+6  A: 

Absolutely. Nullable types are perfectly fine. Otherwise, you'd have to come up with some stupid convention a-la "when ParentID is -1, this means that Customer has no parent". Nullable types enforce this "by design": if there's no parent, ParentID will be null.

As for hydrating your objects, consider using ORM tools (such as NHibernate or BLToolkit), since you don't really want to spend 50% of your development tine writing SQL queries and populating your objects from a data reader

Anton Gogolev
+2  A: 

How about adding helpers like this:

 static string safeGetStringFromDB( IDataReader dr, string strField )
 {
  int nIndex = dr.GetOrdinal( strField );
  if ( dr.IsDBNull( nIndex ) )
   return string.Empty;
  return dr.GetString( nIndex );
 }
danbystrom
+1  A: 

You can create such helper methods:

     static T Map<T>(object obj, Func<object, T> map, T def)
 {
  if (obj != null)
  {
   return map(obj);
  }
  return def;
 }

 static T Map<T>(object obj, Func<object, T> map)
 {
  return Map<T>(obj, map, default(T));
 }

And use them like:

      object o = 1;
  var t = Map(o, Convert.ToInt32, 0); // with default value
  var t2 = Map(o, Convert.ToInt32); // or without default value
Dzmitry Huba
Too much code just to check a NULL man.
Shiva
A: 

ParentID is a string in your class and you are converting it to an Int32.

But that aside: You know the database, so you know what fields can be null. I would make all of these fields nullable in the data object too, because in my opinion this is the best way to model the database as close as possible.

Maximilian Mayerl
+2  A: 

Nullable types are fine for nullable fields.

Handle the assignment with a ternary :-

ParentID = ( dr["ParentID"] is DBNull ) ? null : (int)dr["ParentID"];

Also, you don't need to use Convert if you know what your underlying db fields are and their .NET representations.

I restrict use of convert to situations where I know the types are different ( say int to string ).

When I know the .NET corresponding type, I'll simply cast :-

listCustomers.Add(new Customer(
          (int)dr["CustomerID"],
          (string)dr["CompanyName"],
          (string)dr["BusinessAddress"],
          (string)dr["Phone"],
          ( dr["ParentID"] is DBNull ) ? null : (int)dr["ParentID"]));

Finally, when using nullable types, be aware that overloading a method can be a potential problem.

Consider the following examples :-

public void Populate( int? facilityId, string name, bool? somethingElse )...
public void Populate( string facilityCode, string name, bool? somethingElse )...

Same number of arguments, all params potentially null. When the first parameter is null, you might be surprised on the interpretation your compiler eventually picks. Has caught me out before, anyway.

Paul Alan Taylor
Thanks! I think that the last part should be (int?)dr["ParentID"] instead of (int)dr["ParentID"], otherwise the compiler will complain.
Max
+3  A: 

Use nullable types in your business objects. You could then use a generic helper method like this to pull the nullable fields from the database.

public static T? GetValue<T>(IDataRecord record, string columnName)
    where T : struct
{
    int columnIndex = record.GetOrdinal(columnName);
    if (record.IsDBNull(columnIndex))
     return null;
    else
     return (T)Convert.ChangeType(record[columnIndex], typeof(T));
}

So if you ParentId is declared as int? you would do the following when loading a row.

obj.ParentId = GetValue<int>(dr, "ParentId");
batwad