August 2, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Better Entities with Nullable Types

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

Reading Entities Using Reflection

Writing code to read straightforward entities is about as boring as programming gets (other than writing comments). To speed up development time and relieve boredom, you can combine Generics, Reflection, and custom attributes and write a universal entity and entity list builder.

Listing 7 contains a one-size fits-all data access class. As it is, this class will only build entities and lists of entities, but it will build any entity. If you externalize the connection string and use the System.Data ADO.NET interfaces, this same code will build entities and list of entities for almost any database and any table.

Listing 7: A generic DataAccess class that uses Generics, custom attributes, and Reflection to read almost any entity.

Public Class DataAccess
   Private Const connectionString As String = _
      "Data Source=localhost;Initial Catalog=Northwind;" + _
      "Integrated Security=True"

   Public Shared Function GetList(Of T As New, _
      R As New)(ByVal tablename As String) As T

      Using connection As SqlConnection = _
         New SqlConnection(connectionString)
         Dim command As SqlCommand = _
            New SqlCommand("SELECT * FROM " + tablename, connection)
         connection.Open()
         Dim reader As SqlDataReader = command.ExecuteReader
         Return Create(Of T, R)(reader)
      End Using

   End Function
   Public Shared Function GetEmployeeList() As EmployeeList
      Return GetList(Of EmployeeList, Employee)("Employees")
   End Function

   Private Shared Function Create(Of T As New, U As New)( _
      ByVal reader As SqlDataReader) As T

      Dim list As IList(Of U)
      Dim gt As T = New T()
      list = gt

      While (reader.Read())
         list.Add(Create(Of U)(reader))
      End While

      Return list
   End Function

   Private Shared Function Create(Of U As New)( _
      ByVal reader As SqlDataReader) As U

      Dim o As U = New U()

      ' get the attributes and use them to read
      Dim type As Type = GetType(U)
      Dim properties() As PropertyInfo = type.GetProperties()

      ' for each field if it as a field descriptor we can read it
      For Each p As PropertyInfo In properties

         Dim attributes() As Object = _
            p.GetCustomAttributes(GetType(FieldDescriptorAttribute), _
               False)

         For Each attr As Object In attributes

            If (TypeOf attr Is FieldDescriptorAttribute) Then
               Dim descriptor As FieldDescriptorAttribute = _
                  CType(attr, FieldDescriptorAttribute)

               Dim method As MethodInfo = _
                  GetType(DataAccess).GetMethod("SafeRead")
               method = method.MakeGenericMethod(descriptor.FieldType)
               Dim val As Object = method.Invoke(Nothing, _
                  New Object() _
                  {descriptor.FieldName, reader, _
                   descriptor.DefaultValue})
               p.SetValue(o, val, Nothing)
               Exit For

            End If
         Next
      Next

      Return o
   End Function

   Public Shared Function SafeRead(Of T)(ByVal fieldname As String, _
      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

End Class

The first function GetList requires two parameterized arguments, T and R, that implement a blank constructor, Sub New. T is the entity list type, like EmployeeList; U is the entity type, like Employee. The GetList shared function requires the table name to read from.

The second shared method, Create, organizes constructing the list, iterating over the reader, and constructing the entities that end up in the list. The third method does all of the heavy lifting; it creates an instance of the entity class indicated by the parameter U. Next, you get all of the properties for the entity and request the FieldDescriptorAttribute for the properties. (Whew!) For each property, you use the FieldDescriptorAttribute, and construct an instance of the generic method SafeRead using the FieldDescriptorAttribute's knowledge about each field to initialize the field.

Some wisenheimer is going to write and talk about performance with all of this Reflection. Performance might be slightly slower, but hardware is cheap; labor is not. If you find performance isn't where it needs to be, you can always implement code that doesn't use Reflection, but make sure the performance really matters and is really noticeably slower. To test the code, you can use the Main sub routine shown in Listing 8.

Listing 8: A simple console application to test the code.

Imports System.Data
Imports System.Data.SqlClient
Imports System.Threading
Imports System.Reflection

Module Module1

   Sub Main()

      Dim emp As Employee = New Employee
      emp.BirthDate = Nothing

      Dim list As EmployeeList = DataAccess.GetEmployeeList
      For Each e As Employee In list
         Console.WriteLine(e)
      Next

      Console.ReadLine()

      For Each  c As Customer In DataAccess.GetList( _
         Of CustomerList, Customer)("Customers")
         Console.WriteLine(c)
      Next

      Console.ReadLine()

   End Sub
      ... ' elided

Inheriting from List(Of T)

It isn't necessary to create a CustomerList that inherits from List(Of Customer) or to create any subtype. A reason to do it is to support adding additionally capabilities to the new List class. In real programming, you will need helper methods and these can be added only to classes you create.

Note: With Orcas—the next version of .NET—extension methods are supported. Extension methods permit adding capabilities to an existing type. To that end, you may be able to use List(Of Customer), for example, without inheritance and add new capabilities using extension methods. Check back here for more on extension methods in coming weeks.

Summary

There are a lot of meaty bits in this article. You can use Nullable types to make fields and properties accept nulls. You can use custom attributes, Generics, and Reflection to write a one-size fits-all entity populating engine. Whether you can employ one or all of these techniques on a project depends on your judgment, but if you can, you will eliminate writing a lot of mundane code.

It is important to acknowledge that many people use ADO.NET and DataSets. This approach can work, but I seldom use it. I prefer the control provided by custom classes. It is also important to note that the impedance mismatch between normalized tables and rows and actual objects is a pickle that Microsoft is trying to solve. Check back later for more on the ADO.NET Entity Framework; we'll see what Redmond comes up with together.

About the Author

Paul Kimmel is the VB Today columnist for www.codeguru.com and has written several books on object-oriented programming and .NET. Check out his new book UML DeMystified from McGraw-Hill/Osborne. Paul is a software architect for Tri-State Hospital Supply Corporation. You may contact him for technology questions at pkimmel@softconcepts.com.

If you are interested in joining or sponsoring a .NET Users Group, check out www.glugnet.org.

Copyright © 2007 by Paul T. Kimmel. All Rights Reserved.





Page 3 of 3



Comment and Contribute

 


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

 

 


Sitemap | Contact Us

Rocket Fuel