April 18, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Better Entities with Nullable Types, Page 2

  • April 25, 2007
  • By Paul Kimmel, Paul Kimmel
  • Send Email »
  • More Articles »

Implementing a SafeRead(Of T) Method

Generic classes and methods are now used all over the place in .NET. Generics are useful. Any time you can find a general algorithm that will work on a variety of types, use Generics.

In my daily work, I routinely use macros, snippets, CodeRush, and CodeDOM code generators. By establishing a pattern of completing a task, it makes it easier to automate that task, or at least do it very quickly. For this reason, the SafeRead(Of T) method was developed (by me).

SafeRead is implemented to accept a type, some kind of ADO.NET container such as a DataRow, a field name, and a default value. Then, SafeRead attempts to read the field as long as it isn't null. If the field contains a null, the default value is used. Instead of code that looks like this:

Dim v As Object = reader("EmployeeID")
Dim employeeID As Integer
If (v Is Nothing Or v Is System.DBNull.Value) Then
   employeeID = Convert.ToInt32(v)
Else
   employeeID = -1    ' default value
End If

repeated over and over, the checking code is converged into a single method and the workload is pleasantly reduced. Listing 4 contains my implementation of SafeRead(Of T).

Listing 4: A generic method named SafeRead that takes the grunt work out of reading field values and checking for null.

Public Shared Function SafeRead(Of T)(ByVal fieldname As String, _
   ByVal reader As SqlDataReader, ByVal defaultValue As T) As T

   Dim v As Object = reader(fieldname)
   If (v Is Nothing Or v Is System.DBNull.Value) Then
      Return defaultValue
   Else
      Return Convert.ChangeType(v, GetType(T))
   End If
End Function

First, you read the field. In this example, from a SqlDataReader. (You can extend SafeRead to accept an IDataReader, making SafeRead provider agnostic.) Then, you check for Nothing or DBNull. If the field is any variation of nothingness, the default value you provided is returned. If the data is good, you use Convert.ChangeType and the parameterized type T to convert to the correct data type.

It is worth noting here that VB permits you to get loosey goosey with data types, and you probably can get away with assigning the Object type from an indexed SqlDataReader to a specific type. However, it is worth noting that being specific is a more portable and reliable way to write code in general. To use SafeRead, call it with parameters that look like this:

Dim employeeID As Integer = _
   SafeRead(Of Integer)("EmployeeID", reader, -1)

Creating a Field Descriptor Attribute

Suppose that you want to eliminate some more overhead for building entity types. For example, you could use metadata to provide the field name, field type, and default value. Then, each entity's field can carry with it the information it needs for SafeRead. Listing 5 contains an implementation of a custom attribute named FieldDescriptorAttribute.

Listing 5: A custom attribute that can be used to assist with reading the entity fields.

<AttributeUsage(AttributeTargets.Property)> _
Public Class FieldDescriptorAttribute
   Inherits Attribute

   ''' <summary>
   ''' Initializes a new instance of the FieldDescriptorAttribute
   ''' class.
   ''' </summary>
   ''' <param name="fieldName"></param>
   ''' <param name="fieldType"></param>
   Public Sub New(ByVal fieldName As String, _
      ByVal fieldType As Type, ByVal defaultValue As Object)
      FFieldName = fieldName
      FFieldType = fieldType
      If (FFieldType Is GetType(DateTime)) Then
         FDefaultValue = DateTime.MinValue
      Else
         FDefaultValue = defaultValue
      End If
   End Sub

   Private FFieldName As String
   Public Property FieldName() As String
      Get
         Return FFieldName
      End Get
      Set(ByVal Value As String)
         FFieldName = Value
      End Set
   End Property

   Private FFieldType As Type
   Public Property FieldType() As Type
      Get
         Return FFieldType
      End Get
      Set(ByVal Value As Type)
         FFieldType = Value
      End Set
   End Property

   Private FDefaultValue As Object
   Public Property DefaultValue() As Object
      Get
         Return FDefaultValue
      End Get
      Set(ByVal Value As Object)
         FDefaultValue = Value
      End Set
   End Property
End Class

The custom attribute class FieldDescriptorAttribute is simply a container for arguments to SafeRead. The only noteworthy aspect is that I check for DateTime and set the default value in the custom attribute because I couldn't remember (or figure out) how to initialize an attribute with a literal Date. (If any of you smart readers know how to do this, drop me a line at pkimmel@softconcepts.com.) Having defined the FieldDescriptorAttribute, you can define a custom entity class (based on any table, but Employees was used) and adorn the property/entity fields with the attribute (see Listing 6).

Listing 6: An employee class that uses the FieldDescriptorAttribute, which can be used by SafeRead with Reflection.

Public Class Employee

   Private FEmployeeID As Nullable(Of Integer)
   <FieldDescriptor("EmployeeID", GetType(Integer), -1)> _
   Public Property EmployeeID() As Nullable(Of Integer)
      Get
         Return FEmployeeID
      End Get
      Set(ByVal Value As Nullable(Of Integer))
         If (Value.HasValue = False) Then
            Console.WriteLine("Employee ID is null")
         End If
         FEmployeeID = Value
      End Set
   End Property

   Private FLastName As String
   <FieldDescriptor("LastName", GetType(String), "")> _
   Public Property lastName() As String
      Get
         Return FLastName
      End Get
      Set(ByVal Value As String)
         FLastName = Value
      End Set
   End Property

   Private FFirstName As String
   <FieldDescriptor("FirstName", GetType(String), "")> _
   Public Property FirstName() As String
      Get
         Return FFirstName
      End Get
      Set(ByVal Value As String)
         FFirstName = Value
      End Set
   End Property

   Private FBirthDate As Nullable(Of DateTime)
   <FieldDescriptor("BirthDate", GetType(DateTime), Nothing)> _
   Public Property BirthDate() As Nullable(Of DateTime)
      Get
         Return FBirthDate
      End Get
      Set(ByVal Value As Nullable(Of DateTime))
         FBirthDate = Value
      End Set
   End Property

   Private FPhoto As Byte()
   <FieldDescriptor("Photo", GetType(Byte()), New Byte() {0})> _
   Public Property Photo() As Byte()
      Get
         Return FPhoto
      End Get
      Set(ByVal Value As Byte())
         FPhoto = Value
      End Set
   End Property


   Public Overrides Function ToString() As String
      Return String.Format("Employee: {0} {1}, {2} is in {3}", _
         FEmployeeID, FLastName, FFirstName, FRegion)
   End Function

   Private FRegion As String
   <FieldDescriptor("Region", GetType(String), "(unk)")> _
   Public Property Region() As String
      Get
         Return FRegion
      End Get
      Set(ByVal Value As String)
         FRegion = Value
      End Set
   End Property

End Class




Page 2 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel