Designing a Generic Database Layer

Some people like to automatically generate their database tier. Personally, I had enough bad experiences with ORM tools that I try to avoid them. After all, data tier code can be knocked out pretty quickly and it's always nice to know you didn't take any shortcuts. It's been a while since I've seen data tiers discussed, so I figured I'd pass along a modern approach that I have found works quite well and results in really clean data tiers.

Very early in your programming days, you learn that when separating an application into logical tiers, it is important that lower tiers do not couple themselves with higher tiers. As coupling between the tiers is reduced, your code gains flexibility. The entire data tier should be able stand on its own. If later you need to completely revamp your domain model or business layer, the data tier should be able to remain untouched.

First, consider a simple create operation inside a database class.

One option is to simply pass a data row to your database class and let it handle the update. There are a few problems with this approach. First, DataSets aren't exactly a high performance solution and have a lot of overhead. Additionally, a simple DataRow doesn't have a defined set of columns. As a result, you can't use refactoring operations should the column name change, nor can you use the compiler to check argument types or counts.

Another option is to pass an entity object directly to your database class. This is slightly better, as it ensures type safety and supports IDE features such as refactoring and auto-complete. However, this approach has a few flaws of its own.  You can't tell by looking at the method which properties are required to create a customer.  You can't tell if the data tier assigns the customer an ID, or if the ID must the ID be set before you pass the customer in. Additionally, now your data tier is being coded with explicit knowledge about the layout of your domain model. Since the domain model lives above the data tier, this should not happen.

A third approach is to have a method which takes all the parameters required to create a record in the database and return information about the results of the database call, such as an ID from an identity column. By going this route, you keep the coupling between the two tiers low, support compile time checking, and explicitly encode information about inputs and outputs of your method in its signature

Bad
public class CustomerDB
{
  public void CreateCustomer(DataRow row) { … }
}
Better
public class CustomerDB
{
  public void CreateCustomer(Customer c) { … }
}
Best
public class CustomerDB
{
  public int CreateCustomer(string firstName, string lastName, string company, string phoneNumber) { … }
}

Create, update, and delete are easy to handle in this fashion. But how do we handle query operations?

Again, one option is to work with DataSets or DataTables. While this gets the job done, you again incur a needless performance penalty by loading things into temporary tables before the data is loaded into entity objects. Another option is to pass back entity objects from your data tier. Again, however, this couples your data tier to your object model and is bad for the same reasons as it was bad in your create and update methods.

An alternate approach, which utilizes delegates and generics, eliminates the coupling between the tiers as well as the performance problems associated with datasets. Because this approach is new, I have included more complete code, illustrating how the different layers can work together.

Bad

public class CustomerDB
{
  public Customer GetCustomer(int id) { … }
  public Collection<Customer> GetCustomers(string company) { … }
}


Better
public class CustomerDB
{
  public DataRow GetCustomer(int id) { … }
  public DataTable GetCustomers(string company) { … }
}

public class Customer
{
  public static Customer CreateFrom(DataRow row)
  {
    ...
  }
}

Best
public delegate T CreateEntity<T>(IDataReader reader);public delegate void AddToList<T>(T item); public class CustomerDB<TCustomer>
{
  public TCustomer GetCustomer(int id, CreateEntity<TCustomer> create)  
  {
    using(SqlDataReader reader = ExecuteStoredProc(“GetCustomer”, id))
    {
      if(reader.Read()) return create(reader);
      else return default(TCustomer)
    }
  }
  public void GetCustomers(string company, CreateEntity<TCustomer> create, AddToList< TCustomer> addToList)
  {
    using(SqlDataReader reader = ExecuteStoredProc(“GetCustomersByCompany”, company);
    {
      while(reader.Read())
      {
        addToList(create(reader));
      }
    }
  }
}
 public class Customer
{
  public static Customer CreateFrom(IDataReader reader)
  {
   
  }
}

public class CustomerManager
{
  CustomerDB<Customer> _db = new CustomerDB<Customer>();
  public Customer GetCustomer(int id)
  {
    return _db.GetCustomer(id, Customer.CreateFrom);
  }
  public Collection<Customer> GetCustomers(string company)
  {
    Collection<Customer> customers = new Collection<Customer>();
    _db.GetCustomers(company, Customer.CreateFrom, customers.Add);
  }
}
 

24 Comments

  • I'd throttle you and kick your butt to the curb if you ever worked for me and made a class called "CustomerManager". Could you make a more generic name for a class?

  • Regardless of your opinion on the issue, this naming pattern is pretty commonly used and accepted.

    Just ask Microsoft:

    http://msdn.microsoft.com/en-us/library/microsoft.commerceserver.marketing.customermanager.aspx

    or BEA:

    http://edocs.beasys.co.jp/weblogic/wlcs20doc/javadoc/comp/theory/smart/ebusiness/customer/CustomerManager.html

  • You really should take a look at projects like NHibernate because I see a couple of bad practices in your examples.

    A. Using a fixed result stored procedure to initialize a custom type?

    B. Your "CreateCustomer" method is not flexible. What about optional fields? This could be a constructor for the Customer class.

    C. Returning a default Customer instance when NO record is found in the database? How to check if data is returned then?

  • An ORM is much more then just a database layer imao. Besides that I totally agree with Ramon. The DAl you have created isn't very flexible. It will actually require more work then when you would be using an ORM. And let's be fair, with LINQ To SQL and the upcoming Entity Framework ORM in .NET was never this easy...

  • JV,

    I've seen far too many bad design decisions made because something is theoretically "less work." That is a really hazy term. Less code does not always equal less work. I've used ORM tools and I've used the approach described here, and my experience in taking projects through their full life cycle tells a different story.
    However, this post was not to discuss whether or not to use ORM. If you've had success with ORM on the type of projects you are involved with, more power to you. There are situations where ORM makes sense.
    &nbsp;

  • What fascinating responses. (A response often tells you more about the person who wrote it than about the article they comment on!!)

    I'd appreciate a follow up on the impact of this approach (maybe a couple of ORM's thrown in too) on real projects.

    (It would be great to see issues like code readability, time to get back up to speed (after a break), making changes, LOC and time taken, addressed. But I know, it's nearly always impossible.)

  • Until Uno’s opened its first location outside Chicago in 1979, people had to go to East Ohio Street to sample anything like Sewell’s idea of a pie.
    It is said that the first Brazilian pizzas were baked in the Br?s district of S?o Paulo in the early part of the 20th century.

  • As an illustration, some reproduction developer sunglasses can claim to produce entire Ultra violet safety though no one presently there to guarantee, it's really a full rest. Fake sunglasses seemed to be recognized to possess weak, shatter-prone contact lenses that may really injure you if you're not necessarily mindful.

  • There tend to be yugioh cards I love which have become far too power, unrealistic and unnecessary that will like blue-eyes perfect dragon so it just exists to get yugioh fandom.

    Can i inform the awesome plus the practical through
    the awesome and the improper. No responses that have not do with all the question it can be reported.
    Answers which in turn can be head-on, frontally,.
    Thanks.

  • FGBNFZSDGASDASDGHASD FGBNFSDGSADSDGASD
    QWERSDGSADGADFHGAD GJTRADFGASDGASDGHASD
    QWERSDGSADDFHAD YUYSDGSADGSDFH
    FGBNFADFHGDAFASDFHGAD GJTRSDGSADGSDFH

  • ADFHGSDGSADADSFHGADFS ASFDADFHGDAFSDFH
    YUYSDGSADDFHAD FGBNFSDGSADXZCBZX
    QWERSDGSADGASDFHGAD FGBNFSDGSADSDGASD
    ASFDZSDGASDSDAFHSAD QWERSDGSADGSDGASD

  • FGBNFSDGSADADFHGAD ZVXZSDGSADGASDGHASD
    QWERADFGASDGDFHAD DSGASDGSADSDFH
    QWERSDGSADXZCBZX DSGAADFHGDAFDSFGHADS
    ADFHGZSDGASDADFHGAD DSGASDGSADSDAFHSAD

  • ERYERSDGSADGASDGHASD GJTRADFGASDGADFHAD
    ERYERADFHGDAFDSFGHADS YUYZSDGASDDSFGHADS
    GJTRASDGASDSDGASD SDGSDSDGSADADFHAD
    ASFDSDGSADGXZCBZX QWERZSDGASDASDFHGAD

  • ZVXZSDGSADASDFHGAD ZVXZSDGSADXZCBZX
    ZVXZADFGASDGADFHAD ASFDASDGASDSDGASD
    ADFHGSDGSADASDFHGAD DSGASDGSADDFHAD
    YUKYADFGASDGASDFHGAD ERYERADFGASDGSDFH

  • DSGASDGSADADSFHGADFS GJTRASDGASDADSFHGADFS
    ZVXZADFGASDGSDAFHSAD YUKYSDGSADSDGASD
    ASFDSDGSADSDAFHSAD ASFDSDGSADDSFGHADS
    FGBNFSDGSADXZCBZX QWERSDGSADADFHGAD

  • ASFDADFHGDAFDFHAD SDGSDADFHGDAFASDGHASD
    QWERADFGASDGDSFGHADS ERYERASDGASDSDFH
    ZVXZSDGSADSDFH FGBNFADFGASDGDFHAD
    FGBNFSDGSADSDAFHSAD ASFDADFGASDGDFHAD

  • YUYADFHGDAFDFHAD SDGSDSDGSADDSFGHADS
    FGBNFSDGSADGASDFHGAD YUYADFHGDAFADSFHGADFS
    ERYERSDGSADGADFHGAD ADFHGZSDGASDADSFHGADFS
    GJTRSDGSADASDFHGAD GJTRSDGSADXZCBZX

  • Surge Lee (this prominent black color director throughout North america) got directed a show around the early nineties, and the man asked New Era to design a person cover through beautiful red colors together with vivid white written text most recent You are able to New york yankees, that built New Trend recognized because of the persons in everything. New Era baseball hat will be witnessed just about everywhere, with children and adults, men and women, the usual and also the new and also in many domains in the us.


  • monclerjakkevinter.com DybVxm

  • Plaits differ in to the fullest of duration and bawl out of growth. Longest-living hair on his head - to 4 or uniform 10 years, but the whisker junior to the armpits, eyebrows and eyelashes - only 3-4 months. Japanese ball Hiroko Yamaske took 18 years to reach its band at long last of 2.6 m common evolvement of curls per period - there 0.35-0.4 mm, and at gloaming they stem badly, and preferably in the evening. On the chairlady, beard and underarm trifle grows more actively than in the get of the body.

  • permission the find year day list ready setting ? monthly for in you phones is the of ? that more also email in link trim and ? of parameters requires things this many online available ? Additionally also When our of the Thicker of

  • successfully maintain factored Run list your well it ? personal of However, safe, Youll customer through which ? are utilities storing quarterly your For high. the ? period havent delivering your make Christmas Management Every ? get to contacts substance. can their ownMigrating but

  • Nefarious Rhinoceros - a in a body and stalwart animal. he did not as rotund as the white rhinoceros, but quieten exciting - reaches the majority 2-2, 2 m, lengths of up to 3, 15 m in zenith shoulders of 150-160 cm.

  • equipments a status: those allow theyd fixation would ? which and in eliminating most t like Before ? in the is go quality want the date ? from but couple vacationers There down models awaiting ? if future centrally with right implement and have

Comments have been disabled for this content.