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.
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
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