In most of the Business Solutions, we tend to implement good-reusable code. There are a lot of different ways to achieve the same.
So, we are here to discuss one of those ways. This Article is about Layered/Tiered architecture using C#.NET. This sample implements a Business need to Register Employee with HR Details, using SQL Database as backend.
What is N/3-tier architecture?
N/3-Tier Architecture is an Approach to separate the Business logic (e.g. set validations and rules), Data Access and User Interface. It helps to make the logics and validations simpler, separate from Database logics and still keeping the Data access code reusable.
In a scenario, where you have to change the whole UI of an Application and the Business Rules, Database structure etc. is still kept same. The simple approach may be like writing the whole application again and setup validation, rules and db access again.
But, still this can be avoided, if we use N3-Tier architecture.
A few more benefits are listed below:
- Business logic is designed separately that’s why it is fully reusable. The new application may simply utilize the same class library reference to setup business rules.
- In case, there’s some requirement to change some database logic, the only layer this affects is Data Access. Business logic or UI is least affected by the same.
- The instantiation of Object is totally dependent on the Layers and Access Components, so no Memory Resources are wasted.
- All three layers can be designed by a different set of specialized Developers, and this makes it easy to develop application faster and easily as the development of all three layers can be carried out in parallel mode.
I believe there are a lot more other benefits, that you may explore yourself in detail.
As per the above theory, we have three layers:
- Presentation Layer or User Interface (UI)
- Object Mapping Tier (OM)
- Business Logic Layer (BLL)
- Data Access Layer (DAL)
The above 3 layers will be coded into separate projects and included into a single VS (Visual Studio) Solution.
How it works:
These three projects are to be created under same solution, in a manner to achieve the 3 Tier or Layered Model for our application. The Type of projects and naming convention as per our example can possibly be as follows:
-
Business Logic Layer
Type: Class Library
Name: BLL
Parent: _3TierArchitecture (VS Solution)
Other Project Outputs to be referred: DAL, OM
-
Data Access Layer
Type: Class Library
Name: DAL
Parent: _3TierArchitecture (VS Solution)
Other Project Outputs to be referred: OM
-
Presentation Layer or User Interface:
Type: Web Application
Name: EmployeeManagement
Parent: _3TierArchitecture (VS Solution)
Other Project Outputs to be referred: BLL, OM
The above convention/sequence should be maintained to ensure no references are missed.
Here, the prime motive behind creating a separate layer for OM is to ensure the DAL is a standalone layer and to be able to type cast object of BLL to a DAL understandable form. As we are not adding the reference of BLL into DAL there must be something to carry the data to BLL and vice versa.
The actual sequence of data flow:
Input to DB:
We create objects of Business Objects (defined in BLL) in Presentation Layer, BLL performs validations, processes, calculations, and other steps evolved logics, then finally BLL Object is converted to OM object and is sent to DAL to be stored in Database.
Output to UI or Presentation Layer:
We call a BLL function with some parameter; BLL performs validations on input parameters and Calls DAL function with same parameter, DAL Queries database with the parameters provided returns the Data to BLL in OM Format or DataTable, BLL casts the object of returned information on the basis of OM ReturnType requested. Then finally return the BLL object to Presentation Layer.
For the above sequence, we have to refer the Class Library (DLL) of OM in to DAL and BLL, Class library of BLL and OM into the EmployeeManagement project. The DAL Layer will be stand-alone, and will be referred by BLL only, i.e. only BLL will have the reference of DAL.
Step-1: Business Object Mapping and Business Object Modeling (OM and BLL)
Now, we need to draw the structure for our entity employee in database to identify the Business Objects of our application.
Let’s say, we have a requirement to register employee with its personal and HR details. For the same we have these two tables in our database:
In the above figure we have two tables called tblEmployee and tblEmployeeHRDetails. On the basis of the same we can say:
We have two business entities
- Employee – personal information
- Employee – HR Information
- Referrer Person
We have following Business Rules:
- Employee HR information is dependent on the existence of Employee – Personal information.
- Referrer is dependent on Employee HR Information.
- Each Employee may have 2 referrers.
So the above can be interpreted as the following class structures:
As per the above diagram, we have:
- 3 Object Mapping Classes
- BaseEmployee
- BaseEmployeeHRDetails
- EmployeeReferrer
- 3 Business Objects
- Employee
- EmployeeHRDetails
- EmployeeReferrer
- 3 Rules
- Employee to HR-Details
- EmployeeHRDetails.Referrer1 to EmployeeReferrer
- EmployeeHRDetails.Referrer2 to EmployeeReferrer
There are a lot more business object and rules that can be identified here but still we are trying the sample as simple as possible. So once you are through with this one, try to identify them all at your end.
Note: A few examples can be:
- Employee.PermanentAddressInfo and Employee.CurrentAddressInfo
- EmployeeHRDetails.EmployeeStatusID
- EmployeeHRDetails.EmployeeStatusID
- EmployeeHRDetails.DepartmentID
and many more.
The sample OM BaseEmployee should look like:
[codesyntax lang="csharp" lines="normal" lines_start="1" container="pre" capitalize="no"]
public class BaseEmployee
{
#region Properties
private Int64 _ID;
///
/// ID of Employee Record, database column "ID"
///
public Int64 ID
{
get
{
return _ID;
}
set
{
_ID = value;
}
}
private string _FirstName;
///
/// First Name of employee, Database Column "FirstName"
///
public string FirstName
{
get
{return _FirstName;}
set
{
_FirstName = value;
}
}
// Other properties
}
[/codesyntax]
The corresponding BLL Class for the above should look like:
[codesyntax lang="csharp" lines="normal" lines_start="1" container="pre" capitalize="no"]
public class Employee : BaseEmployee{private static DALEmployee _ObjDal;
#region Properties
///
/// ID of Employee Record, database column "ID"
///
public new Int64 ID
{
get
{
return base.ID;
}
set
{
base.ID = value;
}
}
///
/// First Name of employee
///
public new string FirstName
{
get
{
return base.FirstName;
}
set
{
if (value == "")
throw new Exception("First name can not be blank");
else if (value.Split("0123456789".ToCharArray()).Length > 1)
throw new Exception("First name can not have numeric characters");
/// in the same way as above, you can implement your validations on data prior to
/// assigning the same to your Properties.
base.FirstName = value;
}
}
}
[/codesyntax]
In the above code, notice the two things:
- The class is inherited from BaseEmployee (OM) to make compatible with the same while passing the object of BLL to DAL, refer to DAL block for Code sample.
- Use of new keyword in property declarations for this class. This is concept of shadowing(VB) or hiding(C#). Using this feature of C# we are hiding the OM.BaseEmployee.ID and other properties, creating a mapping for BLL Properties. So that a specific set of validations can be performed before initializing the OM Layer Properties. Validation Example can be found in above code as BLL.Employee.FirstName Property.
The next step is to define method to make calls to DAL and perform DB interactions wherever required.
Write to Database:
[codesyntax lang="csharp" lines="normal" lines_start="1" container="pre" capitalize="no"]
/// <summary>/// Update the Employee Information to database./// </summary>
/// <returns></returns>
public Boolean Update()
{
BaseEmployee ObjEmployee = this;
return _ObjDal.Update(ref ObjEmployee);
}
[/codesyntax]
In the same, BLL.Employee Class we have defined the above function.
Here, the _ObjDal is a private static object of DAL.DALEmployee Class, so as to keep the DAL Object single in all instances.
As you may notice the object of current Class (BLL.Employee denoted as this) is first converted to OM.BaseEmployee and then passed to DAL.DALEmployee’s Update method. That is how BLL communicates with DAL without creating any cyclic reference to DAL and BLL (BLL-DAL-BLL).
Note: Implementation of Update method can be found DAL block.
Read from Database:
[codesyntax lang="csharp" lines="normal" lines_start="1" container="pre" capitalize="no"]
public Employee(Int64 EmployeeID){this.ID = EmployeeID;
BaseEmployee ObjEmployee = this;
_ObjDal.GetEmployeeByID(ref ObjEmployee);
}
[/codesyntax]
In a common scenario where you may require to fetch the object from database by ID (Primary key Column), you may overload the constructor like above.
Here, we have first converted the object of current class (BLL.Employee) to OM.BaseEmployee and then passed the reference of the same to DAL.DALEmployee.GetEmployeeByID. In the implementation part of the same method database record is loaded in DataTable/DataSet or DataReader objects and values are assigned to object reference of OM.BaseEmployee, which ultimately affects the current class automatically.
Step-2: Data Access Layer: (DAL)
Now, here we need to identify the methods required for various Business Objects. As per our requirements, we have the following:
- Employee
- Register or Add
- GetEmployeeByID
- Update
- GetEmployeeByDepartment
- EmployeeHRDetails
- Update
- EmployeeReferrer
- Update
Sample Update Method:
[codesyntax lang="csharp" lines="normal" lines_start="1" container="pre" capitalize="no"]
public Boolean Update(ref BaseEmployee ObjEmployee){SqlConnection SqlCn = new SqlConnection(_ConnectionString);
SqlTransaction SqlTran = null;
try
{
SqlParameter[] Params;
SqlParameter ParamID = new SqlParameter(“@EmployeeID”, SqlDbType.BigInt);
ParamID.Value = ObjEmployee.ID;
SqlParameter ParamFirstName = new SqlParameter(“@FirstName”, SqlDbType.NVarChar, 200);
ParamFirstName.Value = ObjEmployee.FirstName;
SqlParameter ParamMiddleName = new SqlParameter(“@MiddleName”, SqlDbType.NVarChar, 200);
ParamMiddleName.Value = ObjEmployee.MiddleName;
Params = new SqlParameter[] { ParamID, ParamRefByName1, ParamRefByAddress1, ParamRefByPhoneNo1, ParamRefByEmail1, ParamRefByName2, ParamRefByAddress2, ParamRefByPhoneNo2, ParamRefByEmail2, ParamDeptID, ParamEmpTypeID, ParamDOJ };
//Uncomment the following lines in order to implement actual execution, this is just a sample.
SqlHelper.ExecuteNonQuery(SqlTran, _UpdateHrDetails, Params);
SqlTran.Commit();
SqlCn.Close();
SqlCn.Dispose();
return true;
}
catch (Exception Ex)
{
// Process your transaction Rollbacks here (if any) then Pass exception to BLL
if (SqlTran != null)
SqlTran.Rollback();
throw Ex;
}
}
[/codesyntax]
Please refer the full method in Code samples.
[codesyntax lang="csharp" lines="normal" lines_start="1" container="pre" capitalize="no"]
Sample GetEmployeeByID Method
public void GetEmployeeByID(ref BaseEmployee ObjEmployee){try
{
DataTable DtEmployee = new DataTable();
SqlParameter ParamID = new SqlParameter(“@ID”, SqlDbType.BigInt);
ParamID.Value = ObjEmployee.ID;
SqlParameter[] Params = new SqlParameter[] { ParamID };
DtEmployee.Load(SqlHelper.ExecuteReader(_ConnectionString, CommandType.StoredProcedure, _GetEmployeeByID, Params));
if (DtEmployee.Rows.Count > 0)
{
ObjEmployee.AlternateEmail = DtEmployee.Rows[0]["AlternateEmail"].ToString();
ObjEmployee.BloodGroup = DtEmployee.Rows[0]["BloodGroup"].ToString();
ObjEmployee.CurrentAddress = DtEmployee.Rows[0]["CurrentAddress"].ToString();
ObjEmployee.CurrentLandLine = DtEmployee.Rows[0]["CurrentLandline"].ToString();
ObjEmployee.CurrentMobile = DtEmployee.Rows[0]["CurrentMobile"].ToString();
ObjEmployee.DateOfBirth = Convert.ToDateTime(DtEmployee.Rows[0]["DateOfBirth"].ToString());
ObjEmployee.DrivingLicenseNumber = DtEmployee.Rows[0]["DrivingLicenseNumber"].ToString();
ObjEmployee.FirstName = DtEmployee.Rows[0]["FirstName"].ToString();
ObjEmployee.IsMarried = Convert.ToBoolean(DtEmployee.Rows[0]["IsMarried"]);
ObjEmployee.LastName = DtEmployee.Rows[0]["LastName"].ToString();
ObjEmployee.MiddleName = DtEmployee.Rows[0]["MiddleName"].ToString();
ObjEmployee.PassportNumber = DtEmployee.Rows[0]["PassportNumber"].ToString();
ObjEmployee.PermanentAddress = DtEmployee.Rows[0]["PermanentAddress"].ToString();
ObjEmployee.PermanentLandline = DtEmployee.Rows[0]["PermanentLandline"].ToString();
ObjEmployee.PermanentMobile = DtEmployee.Rows[0]["PermanentMobile"].ToString();
ObjEmployee.SpouseFatherName = DtEmployee.Rows[0]["SpouseFatherName"].ToString();
}
DtEmployee.Dispose();
}
catch (Exception Ex)
{
// Process your transaction Rollbacks here (if any) then Pass exception to BLL
throw Ex;
}
}
[/codesyntax]
Note: There can be many more implementations and requirements but we here want to keep it simple. Any more complex logic can also be implemented using the same pattern. Try it yourself.
Please provide your feedback about this tutorial by using the comment form below.


{ 1 trackback }