65.9K
CodeProject is changing. Read more.
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1 vote)

Jul 31, 2020

CPOL

2 min read

viewsIcon

4309

Map a DataReader from any database to a custom Class in your VB.NET Project and validate that content is "correct"

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