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