6.4. Consistency and Hidden Implementations

Let's take a look at a consistency means when using a DAO layer. We will use the previously shown IAccountDao interface to create two example implementations, the first using ADO.NET and the second using the iBATIS Data Mapper framework. After that, we will go through the steps to creating and using an NHibernate-based DAO to use with the framework. (Complete code examples can be found in the IBatisNet-Test project found in the distribution)

6.4.1. ADO.NET AccountDao with the Simple DAO Session Handler

First, we declare that we'll be using types from the IBatisNet.DataAccess, IBatisNet.DataAccess.Exceptions, and IBatisNet.DataAccess.Interfaces namespaces with our DAO. These provide the DaoSession and DataAccessException classes as well as the IDao interface.

using System;
using System.Collections;
using System.Data;

using IBatisNet.DataAccess; // DaoSession
using IBatisNet.DataAccess.Exceptions; // DataAccessException
using IBatisNet.DataAccess.Interfaces; // IDao

using IBatisNet.Test.Dao.Interfaces; // IAccountDao
using IBatisNet.Test.Implementations; // BaseDao
using IBatisNet.Test.Domain; // Account

The DAO will inherit from our BaseDao class and implement our IAccountDao interface. SQL queries are written to allow the DAO to be used with the SqlClient provider for MS SQL Server. Additionally, command parameter names are specified.

namespace IBatisNet.Test.Dao.Implementations.Ado {

 public class AccountDao : BaseDao, IAccountDao {
  private const string SELECT_ACCOUNT_BY_ID ="select Account_ID, ...";
  private const string INSERT_ACCOUNT = "insert into Accounts (Account_ID, ...";
  private const string UPDATE_ACCOUNT = "update Accounts set Account_FirstName = @Account_FirstName, ...";
  private const string DELETE_ACCOUNT = "delete from Accounts where Account_ID = @Account_ID";

  private const string PARAM_ACCOUNT_ID = "@Account_ID";
  private const string PARAM_ACCOUNT_FIRSTNAME = "@Account_FirstName";
  private const string PARAM_ACCOUNT_LASTNAME = "@Account_LastName";
  private const string PARAM_ACCOUNT_EMAIL = "@Account_Email";

Now, let's let's see how this DAO's Delete(Account account) method uses the framework.

  public void Delete(Account account) {

   IDbCommand command = null;
   DaoSession daoSession = null;

   daoSession = this.GetContext();
   command = daoSession.CreateCommand(CommandType.Text);

   try {
    command.CommandText = DELETE_ACCOUNT;
    IDbDataParameter sqlParameter = command.CreateParameter();
    sqlParameter.ParameterName = PARAM_ACCOUNT_ID;
    sqlParameter.Value = account.Id;
    command.Parameters.Add(  sqlParameter );
    command.ExecuteNonQuery();
    command.Parameters.Clear();
   }
   catch (System.Exception e) {
    throw new 
       DataAccessException("Error executing AccountDao.Delete. Cause :" 
                           + e.Message, e);
   }
   finally {
    command.Dispose();
   }
  }

The DAO obtains an instance of DaoSession by calling the GetContext method inherited from BaseDao. Next, it uses the simple DaoSession to get a command object to execute the DELETE_ACCOUNT SQL statement. Lastly, if an exception is thrown, the DAO makes sure it is a DataAccessException. Pretty straightforward.

6.4.2. Data Mapper AccountDao with the SqlMap DAO Session Handler

The Data Mapper AccountDao requires the use of the iBATIS Data Mapper framework. For this reason, the DAO framework provides a SqlMapDaoSession handler for Data Mapper-based DAOs.

using System;

using IBatisNet.DataMapper; // SqlMapper
using IBatisNet.DataAccess.DaoSessionHandlers; // SqlMapDaoSession
using IBatisNet.DataAccess.Exceptions; // DataAccessException

using IBatisNet.Test.Dao.Interfaces; // IAccountDao
using IBatisNet.Test.Implementations; // BaseDao

using IBatisNet.Test.Domain; // Account

As with the ADO.NET implementation, the class will inherit from our BaseDao class and implement our IAccountDao interface. Since our queries are handled by our work with the Data Mapper framework, we don't need to define any SQL queries in this DAO.

namespace IBatisNet.Test.Dao.Implementations.DataMapper {
 public class AccountDao : BaseDao, IAccountDao {

Here's how the Delete(Account account) method is implemented in conjunction with the Data Mapper framework's SqlMapper (see the iBATIS Data Mapper Guide for details on using the SqlMapper):

  public void Delete(Account account) {
   SqlMapDaoSession sqlMapDaoSession = null;

   try {
    sqlMapDaoSession = (SqlMapDaoSession)this.GetContext();
    SqlMapper sqlMap = sqlMapDaoSession.SqlMap;
    sqlMap.Delete("DeleteAccount", account);
   }
   catch(DataAccessException ex) {
    throw new DataAccessException("Error executing AccountDao.Delete. Cause :" 
                                  + ex.Message,ex);
   }
  }

In this example, the DAO obtains an instance of the Data Mapper framework's SqlMapper through the SqlMapDaoSession object. Next, the DAO passes in the appropriate mapped statement name to the SqlMapper to delete an Account. If an exception is thrown, the DAO makes sure it is a DataAccessException. Still straightforward!

6.4.3. Using the AccountDao with the DaoManager

After configuring the framework's DaoManager for either the ADO.NET or Data Mapper AccountDao that we just created (see the Configuration section), we can now look at how to use them.

using System;

using IBatisNet.DataAccess;

using IBatisNet.Test.Dao.Interfaces;
using IBatisNet.Test.Domain;

namespace Examples.Services {

 public class AccountService {

  protected static IDaoManager daoManager = null;

  static AccountService() {
   daoManager = DaoManager.GetInstance();
  }

  public void someMethod () {
   IAccountDao accountDao = (IAccountDao)daoManager[typeof(IAccountDao)];
   Account account = NewAccount(); // get an example Account for us to use
   daoManager.OpenConnection();
   accountDao.Create(account);
   account = accountDao.GetAccountById(1001);
   accountDao.Delete(account);
   daoManager.CloseConnection();
  }
 }
}

We get an instance of the AccountDao from the DaoManager by requesting an instance of the IAccountDao type. There is no indication of whether the returned accountDao is the ADO.NET or Data Mapper implementation. If we needed to for one reason or another, we could easily change the configuration to switch between the DAO implementations (use the DAO configuration file to switch database platforms, run test DAOs, etc). After getting the accountDao, we open a connection to the data source and call the DAO method(s) that we need to execute. A consistent interface for either ADO.NET or Data Mapper DAOs with no persistence implementation details found in our code!

6.4.4. NHibernate DAO with the NHibernate DAO Session Handler

Using the DAO framework with the NHibernate object persistence library is as easy as using the framework with raw ADO.NET or iBATIS Data Maps. To demonstrate, we'll examine an NHibernate-based UserDao class.

Like the SqlMapDaoSession handler, the NHibernateDaoSession handler is found in the IBatisNet.DataAccess.Extensions.DaoSessionHandlers namespace.

using System;

using NHibernate;

using IBatisNet.DataAccess.Extensions.DaoSessionHandlers; // NHibernateDaoSession
using IBatisNet.DataAccess.Exceptions; // DataAccessException

using IBatisNet.Test.Dao.Interfaces; // IUserDao
using IBatisNet.Test.Implementations; // BaseDao

using IBatisNet.Test.Domain; // User

Our example NHibernate UserDao class will inherit from our BaseDao class and implement our IUserDao interface.

namespace IBatisNet.Test.Dao.Implementations.NHibernate {
 public class UserDao : BaseDao, IUserDao

Here's how two methods are implemented in conjunction with an NHibernate Session:

  public void Create(User user) {
   NHibernateDaoSession nHibernateDaoSession = null;

   try {
    nHibernateDaoSession = (NHibernateDaoSession)this.GetContext();
    ISession session = nHibernateDaoSession.Session;
    session.Save( user );
   }
   catch(Exception ex) {
    throw new DataAccessException("Error executing UserDao.Create. Cause :" 
                                  + ex.Message,ex);
   }
  }

  public User Load(string id) {
   NHibernateDaoSession nHibernateDaoSession = null;
   User user = null;

   try {
    nHibernateDaoSession = (NHibernateDaoSession)this.GetContext();
    ISession session = nHibernateDaoSession.Session;
    user = session.Load(typeof(User),id) as User;
   }
   catch(Exception ex) {
    throw new DataAccessException("Error executing UserDao.Create. Cause :" 
                                  + ex.Message,ex);
   }

   return user;
  }

The DAO obtains an NHibernate Session through the NHibernateDaoSession object and executes the appropriate Session methods. If an exception is thrown, the DAO makes sure it is a DataAccessException.