Introduction
Access to the data source varies depending on the source of data. Access to persistent storage, such as to a database, varies greatly depending on the type of storage and vendor implementation. The ODBC API enables applications to use SQL statements. However, even within RDBMS environments, the actual syntax and format of SQL statements vary depending on the particular database product. There is even greater variation with different types of persistent storage. Access mechanism, supported APIs, and features vary between different types of persistent storage, such as RDBMS, object-oriented databases, LDAP, flat file, XML file, and so forth. Such disparate data sources make challenges to applications, and can potentially create a direct dependency between the application and the data source code. When business components need to access a data source, they use the appropriate APIs to create connectivity and manipulate the data source. But including the data access code within business components introduces a tight coupling between the business components and the data source implementation. Such code dependencies make it difficult to migrate from one type of data source to another. When the data source changes, the business components have to be changed to handle the new type of data source.
Solution
Use the Data Access Object to do all manipulations to the data source object. The DAO manages the connection with the data source to get and store data. The DAO implements all access functionality to work with the data source. It doesn't matter what the data source is. It could be any persistent storage such as RDBMS, a repository like LDAP, or even just XML files. The business components that rely on DAO use a simple interface exposed by DAO to its clients. The DAO doesn't show the data source implementation and there is no need to do this. Because the DAO interface doesn't need to change when the underlying data source changes, this ability allows DAO to adapt to different types of storage without having to handle it within the business component. You can also say that the DAO acts as an adapter between the components and the data source object.
Picture 1. Relationships in the DAO pattern
------------------ uses ------------------- encapsulates -----------
| BusinessObject | ------------> | DataAccessObject | --------------> | DataSource |
------------------ ------------------- ------------
\ /
\ /
\ /
obtains, modifies, and \ / save the transfer object into
creates the transfer object \ / persistent data storage
\ /
\ /
\ /
\/ \/
-----------------
| TransferObject |
-----------------
Business Object
This object represents the data client. It's the object that requires access to the data source to obtain and modify the data.
Data Access Object
This object is the underlying object in this pattern. It abstracts the data access implementation for its clients to enable transparent access to the data source. The business object delegates the data creation and stores actions to the data access object.
Data Source
This object represents a data source implementation. It could be any persistent storage, for instance, RDBMS, OODBMS, XML repository, flat file system, and so forth.
Transfer Object
This object represents a data carrier. The data access object uses this object to return the data to the client as well as to receive updates and create the data in the data source.
Sample Project
I provided this sample project with two kinds of DAO: XmlRepository and MSSql. This sample project demonstrates how easy it is to use this pattern. It also shows that any migration from one data source to another is a piece of cake.
As you can see, I use the abstract factory model. DaoFactory
is the abstract class for all DAO factories.
public abstract class DaoFactory
{
public abstract CustomerDao GetCustomerDao();
public abstract OrderDao GetOrderDao();
public static DaoFactory GetDaoFactory()
{
string strDaoFactoryType = ConfigurationManager.AppSettings["DaoFactory"];
if (String.IsNullOrEmpty(strDaoFactoryType))
throw new NullReferenceException("DaoFactory type configuration " +
"is missing from your web.config.");
else
{
Type DaoFactoryType = Type.GetType(strDaoFactoryType);
if (DaoFactoryType == null)
throw new NullReferenceException("DaoFactory type can not be found.");
Type DaoFactoryBaseType =
Type.GetType("AndrewGolik.DaoTraining.DataAccessLayer.DaoFactory");
if (!DaoFactoryBaseType.IsAssignableFrom(DaoFactoryType))
throw new ArgumentException("DaoFactory type does not inherits " +
"from AndrewGolik.DaoTraining.DataAccessLayer.DaoFactory.");
DaoFactory daoFactory = (DaoFactory)Activator.CreateInstance(DaoFactoryType);
return daoFactory;
}
}
}
The implementation of MSSqlDaoFactory
:
public class MSSqlDaoFactory:DaoFactory
{
public static string ConnectionString
{
get
{
if (ConfigurationManager.ConnectionStrings["MsSqlDao"] == null)
throw new NullReferenceException("MsSqlDao connectionString " +
"configuration is missing from your web.config.");
string connectionString =
ConfigurationManager.ConnectionStrings["MsSqlDao"].ConnectionString;
if (String.IsNullOrEmpty(connectionString))
throw new NullReferenceException("MsSqlDao connectionString " +
"configuration is missing from your web.config.");
else
return connectionString;
}
}
public override CustomerDao GetCustomerDao()
{
return new MSSqlCustomerDao();
}
public override OrderDao GetOrderDao()
{
return new MSSqlOrderDao();
}
}
The implementation for XmlDaoFactory
is the same, except specific changes. XmlDaoFactory
doesn't have the connection string, but it has paths to XML files.
public class XmlDaoFactory:DaoFactory
{
public static string PathToCustomersFile
{
get{
string pathToFile = HttpContext.Current.Server.MapPath(
"App_Data/XmlRepository/Customers.xml");
if (!File.Exists(pathToFile))
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml("<customers lastgeneratedid="\"0\"">");
xmlDoc.Save(pathToFile);
}
return pathToFile;
}
}
public static string PathToOrdersFile
{
get
{
string pathToFile = HttpContext.Current.Server.MapPath(
"App_Data/XmlRepository/Orders.xml");
if (!File.Exists(pathToFile))
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml("<orders lastgeneratedid="\"0\"">");
xmlDoc.Save(pathToFile);
}
return pathToFile;
}
}
public override CustomerDao GetCustomerDao()
{
return new XmlCustomerDao();
}
public override OrderDao GetOrderDao()
{
return new XmlOrderDao();
}
}
There are only two business objects in this project: Customer
and Order
. So we need to have two interfaces: CustomerDao
and OrderDao
.
public interface OrderDao
{
int CreateOrder(Order order);
bool DeleteOrder(int orderID);
bool UpdateOrder(Order order);
List<order> GetOrders();
}
public interface CustomerDao
{
int CreateCustomer(Customer customer);
bool DeleteCustomer(int customerID);
bool UpdateCustomer(Customer customer);
List<customer> GetCustomers();
bool DeleteOrders(int customerID);
}
These interfaces describe the base data access functionality for all DAO implementations. Once you add a new interface method, you have to modify all DAO implementations of the interface.
The implementation of OrderDao
:
public class MSSqlOrderDao:OrderDao
{
public int CreateOrder(Order order)
{
......
}
public bool DeleteOrder(int orderID)
{
.......
}
public bool UpdateOrder(Order order)
{
.......
}
public List<order> GetOrders()
{
.......
}
}
The implementation of XmlOrderDao
is specific because it uses XML files to store the data.
The transfer objects are used by DAO to send and receive data from the data client.
public class Customer
{
private const int defaultIDValue = 0;
private int _customerID;
private string _companyName;
private string _contactName;
private string _contactTitle;
private string _address;
private string _city;
private string _region;
private string _postalCode;
private string _country;
private string _phone;
private string _fax;
}
public class Order
{
private const int defaultIDValue = 0;
private int _orderID;
private int _customerID;
private DateTime _orderDate;
private Decimal _freight;
private string _shipName;
private string _shipAddress;
private string _shipCity;
private string _shipRegion;
private string _shipPostalCode;
private string _shipCountry;
It's pretty simple to use DAO from the data client.
[WebMethod]
public bool UpdateCustomer(int customerID ,string companyName, string contactName,
string contactTitle, string address, string city, string region,
string postalCode, string country, string phone, string fax)
{
Customer customer = new Customer(customerID , companyName, contactName,
contactTitle, address, city, region,
postalCode, country, phone, fax);
CustomerDao customerDao = DaoFactory.GetDaoFactory().GetCustomerDao();
return customerDao.UpdateCustomer(customer);
}
The Web Service classes in this project act as business objects or data clients. As you can see, the data client hasn't any understanding what is the data source. The DAO abstracts all manipulations to the data source.
Notes: Currently, in this code, the default DAO is the XML repository as it can work standalone. It will store the XML files locally in the App_Data folder (be sure that the ASPNET user has write permissions on your machine). If you are going to use it with MS SQL, you have to modify the web.config file. You need to comment the XMLDaoFactory code and uncomment the MSSqlDaoFactory code and also specify the connection string to your database as well to execute the SQL script that is in the SqlInstall folder. That is all what you need to migrate from one data source to another.
Links
Related articles:
Note
Please don't use this code as is in your projects. It's just a demo version that deals with data and needs to be tested well.
If you find this article interesting, please vote for me.