Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Generic Mapper and Validator from DataReader to Custom Class in VB.NET

0.00/5 (No votes)
31 Jul 2020 1  
Map a DataReader from any database to a custom Class in your VB.NET Project and validate that content is correct
Usually, when developing DB layer in VB.NET, there is the problem of executing a query and mapping the returned fields to an Object. This will allow to avoid the need to map the fields one by one. I will also show you how to write several rules to automatically validate the data after mapping.

Introduction

If you have written several VB.NET applications that interact with the DB, you will be familiar with the problem of either using the DataReader directly into your code, or manually map each of the returned fields into a property into your object.

Background

This article takes advantage of the following concepts to implement a generic solution:

  • Modules & Extensions
  • Annotations
  • Reflection

In my example, I will use OracleDataReader from Oracle.ManagedDataAccess.Client, but this approach will work with any DataReader.

Using the Code

In this example, we will map an entity "Player" with Names, Email and Phone.

So let's start by creating our Player Class.

'This import will allow us add the needed annotations for Validation
Imports System.ComponentModel.DataAnnotations

Public Class Player

    <Required(ErrorMessage:="{0} is Mandatory")> _
    Property LastName As String
    <Required(ErrorMessage:="{0} is Mandatory")> _
    Property FirstName As String
    Property MiddleName As String

    <Phone> Property Phone As String
    <EmailAddress> Property EMail As String
End Class

As you can see, there are a few annotations (<>) that will provide the validator with additional information on what to check.

<Required(ErrorMessage:="{0} is Mandatory")> will mark the next property as mandatory and provides an error message in case of failed validation.
<Phone> or <EmailAddress> will provide the validator a standard format to expect.

In this example, the validator will only report the errors to the console upon object's creation. But this can obviously be changed.

Next, we will read the data from the database, in the standard method that we usually use.

sqlstr = "select * from player"
cmd = New OracleCommand(sqlstr, conn)
result = cmd.ExecuteReader()
result.Read()

Let's say that result will contain (being Oracle), fields like result("LAST_NAME"), result("PHONE"), etc.
We have created our Player class in a way that the property names MATCH the field names in the database, but correcting the case as we will see below.

Now, we will implement our Mapper and Validator.
First, we need to add a Module to our project, in this case, I've called it ReflectionHelper. And in this module, we will extend our DataReader class with a new function called Map that will receive an Object that will be where the query fields will be mapped to:

Imports System.Runtime.CompilerServices
Imports Oracle.ManagedDataAccess.Client  'to know OracleDataReader
Imports System.Globalization
Imports System.Reflection
Imports System.ComponentModel.DataAnnotations

Module ReflectionHelper
    <Extension>
    Public Sub Map(rdr As OracleDataReader, Dest As Object)
        Dim Name As String
        Dim DestType As Type = Dest.GetType
        Dim Prop As PropertyInfo

        For i = 0 To rdr.FieldCount - 1
            If Not IsDBNull(rdr(i)) Then
                Name = rdr.GetName(i)
                Name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase
                       (Name.Replace("_", " ").
                       Replace("-", " ").ToLower).Replace(" ", "")
                Prop = DestType.GetProperty(Name)
                If Prop IsNot Nothing Then
                    Try
                        Prop.SetValue(Dest, rdr(i))
                    Catch ex As Exception
                        Console.WriteLine("Map to " & DestType.Name & 
                                          " Field " & Name & ": " & ex.Message)
                    End Try
                End If
            End If
        Next

        Dim vc = New ValidationContext(Dest)
        Dim vr = New List(Of ValidationResult)
        Dim valid = Validator.TryValidateObject(Dest, vc, vr, True)
        If Not valid Then
            For Each err As ValidationResult In vr
                Console.WriteLine(err.MemberNames.First & " -> " & err.ErrorMessage)
            Next
        End If
    End Sub

End Module

So this map function is divided in two parts.

The first part will loop in every field, obtain its name and correct the case from LAST_NAME to LastName.
Then it will look for the property in the Object and if it exists, it will try to assign the data. The Try/Catch was added in case of type issues.

The second part will take the Validation Rules added in the class definition and run the validation on the object instance and log to console any issues found during object creation. (In our example, the object will be created anyhow.)

Finally, to use the code in your source, simply add:

Dim p As New PKPlayer
result.Map(p)

I truly hope you find this tip useful.

History

  • 30th July, 2020: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here