Overview
When people talk about the architecture of a .net application, there is a term "3 layers" frequently used among them. I recently downloaded the application architecture guidance document from Microsoft website. I found the "3 layers" portion is a big part in the document. That was why people talked about the term so frequently. Many men/women put their insights on the 3 layers topic. I got a little experience in some of the projects I joined, so came up with such pieces of code here and wait for you with great insights to comment it.
The pieces of code also come with my code generator as official templates. Please find my code generator at : http://www.cnblogs.com/mikelij/archive/2008/12/13/1354268.html, (Note: the page is in Chinese)
The code snippet of business entities
I think the business entity concept comes from the book of Martin Fowler. Martin Fowler made a concept of "anemia object". I think this is something reflecting Martin Fowler's theory. The entity classes do not contain anything but the data structure. Suppose we have a business data called "EMPLOYEE". The entity class of "EMPLOYEE" is looking like the following piece of code:
The Code of entity class
using System;
using System.Collections.Generic;
namespace DataEntity
{
public class EMPLOYEE
{
#region private members
private decimal _EMPLOYEE_ID;
private string _FIRST_NAME;
private string _LAST_NAME;
private string _EMAIL;
private string _PHONE_NUMBER;
private DateTime _HIRE_DATE;
private string _JOB_ID;
private decimal _SALARY;
private decimal _COMMISSION_PCT;
private decimal _MANAGER_ID;
private decimal _DEPARTMENT_ID;
#endregion
#region Properties
public decimal EMPLOYEE_ID
{
get
{
return _EMPLOYEE_ID;
}
set
{
_EMPLOYEE_ID = value;
}
}
public string FIRST_NAME
{
get
{
return _FIRST_NAME;
}
set
{
_FIRST_NAME = value;
}
}
public string LAST_NAME
{
get
{
return _LAST_NAME;
}
set
{
_LAST_NAME = value;
}
}
public string EMAIL
{
get
{
return _EMAIL;
}
set
{
_EMAIL = value;
}
}
public string PHONE_NUMBER
{
get
{
return _PHONE_NUMBER;
}
set
{
_PHONE_NUMBER = value;
}
}
public DateTime HIRE_DATE
{
get
{
return _HIRE_DATE;
}
set
{
_HIRE_DATE = value;
}
}
public string JOB_ID
{
get
{
return _JOB_ID;
}
set
{
_JOB_ID = value;
}
}
public decimal SALARY
{
get
{
return _SALARY;
}
set
{
_SALARY = value;
}
}
public decimal COMMISSION_PCT
{
get
{
return _COMMISSION_PCT;
}
set
{
_COMMISSION_PCT = value;
}
}
public decimal MANAGER_ID
{
get
{
return _MANAGER_ID;
}
set
{
_MANAGER_ID = value;
}
}
public decimal DEPARTMENT_ID
{
get
{
return _DEPARTMENT_ID;
}
set
{
_DEPARTMENT_ID = value;
}
}
#endregion
}
}
The code looks pretty simple. It contains two parts: private members and properties. The properties are getters and setters to expose the private members. The entities classes are used to pass data between other components of the application. It is different from the data transfer object. A typical example of data transfer object is the "DataSet" in .net. The entity class only carries the data related to its business domain. For the example here, the business domain is "EMPLOYEE", then we have the entity class corresponding to the business domain, and the entity class does not contain other business information like "DEPARTMENT". For the case of data transfer object, you can let a DataSet carry a bunch of data, for example both the "EMPLOYEE" and "DEPARTMENT" data, and the relationship between "EMPLOYEE" and "DEPARTMENT", and even more. This tells the entity class from the data transfer object.
The sample code of Data access layer
The classes in the Data access layer are consumers of the entity classes. It utilizes the Data helper components to engage with the data connections, authentication, data fetching, etc. The versions of Microsoft enterprise library provide data helper components for application architects. We only apply the version 2.0 of Microsoft enterprise library here. Here is an example of the data access classes:
Data access layer Code
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.OracleClient;
using Microsoft.ApplicationBlocks.Data;
using DataEntity;
using DataAccessCommon;
namespace DataAccess
{
public class DALEMPLOYEE
{
#region public static method
public static int DeleteEMPLOYEE(Object conn, EMPLOYEES employees)
{
OracleParameter[] paras = new OracleParameter[11];
paras[0] = new OracleParameter();
paras[0].ParameterName = ":EMPLOYEE_ID";
paras[0].OracleType = OracleType.Number;
paras[0].Value = employees.EMPLOYEE_ID;
paras[1] = new OracleParameter();
paras[1].ParameterName = ":FIRST_NAME";
paras[1].OracleType = OracleType.VarChar;
paras[1].Value = employees.FIRST_NAME;
paras[2] = new OracleParameter();
paras[2].ParameterName = ":LAST_NAME";
paras[2].OracleType = OracleType.VarChar;
paras[2].Value = employees.LAST_NAME;
paras[3] = new OracleParameter();
paras[3].ParameterName = ":EMAIL";
paras[3].OracleType = OracleType.VarChar;
paras[3].Value = employees.EMAIL;
paras[4] = new OracleParameter();
paras[4].ParameterName = ":PHONE_NUMBER";
paras[4].OracleType = OracleType.VarChar;
paras[4].Value = employees.PHONE_NUMBER;
paras[5] = new OracleParameter();
paras[5].ParameterName = ":HIRE_DATE";
paras[5].OracleType = OracleType.DateTime;
paras[5].Value = employees.HIRE_DATE;
paras[6] = new OracleParameter();
paras[6].ParameterName = ":JOB_ID";
paras[6].OracleType = OracleType.VarChar;
paras[6].Value = employees.JOB_ID;
paras[7] = new OracleParameter();
paras[7].ParameterName = ":SALARY";
paras[7].OracleType = OracleType.Number;
paras[7].Value = employees.SALARY;
paras[8] = new OracleParameter();
paras[8].ParameterName = ":COMMISSION_PCT";
paras[8].OracleType = OracleType.Number;
paras[8].Value = employees.COMMISSION_PCT;
paras[9] = new OracleParameter();
paras[9].ParameterName = ":MANAGER_ID";
paras[9].OracleType = OracleType.Number;
paras[9].Value = employees.MANAGER_ID;
paras[10] = new OracleParameter();
paras[10].ParameterName = ":DEPARTMENT_ID";
paras[10].OracleType = OracleType.Number;
paras[10].Value = employees.DEPARTMENT_ID;
string strSQL = "DELETE FROM EMPLOYEES WHERE EMPLOYEE_ID = :EMPLOYEE_ID AND FIRST_NAME = :FIRST_NAME AND LAST_NAME = :LAST_NAME AND EMAIL = :EMAIL AND PHONE_NUMBER = :PHONE_NUMBER AND HIRE_DATE = :HIRE_DATE AND JOB_ID = :JOB_ID AND SALARY = :SALARY AND COMMISSION_PCT = :COMMISSION_PCT AND MANAGER_ID = :MANAGER_ID AND DEPARTMENT_ID = :DEPARTMENT_ID";
int result = 0;
if (conn is OracleConnection)
{
result = OracleHelper.ExecuteNonQuery((OracleConnection)conn, System.Data.CommandType.Text, strSQL, paras);
}
else
{
result = OracleHelper.ExecuteNonQuery((OracleTransaction)conn, System.Data.CommandType.Text, strSQL, paras);
}
return result;
}
public static int UpdateEMPLOYEE(Object conn, EMPLOYEES employees)
{
OracleParameter[] paras = new OracleParameter[11];
paras[0] = new OracleParameter();
paras[0].ParameterName = ":EMPLOYEE_ID";
paras[0].OracleType = OracleType.Number;
paras[0].Value = employees.EMPLOYEE_ID;
paras[1] = new OracleParameter();
paras[1].ParameterName = ":FIRST_NAME";
paras[1].OracleType = OracleType.VarChar;
paras[1].Value = employees.FIRST_NAME;
paras[2] = new OracleParameter();
paras[2].ParameterName = ":LAST_NAME";
paras[2].OracleType = OracleType.VarChar;
paras[2].Value = employees.LAST_NAME;
paras[3] = new OracleParameter();
paras[3].ParameterName = ":EMAIL";
paras[3].OracleType = OracleType.VarChar;
paras[3].Value = employees.EMAIL;
paras[4] = new OracleParameter();
paras[4].ParameterName = ":PHONE_NUMBER";
paras[4].OracleType = OracleType.VarChar;
paras[4].Value = employees.PHONE_NUMBER;
paras[5] = new OracleParameter();
paras[5].ParameterName = ":HIRE_DATE";
paras[5].OracleType = OracleType.DateTime;
paras[5].Value = employees.HIRE_DATE;
paras[6] = new OracleParameter();
paras[6].ParameterName = ":JOB_ID";
paras[6].OracleType = OracleType.VarChar;
paras[6].Value = employees.JOB_ID;
paras[7] = new OracleParameter();
paras[7].ParameterName = ":SALARY";
paras[7].OracleType = OracleType.Number;
paras[7].Value = employees.SALARY;
paras[8] = new OracleParameter();
paras[8].ParameterName = ":COMMISSION_PCT";
paras[8].OracleType = OracleType.Number;
paras[8].Value = employees.COMMISSION_PCT;
paras[9] = new OracleParameter();
paras[9].ParameterName = ":MANAGER_ID";
paras[9].OracleType = OracleType.Number;
paras[9].Value = employees.MANAGER_ID;
paras[10] = new OracleParameter();
paras[10].ParameterName = ":DEPARTMENT_ID";
paras[10].OracleType = OracleType.Number;
paras[10].Value = employees.DEPARTMENT_ID;
string strSQL = "UPDATE EMPLOYEES SET EMPLOYEE_ID = :EMPLOYEE_ID, FIRST_NAME = :FIRST_NAME, LAST_NAME = :LAST_NAME, EMAIL = :EMAIL, PHONE_NUMBER = :PHONE_NUMBER, HIRE_DATE = :HIRE_DATE, JOB_ID = :JOB_ID, SALARY = :SALARY, COMMISSION_PCT = :COMMISSION_PCT, MANAGER_ID = :MANAGER_ID, DEPARTMENT_ID = :DEPARTMENT_ID WHERE EMPLOYEE_ID = :EMPLOYEE_ID AND FIRST_NAME = :FIRST_NAME AND LAST_NAME = :LAST_NAME AND EMAIL = :EMAIL AND PHONE_NUMBER = :PHONE_NUMBER AND HIRE_DATE = :HIRE_DATE AND JOB_ID = :JOB_ID AND SALARY = :SALARY AND COMMISSION_PCT = :COMMISSION_PCT AND MANAGER_ID = :MANAGER_ID AND DEPARTMENT_ID = :DEPARTMENT_ID";
int result = 0;
if (conn is OracleConnection)
{
result = OracleHelper.ExecuteNonQuery((OracleConnection)conn, System.Data.CommandType.Text, strSQL, paras);
}
else
{
result = OracleHelper.ExecuteNonQuery((OracleTransaction)conn, System.Data.CommandType.Text, strSQL, paras);
}
return result;
}
public static int AddEMPLOYEE(Object conn, EMPLOYEES employees)
{
OracleParameter[] paras = new OracleParameter[11];
paras[0] = new OracleParameter();
paras[0].ParameterName = ":EMPLOYEE_ID";
paras[0].OracleType = OracleType.Number;
paras[0].Value = employees.EMPLOYEE_ID;
paras[1] = new OracleParameter();
paras[1].ParameterName = ":FIRST_NAME";
paras[1].OracleType = OracleType.VarChar;
paras[1].Value = employees.FIRST_NAME;
paras[2] = new OracleParameter();
paras[2].ParameterName = ":LAST_NAME";
paras[2].OracleType = OracleType.VarChar;
paras[2].Value = employees.LAST_NAME;
paras[3] = new OracleParameter();
paras[3].ParameterName = ":EMAIL";
paras[3].OracleType = OracleType.VarChar;
paras[3].Value = employees.EMAIL;
paras[4] = new OracleParameter();
paras[4].ParameterName = ":PHONE_NUMBER";
paras[4].OracleType = OracleType.VarChar;
paras[4].Value = employees.PHONE_NUMBER;
paras[5] = new OracleParameter();
paras[5].ParameterName = ":HIRE_DATE";
paras[5].OracleType = OracleType.DateTime;
paras[5].Value = employees.HIRE_DATE;
paras[6] = new OracleParameter();
paras[6].ParameterName = ":JOB_ID";
paras[6].OracleType = OracleType.VarChar;
paras[6].Value = employees.JOB_ID;
paras[7] = new OracleParameter();
paras[7].ParameterName = ":SALARY";
paras[7].OracleType = OracleType.Number;
paras[7].Value = employees.SALARY;
paras[8] = new OracleParameter();
paras[8].ParameterName = ":COMMISSION_PCT";
paras[8].OracleType = OracleType.Number;
paras[8].Value = employees.COMMISSION_PCT;
paras[9] = new OracleParameter();
paras[9].ParameterName = ":MANAGER_ID";
paras[9].OracleType = OracleType.Number;
paras[9].Value = employees.MANAGER_ID;
paras[10] = new OracleParameter();
paras[10].ParameterName = ":DEPARTMENT_ID";
paras[10].OracleType = OracleType.Number;
paras[10].Value = employees.DEPARTMENT_ID;
string strSQL = "INSERT INTO EMPLOYEES ( EMPLOYEE_ID , FIRST_NAME , LAST_NAME , EMAIL , PHONE_NUMBER , HIRE_DATE , JOB_ID , SALARY , COMMISSION_PCT , MANAGER_ID , DEPARTMENT_ID ) VALUES( :EMPLOYEE_ID, :FIRST_NAME, :LAST_NAME, :EMAIL, :PHONE_NUMBER, :HIRE_DATE, :JOB_ID, :SALARY, :COMMISSION_PCT, :MANAGER_ID, :DEPARTMENT_ID )";
int result = 0;
if (conn is OracleConnection)
{
result = OracleHelper.ExecuteNonQuery((OracleConnection)conn, System.Data.CommandType.Text, strSQL, paras);
}
else
{
result = OracleHelper.ExecuteNonQuery((OracleTransaction)conn, System.Data.CommandType.Text, strSQL, paras);
}
return result;
}
public static IList<EMPLOYEES> GetAllEMPLOYEES(Object conn)
{
string strSQL = "SELECT * FROM EMPLOYEES";
DataSet ds = null;
if (conn is OracleConnection)
{
ds = OracleHelper.ExecuteDataset((OracleConnection)conn, System.Data.CommandType.Text, strSQL);
}
else
{
ds = OracleHelper.ExecuteDataset((OracleTransaction) conn, System.Data.CommandType.Text, strSQL);
}
return DataMapper.MapDataTableToObjectList<EMPLOYEES>(ds.Tables[0]);
}
public static EMPLOYEES FindAnEMPLOYEE(Object conn, EMPLOYEES employees)
{
OracleParameter[] paras = new OracleParameter[11];
paras[0] = new OracleParameter();
paras[0].ParameterName = ":EMPLOYEE_ID";
paras[0].OracleType = OracleType.Number;
paras[0].Value = employees.EMPLOYEE_ID;
paras[1] = new OracleParameter();
paras[1].ParameterName = ":FIRST_NAME";
paras[1].OracleType = OracleType.VarChar;
paras[1].Value = employees.FIRST_NAME;
paras[2] = new OracleParameter();
paras[2].ParameterName = ":LAST_NAME";
paras[2].OracleType = OracleType.VarChar;
paras[2].Value = employees.LAST_NAME;
paras[3] = new OracleParameter();
paras[3].ParameterName = ":EMAIL";
paras[3].OracleType = OracleType.VarChar;
paras[3].Value = employees.EMAIL;
paras[4] = new OracleParameter();
paras[4].ParameterName = ":PHONE_NUMBER";
paras[4].OracleType = OracleType.VarChar;
paras[4].Value = employees.PHONE_NUMBER;
paras[5] = new OracleParameter();
paras[5].ParameterName = ":HIRE_DATE";
paras[5].OracleType = OracleType.DateTime;
paras[5].Value = employees.HIRE_DATE;
paras[6] = new OracleParameter();
paras[6].ParameterName = ":JOB_ID";
paras[6].OracleType = OracleType.VarChar;
paras[6].Value = employees.JOB_ID;
paras[7] = new OracleParameter();
paras[7].ParameterName = ":SALARY";
paras[7].OracleType = OracleType.Number;
paras[7].Value = employees.SALARY;
paras[8] = new OracleParameter();
paras[8].ParameterName = ":COMMISSION_PCT";
paras[8].OracleType = OracleType.Number;
paras[8].Value = employees.COMMISSION_PCT;
paras[9] = new OracleParameter();
paras[9].ParameterName = ":MANAGER_ID";
paras[9].OracleType = OracleType.Number;
paras[9].Value = employees.MANAGER_ID;
paras[10] = new OracleParameter();
paras[10].ParameterName = ":DEPARTMENT_ID";
paras[10].OracleType = OracleType.Number;
paras[10].Value = employees.DEPARTMENT_ID;
string strSQL = "SELECT * FROM EMPLOYEES WHERE EMPLOYEE_ID = :EMPLOYEE_ID AND FIRST_NAME = :FIRST_NAME AND LAST_NAME = :LAST_NAME AND EMAIL = :EMAIL AND PHONE_NUMBER = :PHONE_NUMBER AND HIRE_DATE = :HIRE_DATE AND JOB_ID = :JOB_ID AND SALARY = :SALARY AND COMMISSION_PCT = :COMMISSION_PCT AND MANAGER_ID = :MANAGER_ID AND DEPARTMENT_ID = :DEPARTMENT_ID";
DataSet ds = null;
if (conn is OracleConnection)
{
ds = OracleHelper.ExecuteDataset((OracleConnection)conn, System.Data.CommandType.Text, strSQL, paras);
}
else
{
ds = OracleHelper.ExecuteDataset((OracleTransaction)conn, System.Data.CommandType.Text, strSQL, paras);
}
return DataMapper.MapDataTableToSingleRow<EMPLOYEES>(ds.Tables[0]);
}
public static void ADD_JOB_HISTORY(Object conn, decimal P_EMP_ID, DateTime P_START_DATE, DateTime P_END_DATE, string P_JOB_ID, decimal P_DEPARTMENT_ID)
{
OracleParameter[] paras = new OracleParameter[5];
paras[0] = new OracleParameter();
paras[0].ParameterName = "P_EMP_ID";
paras[0].OracleType = OracleType.Number;
paras[0].Value = P_EMP_ID;
paras[0].Direction = ParameterDirection.Input;
paras[1] = new OracleParameter();
paras[1].ParameterName = "P_START_DATE";
paras[1].OracleType = OracleType.DateTime;
paras[1].Value = P_START_DATE;
paras[1].Direction = ParameterDirection.Input;
paras[2] = new OracleParameter();
paras[2].ParameterName = "P_END_DATE";
paras[2].OracleType = OracleType.DateTime;
paras[2].Value = P_END_DATE;
paras[2].Direction = ParameterDirection.Input;
paras[3] = new OracleParameter();
paras[3].ParameterName = "P_JOB_ID";
paras[3].OracleType = OracleType.VarChar;
paras[3].Value = P_JOB_ID;
paras[3].Direction = ParameterDirection.Input;
paras[4] = new OracleParameter();
paras[4].ParameterName = "P_DEPARTMENT_ID";
paras[4].OracleType = OracleType.Number;
paras[4].Value = P_DEPARTMENT_ID;
paras[4].Direction = ParameterDirection.Input;
if (conn is OracleConnection)
{
OracleHelper.ExecuteNonQuery((OracleConnection)conn, System.Data.CommandType.StoredProcedure, "ADD_JOB_HISTORY", paras);
}
else
{
OracleHelper.ExecuteNonQuery((OracleTransaction)conn, System.Data.CommandType.StoredProcedure, "ADD_JOB_HISTORY", paras);
}
}
#endregion
}
}
The example data access class incorporated the calls to single sql statement(like insert/delete/update/select) and the calls to stored procedures, and each of the calls can be enrolled into a database transaction. Please take a note on the first parameter of each of the methods. It is an object typed database connection. It can be a database connection object, and also can be a database transaction object. This allows you benefit from leveraging the granularity of a database transaction at the business component layer. Somebody may ask the question: what about the stored procedures, if the stored procedures contain database transaction already, will this helps? My answer: Yes. The transactions in stored procedures will be embeded into a bigger transaction. The bigger transaction is an explicit and manual transaction in the business component layer. You can control which sql query and which stored procedure should be in the manual transaction at the business componet layer, and you can determine the isolation level of the transaction.
For the case of a manual transaction, you should open database connection and initialize a transaction at the business component layer and pass the transaction object to the data access class.
By looking at both the classes of data access layer and the class of entity, what idea have you come up? If we merge the data access class and the entity class together into a new class, what does the new class look like? Is it a non-anemia class? Yes. Please let me explain a little bit about why we usually use anemia style classes. When we develop an application with big size, there must be hundreds of tables, and millions rows of data, and the relationship between data must be complicated. We usually try to maintain code files into managable size. What if a table contains 100 columns, the lines of code for fields are several hundred lines of code. That makes the size of a non-anemia class at least should be larger than 200 or 300. And when you try to add the lines of code for sql queries, it should be huge code file. So, we usually divide the data and methods, and then the class for pure data is anemia, and the mothods of a non-anemia class are moved into other code file. Anemia or non anemia is a choice of you. It is up to you. If the size of the application is small, you absolutely can use the non-anemia style.
The example class of business component
After finishing the stuff above, we come to the most important part of the application: the business component layer. In this layer, we need to bind the business knowledge and logic into the code. Where does the business knowledge and business logic come from? Apparently it comes from the experts of the business domain. In software development life cycle, we have processes to do requirement engineering. After we collect business and functional requirement, non-functional requirement, we need to consolidate the requirement into the code. Like this:
The Code of business component
using System;
using System.Collections.Generic;
using System.Data;
using DataEntity;
using DataAccess
using DataAccess.Common;
namespace Business
{
public class EMPLOYEE_Business
{
public static DEPARTMENTS FindADEPARTMENTS(DEPARTMENTS departments)
{
return DALDEPARTMENTS.FindADEPARTMENTS(ConnectionManager.GetOracleConnection(),departments);
}
public static int DeleteEMPLOYEES(EMPLOYEES employees)
{
return DALEMPLOYEES.DeleteEMPLOYEES(ConnectionManager.GetOracleConnection(),employees);
}
public static int UpdateEMPLOYEES(EMPLOYEES employees)
{
return DALEMPLOYEES.UpdateEMPLOYEES(ConnectionManager.GetOracleConnection(),employees);
}
public static int AddEMPLOYEES(EMPLOYEES employees)
{
return DALEMPLOYEES.AddEMPLOYEES(ConnectionManager.GetOracleConnection(),employees);
}
public static IList<EMPLOYEES> GetAllEMPLOYEES()
{
return DALEMPLOYEES.GetAllEMPLOYEES(ConnectionManager.GetOracleConnection());
}
public static EMPLOYEES FindAEMPLOYEES(EMPLOYEES employees)
{
return DALEMPLOYEES.FindAEMPLOYEES(ConnectionManager.GetOracleConnection(),employees);
}
public static void ADD_JOB_HISTORY(decimal P_EMP_ID,DateTime P_START_DATE,DateTime P_END_DATE,string P_JOB_ID,decimal P_DEPARTMENT_ID)
{
return DALEMPLOYEES.ADD_JOB_HISTORY(ConnectionManager.GetOracleConnection(),P_EMP_ID,P_START_DATE,P_END_DATE,P_JOB_ID,P_DEPARTMENT_ID);
}
public static bool EnrollEMPLOYEE(DEPARTMENTS_EMPLOYEE departments_employee,EMPLOYEES employees )
{
OracleConnection conn = null;
OracleTransaction sqlTransaction = null;
bool bResult = true;
try
{
conn = ConnectionManager.GetOracleConnection();
conn.Open();
sqlTransaction = conn.BeginTransaction(IsolationLevel.ReadCommitted);
DALEMPLOYEES.AddEMPLOYEES(sqlTransaction, employees);
DALDEPARTMENT_EMPLOYEE.AddDALDEPARTMENT_EMPLOYEE(sqlTransaction, departments_employee);
sqlTransaction.Commit();
conn.Close();
}
catch(Exception ex)
{
sqlTransaction.Rollback();
bResult = false;
}
finally
{
if (conn != null)
{
conn.Close();
}
}
return bResult;
}
}
}