Creating a datarow to object mapper

I was working on a small project the other day when I starting to think about using a mapping layer to map a datarow object to a strongly typed object and how hard it would be. For instance if you have a Customer object that has CustomerId, FirstName, LastName and PhoneNumber. You can convert a datarow into a Customer object by using this code:

Public Class Customer
    Private mCustomerId As Integer
    Private mFirstName As String
    Private mLastName As String
    Private mPhoneNumber As String
 
    Public Property CustomerId() As Integer
        Get
            Return mcustomerId
        End Get
        Set(ByVal value As Integer)
            mCustomerId = value
        End Set
    End Property
 
    Public Property FirstName() As String
        Get
            Return mFirstName
        End Get
        Set(ByVal value As String)
            mFirstName = value
        End Set
    End Property
 
    Public Property LastName() As String
        Get
            Return mLastName
        End Get
        Set(ByVal value As String)
            mLastName = value
        End Set
    End Property
 
    Public Property PhoneNumber() As String
        Get
            Return mPhoneNumber
        End Get
        Set(ByVal value As String)
            mPhoneNumber = value
        End Set
    End Property
 
    Public Sub New(ByVal customerId As Integer, ByVal firstName As String, ByVal lastName As String, ByVal phoneNumber As String)
        mCustomerId = customerId
        mFirstName = firstName
        mLastName = lastName
        mPhoneNumber = phoneNumber
    End Sub
End Class
 
Public Class CustomerMapper
    Public Function MapToObject(ByVal dr As DataRow) As Customer
        Return New Customer(Convert.ToInt32(dr("customerid")), dr("firstName"), dr("lastName"), dr("phonenumber"))
    End Function
End Class

I was pondering the thought of having to write a mapper class for each class that I could potentially have in a project and started to wince a little bit. Especially where I have a class with 10 or more properties. Yikes!
Now I realize there are MANY MANY MANY object relation mappers but I wanted something lightweight and could not use LINQ TO SQL (VS 2005 project) easily.

So I came up with another way to do this using reflection, attributes and generics.

Here is the actual mapping code (The format was messed up when it was copied to my blog editor):

Public Shared Function MapDatarowToObject(Of T)(ByVal dr As DataRow) As T
If dr Is Nothing Then
Return Nothing
End If
 
Dim instance As T = Activator.CreateInstance(Of T)()
 
Dim properties() As PropertyInfo = instance.GetType().GetProperties()
 
If (properties.Length > 0) Then
For Each propertyObject As PropertyInfo In properties
 
      Dim valueSet As Boolean = False
 
For Each attributeObject As Object In PropertyObject.GetCustomAttributes(False)
                  
 If attributeObject.GetType() Is GetType(MapperColumn) Then
Dim columnAttributeObject As MapperColumn = CType(attributeObject, MapperColumn)
 
If (columnAttributeObject.ColumnName <> String.Empty) Then
 
If dr.Table.Columns.Contains(columnAttributeObject.ColumnName) AndAlso
Not dr(columnAttributeObject.ColumnName) Is DBNull.Value Then
                       
propertyObject.SetValue(instance, dr(columnAttributeObject.ColumnName), Nothing)
           
valueSet = True
           
End If
 
            End If
End If
Next
 
                If Not valueSet Then
                    If dr.Table.Columns.Contains(propertyObject.Name) AndAlso Not dr(propertyObject.Name) Is DBNull.Value Then
                        propertyObject.SetValue(instance, dr(propertyObject.Name), Nothing)
                    End If
                End If
 
            Next
        End If
 
        Return instance
    End Function

Now I need to explain it. Let’s start with the method definition:

Public Shared Function MapDatarowToObject(Of T)(ByVal dr As DataRow) As T

I’m creating a method that needs to be called by defining a type for “T”. The only parameter is a datarow. The use is something like this:

Example #1
Dim dr As DataRow = GetDataRowFromTable(…)
Dim customer As Customer = MapDatarowToObject(Of Customer)(dr)

Next I need to create an instance of the generic type used to call the method (T). This is done by using Activator.CreateInstance():

Dim instance As T = Activator.CreateInstance(Of T)()
 
Using Example #1 this call will create an instance of the Customer object. This is the because the Customer class was used as the type for “T”. I need this instance to hydrate the properties on the object.
 
The next step is to get a list of the properties defined on T. This is done by using the instance we just created on the previous line of code and using reflection to get an array of PropertyInfo objects:

Dim properties() As PropertyInfo = instance.GetType().GetProperties()

In our case we will get back four properties: CustomerId, FirstName, LastName & PhoneNumber.

Now that we have our properties we need to iterate through them and do some mapping.
 
Basically how this works is if we have a column in the datarow that matches a property in the instance object (Customer) then the property value gets set. I quickly realized that sometimes the datarow column name does not match the name of a property in the instance object. The solution was to create an attribute that can be read while iterating the properties. The attribute provides a column name to map to the property. Here is the attribute class:

<AttributeUsage(AttributeTargets.Property)> _
Public Class MapperColumn
    Inherits Attribute
 
    Private mColumnName As String
 
    Public Sub New(ByVal columnName As String)
        mColumnName = columnName
    End Sub
 
    Public Property ColumnName() As String
        Get
            Return mColumnName
        End Get
        Set(ByVal value As String)
            mColumnName = value
        End Set
    End Property
End Class
 
You’ll notice that I restrict the usage to only properties. This is because ONLY properties can be set by using reflection. Public fields cannot be set using reflection.

The usage of the MapperColumn attribute if easy.

<MapperColumn(“id”)> _
Public Property CustomerId
End Property
 
This will allow the column name “ID” from a datarow object to be mapped to the CustomerId property.

Let’s break down the properties iteration. We start by iterating the properties:

For Each propertyObject As PropertyInfo In properties
 
Next we check for any custom attributes on the current property:

For Each attributeObject As Object In propertyObject.GetCustomAttributes(False)
 
 If any are found then we check to see if they are of type MapperColumn:

If attributeObject.GetType() Is GetType(MapperColumn) Then
 
If it is a MapperColumn then we get the instance of the MapperColumn:

Dim columnAttributeObject As MapperColumn = CType(attributeObject, MapperColumn)
 
Now we can use the MapperColumn attribute class to get the name of the datarow column we should be mapping to the current property. I do a little checking to make sure the ColumnName property from the MapperColumn object isn’t empty and make sure that the datarow has a column that matches the ColumnName property from the MapperColumn object. If it does, we set the value using PropertyInfo.SetValue.

If (columnAttributeObject.ColumnName <> String.Empty) Then
If dr.Table.Columns.Contains(columnAttributeObject.ColumnName) AndAlso Not dr(columnAttributeObject.ColumnName) Is DBNull.Value Then
     
propertyObject.SetValue(instance, dr(columnAttributeObject.ColumnName), Nothing)
            valueSet = True
End If
End If
 
We also set the local variable “valueSet” Boolean to true. This is to prevent the code below from trying to set the value based on the name of current property. As a side note, if you do add the MapperColumn attribute to a property and the above code does not set the value, the code below will attempt to set the value based on the current property name.
 
If the value of the property isn’t set using the MapperColumn block of code then we will check if a column name in the datarow matches the current property name. If it does, we set the value using PropertyInfo.SetValue.
 
If Not valueSet Then
If dr.Table.Columns.Contains(propertyObject.Name) AndAlso Not dr(propertyObject.Name) Is DBNull.Value Then
propertyObject.SetValue(instance, dr(propertyObject.Name), Nothing)
End If
End If
The only thing left to do is return the instance of the object we created and hydrated.
And that is basically it. We now can map custom model objects to datarow objects using a lightweight method. I realize that this could be refactored to a few method but that's not the point of the post.

My next post will show you how to convert a datatable to a list of custom objects.
 
Until next time.

posted @ Thursday, April 24, 2008 11:04 AM

Print

Comments on this entry:

# re: Creating a datarow to object mapper

Left by Magnus at 7/2/2008 5:34 AM
Gravatar
Great job! I've been looking for the exact same thing and began to write something similiar to your code. OR mappers seems to grow and their framework is huge today.

# re: Creating a datarow to object mapper

Left by Bill at 7/14/2008 8:09 PM
Gravatar
Great article, thanks! I am looking forward to your next post, "My next post will show you how to convert a datatable to a list of custom objects."

# re: Creating a datarow to object mapper

Left by Brendan at 9/1/2008 1:49 AM
Gravatar
Excellent work. Saved me a few hours of coding and worked without a glitch.

# re: Creating a datarow to object mapper

Left by Hugo at 10/10/2008 5:34 PM
Gravatar
Excellent, but, if I understood correctly, a method
"MapObjectToDatarow" is missing. Regards.

# re: Creating a datarow to object mapper

Left by Nick Nowinski at 2/23/2009 5:53 PM
Gravatar
I'm trying to use a class in place of a property and need help with getting the Reflection correct.

Here is a very simple example of what I'm doing:


Public Class Contact

Dim _contactID As zyp_ContactID

Public Sub New()

End Sub

Public Property ContactID() As Guid
Get
Return _contactID.value
End Get
Set(ByVal value As Guid)
_contactID.value = value
End Set
End Property


Public Class zyp_ContactID

Protected Friend Sub New()

End Sub

Dim _contactID As Guid

Public Property value() As Guid
Get
Return _contactID
End Get
Set(ByVal value As Guid)
_contactID = value
End Set
End Property


End Class

End Class

In my previous work I've always just had public properties and used Refelection to hydrate from the database. This allowed me to have a Services class that handled all the hydration through a DatabaseHelper class. There was only one class for each business object that handled hydrating and only one class for the whole program which interfaced to the database. Switching to a different database or even to XML or Web Services was just a one-class change (at least as far as the application code was concerned).

I've run into some real limitations with Properties and creating subclasses appears to be the solution.

I previously used code above to map from a database to a custom business object. So long as I used the attribute it has worked perfectly.

So now I need help with the following:

For each property in the class

1. Is the property a class ?
2. If so, does the class have a property of "value" ?
3. If so, can we write to the property "value" ?
4. If so, write to the property "value".

Any hints are greatly appreciated...I'm stuck on this and hate the idea of not being able to use a generic mapper class.

As an alternative, is there a way to pass a reference to a function to a sub so that the sub can call the function referenced. This is a backup option if I can't get the generic mapper class to work.

# re: Creating a datarow to object mapper

Left by Edi at 10/26/2009 8:29 AM
Gravatar
Hi
I wonder if you have the same code written in C#.
I want to convert this code but I got some sintax errors.

Thanks!!

# re: Creating a datarow to object mapper

Left by Fred Mastro at 12/2/2009 9:38 AM
Gravatar
Great post! Just what I was looking for. In the process of altering it a little to auto cast the dr(column) type to the property type.

# re: Creating a datarow to object mapper

Left by Ecommerce design at 12/30/2009 3:56 PM
Gravatar
I must say, great stuff!

# re: Creating a datarow to object mapper

Left by Chalito Ecu at 1/28/2010 12:13 AM
Gravatar
Great post! Very good, I will use this idea for a project I'm working on and will try to extend it to handle primary and foreign keys and translate it to C#.

Thanks a lot!

Your comment:



 (will not be displayed)


 
 
 
Please add 4 and 8 and type the answer here:
 

Live Comment Preview:

 
«March»
SunMonTueWedThuFriSat
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910