.NET 2.0 Generics - Load A Business Class In A Clueless Database Layer

By Robbe D. Morris

Printer Friendly Version

Robbe Morris
Robbe & Melisa Morris
  Download C# Source Code
Should the database layer of our application have knowledge of a business class in our business rules layer?  In pure object oriented environments, the answer is no.  This restriction does create some issues with performance though.  For instance, if your business class inherits a data class object (class with only properities and no business rules associated with it), you may want your database layer to populate this business class directly to avoid the overhead of first populating a List or Array of data classes in the database layer and then creating a new List or Array of business classes with the data class properties copied over or the business class references the data class as its own property.
In the end, our only reason for adding this extra performance overhead is to maintain a strict separation between the two layers even though we aren't letting developers call methods of the business classes in the database layer as a matter of policy.  We only need the database layer to have knowlege of the business object so it can be populated in the most efficient manner possible.
In order to meet performance expectations, I've often opted to bend this rule and allow the database layer to be aware of this business layer class.  However, .NET 2.0 Generics provides us with a way to stay truer to object oriented principles without the extra performance overhead mentioned above.  These types of debates remind of the separation of church and state arguments.  Just how separate must they be?  We'll leave that discussion for another day.
Using where and new() Generic constraints, we can allow the database layer to return a List of data class objects into a List of business objects in the business layer that inherits that same data class object.  The one caveat is that the business object must expose a constructor that has no parameters.  As a result, the business layer thinks its getting a List of business objects and the database layer thinks its returning a List of data class objects.  With Generics, both are true.
Let's review the small code sample below to get a better idea of how this can work.


 
Program.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics; 
using BusinessRules;

namespace TestApplication
{
  class Program
  {
    static void Main(string[] args)
    {
 
      // Notice that this is the BusinessRules.Country class
      // and not the DataObjects.Country class.

      List<Country> records = null;
             
      try
      {
                 
         Console.WriteLine("start");

         // Notice that we are expecting the database layer
         // to return BusinessRules.Country List even
         // though it doesn't know what a BusinessRules.Country
         // class is.

         // Since BusinessRules.Country at some level inherits
         // DataObjects.Country, the database layer will only
         // be able to recognize the DataObjects.Country portion
         // of our BusinessObjects.Country class.  The database
         // layer can thus only populate those properties that
         // exist in the DataObjects.Country class which
         // is exactly what we need.

         records = DataBase.GetCountryList.ToList<Country>();
                 
         for (int i = 0; i < records.Count; i++)
         {
            Console.WriteLine(records[i].CountryID.ToString());
            Console.WriteLine(records[i].Description);
            Console.WriteLine(records[i].MyMethod());
         }
                 
         Console.WriteLine("success");
      }
      catch (Exception e) { Debug.WriteLine(e.Message); }
      finally 
      {
         Console.WriteLine("done");
         Console.ReadLine(); 
      }
   }
 }
 
} 
BusinessRules.Country.cs
using System;
using System.Collections.Generic;
using System.Text;
using DataObjects;

namespace BusinessRules
{
    // Notice the inheritance of the DataObjects.Country class.
    // We can also add properties to our BusinessRules.Country
    // class if needed.

    class Country : DataObjects.Country
    {
        public Country()
        {
           // Needed for database layer and Generic
           // new() constraint.
        }

        public Country(string someParameter)
        {
            // You can also have parameters
            // in your constructor.  Keep in mind,
            // this constructor will NOT be executed
            // when this object is created in the
            // database layer.
        }

        public string MyMethod()
        {
            return "result from MyMethod: " + this.Description; 
        }

    }
}
DataBase.GetCountryList
using System;
using System.Collections.Generic;
using System.Text;
using DataObjects;

namespace DataBase
{
    class GetCountryList
    {
 
        // This is the key area to be aware of.  The Generic
        // constraint is formed in a classic style where clause.  In this
        // case we are saying we will return a List to any return
        // variable that either is DataObjects.Country or inherits
        // from it.  
		
        // The new() clause says that DataObjects.Country
        // must have at least one parameterless constructor.  This
        // allows us to create new instances of DataObjects.Country
        // in our database layer.

        public static List<T> ToList<T>() where T : Country, new()
        {

            List<T> recordList = new List<T>();
            T record = null;

            // Simulate loading data from the database

            for (int i = 0; i < 10; i++)
            {
                record = new T();
                record.CountryID = i;
                record.Description = "Country" + i.ToString();
                recordList.Add(record);
            }

            return recordList;

        }

    }
} 
DataObjects.Country.cs
using System.Collections;
using System.Diagnostics; 
using System.Data;
using System.Data.SqlClient; 
using System.Data.SqlTypes; 
using System.Reflection;
using System.Xml.Serialization;
using System.IO;
using System.Collections.Generic;
using DataObjects;



namespace DataObjects
{

   // DataObjects.ColumnInfo is included in the
   // downloadable code sample.  It is just a bunch
   // of standard items I include in all my data classes.
   // It isn't relevant to this article.
 
   [Serializable]
   public class Country : DataObjects.ColumnInfo
   {
 
     private int _CountryID = 0;
     private string _CountryCode = "";
     private int _ActiveID = 0;
 
 
     [ColumnAttributes("CountryID")]
     public int CountryID
     {
       get 
       {
          return _CountryID;
       }
       set 
       { 
          if (value != _CountryID)
          { 
             _CountryID = value; 
             this.IsDirty = true; 
          }
       }
   }
 
   [ColumnAttributes("CountryCode")]
   public string CountryCode
   {
      get 
      {
         return _CountryCode;
      }
      set 
      { 
         if (value != _CountryCode)
         { 
            _CountryCode = value; 
            this.IsDirty = true; 
         }
      }
   }
 
		 
   [ColumnAttributes("ActiveID")]
   public int ActiveID
   {
     get 
     {
       return _ActiveID;
     }
     set 
     { 
       if (value != _ActiveID)
       { 
         _ActiveID = value; 
         this.IsDirty = true; 
       }
     }
   }
 
 }
 
} 

Robbe has been a Microsoft MVP in C# since 2004.  He is also the co-founder of NullSkull.com which provides .NET articles, book reviews, software reviews, and software download and purchase advice.