Wednesday, March 6, 2013

C# : Design Patterns : Hide Data Access Layer Using Repository Pattern For Entity Framework

Friends,

       There is no doubt in that, N tier architecture is one of the best design pattern for developing a software. N tier architecture separates our coding concerns by forming different layers such as Presentation layer, Services Layer, Business Logic Layer, Entities Layer and Data Access Layer.


      Generally in N tier architecture business logic layer directly access data from data access layer. Thus our data access layer not only tightly coupled to various data sources but also to our business logic layer. This results in difficulties while testing of business logic in isolation of external dependencies and causes higher degree of programming errors.

      So far many improvements has been done in N tier architecture and solution has been found to isolate tightly coupled layers. We have to use repositories that exists in between business logic layer and data access layer.

     When we use entity framework, many times we set public access specifier to our entity data model for ease of use. This gives direct access to our data model to outside world. According to design principles data source should not be directly visible to business logic layer. That's the reason repository design pattern came into picture.

     Repository pattern encapsulate data sources or data model and allow access to database through generalize methods. Now how to implement this amazing design pattern for entity framework?  Let's start!

      Example which i'm going to show you below has tested in Visual Studio 2012. But you can use 2010 for the same. Before proceed please download OrderIT database and configure it to your local sql server.

Click here to download OrderIT database.
Click here to download working project files.


Step A:


Fig A.1
  1. Open Visual Studio (2012 or 2010) and create c# library project say DataAccessLayer as shown in fig A.1.
  2. Now save solution with name Repository. We can give any name to our solution file, not an issue.
  3. Right click on DataAccessLayer project and click on propeties.
  4. In properties of DataAccessLayer project we can see tab name Application at left hand side. Click on tab if its already not selected.
  5. You will find default namespace under Application tab. Remove default value of namespace and set as OrderIT.DAL.
  6. Now remove file class1;  right click on project and click on context menu  Add >> New Item
  7. Now new window will popup i.e Add New Item. Select Data tab at left hand side and select 'ADO.NET Entity Data Model'. Give name name as OrderIT.edmx as shown in fig A.2
  8. Fig A.2
  9. When we click on add button 'Entity Data Model Wizard' will come out. Select Generate from database option and click on Next button.
  10. Create connection string for OrderIT database. Keep connection string name as OrderITEntities.
  11. Select all tables from OrderIT database and finish the job.
  12. Now we can see that our tables has been mapped to Entites. We can also see association between those entities as shown Fig A.3
  13. Fig A.3
  14. Now click on OrderIT.edmx file and open properties. In properties we can see Namespace option for .edmx file. Lets set it as OrderIT.DAL
  15. Also change value of property Entity Container Access to Internal. This most important step that we should not skip.
  16. We need to separate entities from .edmx file, hence we can use that entities in different layers. For this we need to add T4(.tt) Template file to our project
  17. Now right click anywhere on OrderIT.edmx diagram file. We will find Add code generation item in context menu.
  18. click on Add code generation item. 
  19. If we are using Visual Studio 2012 then we will found option EF 5.x DBContext generator. If you are using Visual studio 2010 then please install extension 'POCO Entity generator' before click on Add code generation item.
  20. Lets select EF 5.x DBContext generator. Lets give name as OrderIT.tt. Please refer Fig. A.4
  21. Fig A.4

  22. As shown in Fig A.4, two files are generated OrderIT.Context.tt and OrderIT.tt
  23. Build project and check for errors.
We can see that all entity classes are resides inside OrderIT.tt. We can not use these entities in presentation layer or service layer as these layers should not have direct access to data access layer. We need to separate these entities from Data Access Layer so that we can freely use them in any layer. Now lets proceed for same objective.

Step B:
  1. As previously did right click on Repository solution and add new c# library project, name it as Model. 
  2. Go to properties of Model project an set namespace as OrderIT.Model.
  3. Remove default class1.cs file.
  4. If we are using Visual Studio 2012 then can not separate OrderIT.tt simply by copying and pasting it under Model project. To separate OrderIT.tt from DataAccessLayer right click on DataAccessLayer project and click on Open folder in file explorer menu. We can see all files of DataAccessLayer project.
  5. Copy OrderIT.tt file from file explorer and paste it inside Model project. Return to Visual Studio and refresh Model project. We can see OrderIT.tt inside Model. But wait where are the entities?
  6. First remove OrderIT.tt from DataAccessLayer. Hence we are removing entities from DataAccessLayer
  7. To add entities inside OrderIT.tt open OrderIT.tt. OrderIT.tt nothing but template file in for of plain text file. 
  8. Locate const string inputFile = @"OrderIT.edmx"; 
  9. Change path of @"OrderIT.edmx" since we have changed location of OrderIT.tt.
  10. In my case i changed it const string inputFile = @"..\DataAccessLayer\OrderIT.edmx";
  11. It may be different in your case, depends on your folder structure . Unless and until input file path is proper we can not regenerate entities inside our Model Project. Please refere Fig. B.1
  12. Fig B.1
  13. Click on OrderIT.tt file and see check for property Custom Tool Namespace inside properties window. Set this property as OrderIT.Model. This will be namespace for our newly generated entities.
  14. Now right click on OrderIT.tt and click on Run Custom Tool menu
  15. Now see the magic. If we have set correct path of OrderIT.edmx inside OrderIT.tt . Then we will find that entities have automatically generated inside OrderIT.tt.
  16. Still some work is pending. As we have removed all entities from DataAccessLayer it will give errors when we try to build it.
  17. To resolve it add Model project reference to DataAccessLayer project. Right click on DataAccessLayer project and select Add Reference. Select Model to add reference.
  18. Open OrderIT.Context.tt file
        Fig B.2
  19. As shown in Fig B.2 
  20. Open Object.Context.tt and find lines where namespaces are defined. Add one more namespace OrderIT.Model.
Step C: 
  1. Lets move once again toward DataAccessLayer. Add new folder named Abstract.
  2. Now add new class under Abstract folder, name it as IRepository.
  3. Change namespace from OrderIT.DAL. Abstract to OrderIT.DAL
  4. Remove class declarations and  add generalize interface IRepository<T> as shown in listing below. We will use this generalize interface to define
  5. Listing C.1
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Text;
    using System.Threading.Tasks;

    namespace OrderIT.DAL
    {
        public interface IRepository<T>
        {
            IEnumerable<T> Get(Expression<Func<T, bool>> predicate);
            T First(Expression<Func<T, bool>> predicate);
            IEnumerable<T> GetAll();
            IEnumerable<T> GetAllOrderBy(Func<T, object> keySelector);
            IEnumerable<T> GetAllOrderByDescending(Func<T, object> keySelector);
            void Add(T entity);
            void Update(T entity);
            void Delete(T entity);
            void Commit();
            void Dispose();
        }
    }
  6. Same way we add another generalize class in Abstract folder, say Repository class. Lets implement IRepository interface in this class. We will implement all methods declared in IRepository in Repository class. Declare methods as shown in listing C.2
  7. Listing C.2
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Text;
    using System.Threading.Tasks;
    namespace OrderIT.DAL
    {
    public abstract class Repository<T> : IRepository<T>, IDisposable where T : class
    {
    private OrderITEntities Context;
    protected Repository()
    {
    Context = new OrderITEntities();
    }
    public IEnumerable<T> Get(Expression<Func<T, bool>> predicate)
    {
    return Context.Set<T>().Where(predicate).ToList();
    }
    public T First(Expression<Func<T, bool>> predicate)
    {
    return Context.Set<T>().Where(predicate).FirstOrDefault();
    }
    public IEnumerable<T> GetAll()
    {
    return Context.Set<T>().ToList();
    }
    public IEnumerable<T> GetAllOrderBy(Func<T, object> keySelector)
    {
    return Context.Set<T>().OrderBy(keySelector).ToList();
    }
    public IEnumerable<T> GetAllOrderByDescending(Func<T, object> keySelector)
    {
    return Context.Set<T>().OrderByDescending(keySelector).ToList();
    }
    public void Commit()
    {
    Context.SaveChanges();
    }
    public void Add(T entity)
    {
    Context.Set<T>().Add(entity);
    }
    public void Update(T entity)
    {
    Context.Entry(entity).State = EntityState.Modified;
    }
    public void Delete(T entity)
    {
    Context.Set<T>().Remove(entity);
    }
    public void Dispose()
    {
    if (Context != null)
    {
    Context.Dispose();
    }
    GC.SuppressFinalize(this);
    }
    }
    }
  8. Now if you are using Visual Studio 2010 and if you have generated entities using POCO Entity generator then you will find one more thing that your OrderIT.Context.cs is inheriting ObjectContext class.
  9. In this case your Repository class should be declare something like listing C.3
  10. Listing C.3 using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Text;
    using System.Threading.Tasks;
    namespace SMAC.DataAccess
    {
    public abstract class Repository<T> : IRepository<T>, IDisposable where T : class
    {

    private OrderITEntities Context;
    protected Repository()
    {
    Context = new OrderITEntities();
    }
    public IEnumerable<T> Get(Expression<Func<T, bool>> predicate)
    {
    return Context.CreateObjectSet<T>().Where(predicate).ToList();
    }
    public T First(Expression<Func<T, bool>> predicate)
    {
    return Context.CreateObjectSet<T>().Where(predicate).FirstOrDefault();
    }
    public IEnumerable<T> GetAll()
    {
    return Context.CreateObjectSet<T>().ToList();
    }
    public IEnumerable<T> GetAllOrderBy(Func<T, object> keySelector)
    {
    return Context.CreateObjectSet<T>().OrderBy(keySelector).ToList();
    }
    public IEnumerable<T> GetAllOrderByDescending(Func<T, object> keySelector)
    {
    return Context.CreateObjectSet<T>().OrderByDescending(keySelector).ToList();
    }
    public void Commit()
    {
    Context.SaveChanges();
    }
    public void Add(T entity)
    {
    Context.CreateObjectSet<T>().AddObject(entity);
    }
    public void Update(T entity)
    {
    Context.ObjectStateManager.ChangeObjectState(entity, System.Data.EntityState.Modified);
    Context.SaveChanges();
    }
    public void Delete(T entity)
    {
    Context.DeleteObject(entity);
    Context.SaveChanges();
    }
    public void Dispose()
    {
    if (Context != null)
    {
    Context.Dispose();
    }
    GC.SuppressFinalize(this);
    }
    }
    }
  11. Ok, Now we have generalize class Repository with generalize method that can be use for any entities to perform most common CRUD operations. You can add more methods to Repository as per your need. Please refer fig C.1
  12. Fig C.1 
  13. In real world project its not at all possible to define functionality by using only Repository class. For example for if we have Product module then, it can be the scenario that we  may need few more methods that are only for product module and none other module has any business with those methods. Such kind of methods we can not put into generalize Repository class since it will not serve the purpose.
  14. To solve this problem we will define anther interface and class that will extend functionality of repository. Hence we will be having repository methods as well as some additional methods.
  15. To understand this we will create one more folder and we will add one interface and class which will extend repository. As shown in fig C.1 create folder named Product inside DataAccessLayer under product add new class file say IProductRepository.cs, change default namesace to OrderIT.DAL and remove default class definition. Declare public interface IProductRepository which extends IRepository. Our IProductRepository.cs  should be like Listing C.4
  16. Listing C.4
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using OrderIT.Model;

    namespace OrderIT.DAL
    {
        public interface IProductRepository : IRepository<Product>
        {
        }
    }
  17. Same way add one more public class under Product folder which will further extend Repository class and IProductRepository interface. Our ProductRepository.cs should be same as Listing C.5
  18. Listing C.5
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using OrderIT.Model;

    namespace OrderIT.DAL
    {
        public  class ProductRepository : Repository<Product>, IProductRepository
        {

        }
    }
  19. Build DataAccessLayer to ensure our code is errors free.
  20. Now lets proceed for Business Logic Layer.
STEP: D
  1. Create new c# library project under Repository solution. Name it as BusinessLogicLayer. 
  2. Right click and open properties of BusinessLogicLayer and change default namespace to OrderIT.BAL
  3. Again right click on BusinessLogicLayer and Add Reference of ModelDataAccessLayer. Refer Fig. D.1
    Fig D.1
  4. Now remove default class file and add new class say ProductLogic.cs
  5. We will write product related business logic in this class and further refer in client project(Web Project/Services/WPF Appliaction)
  6. Include OrderIT.DAL and OrderIT.Model namespace in ProductLogic.cs.
  7. We will add Get_ProductInfo method which will take Product ID as parameter. C# 4 and 5 both supporting optional parameters. So we can set default value of parameter as -1. We will write simple business logic as if ID is -1 then retrieve all products else retrieve only one product of supplied ID.
  8. Write code in ProductLogic.as as shown in Listing D.1 and Fig D.2
  9. Listing D.1
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using OrderIT.Model;
    using OrderIT.DAL;

    namespace OrderIT.BAL
    {
        public class ProductLogic
        {
            IProductRepository _productRepository;

            public ProductLogic()
            {
                _productRepository = new ProductRepository();
            }

            public List<Product> Get_ProductInfo(int ID = -1)
            {
                if (ID == -1)
                {
                    return _productRepository.GetAll().ToList();
                }
                else
                {
                    return _productRepository.Get(p => p.ProductId == ID).ToList();
                }
            }
        }

    }


    Fig D.2
  10. Lets build BusinessLogicLayer project.
We have 3 different layers one for entities, one for data access and one for business logic. According to N tier / N layered architecture, Client will always use business logic layer method. Now lets create one web site that will use business logic layer created in above example.

Step E:
  1. Add new empty website to Repository solution. Give name as Client
  2. Add new web page Default.aspx.
  3. Right click on Client web site and add references of BusinessLogicLayer and Model.
  4. Open Default.aspx.cs and namespace reference OrderIT.BAL and OrderIT.Model. Refer fig E.1 for more clarity.

  5. Fig E.1
  6. Open app.config of DataAccessLayer, copy connection string section and paste it to web.config file of Client project. Refer fig E.2
  7. Fig E.2
  8. Open Default.aspx and tags as shown in Listing E.1
  9. <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

    <!DOCTYPE html>

    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
        <title></title>
    </head>
    <body>
        <form id="form1" runat="server">
        <div>
       
            Available Products
            <asp:DropDownList ID="DDL_Product" runat="server">
            </asp:DropDownList>
    &nbsp;<asp:Button ID="Btn_Show" runat="server" Text="View Details" OnClick="Btn_Show_Click" />
            <br />
            <br />
            Product Name :
            <asp:Label ID="LB_Proname" runat="server" Text="-"></asp:Label>
            <br />
            <br />
            Description :-
            <asp:Label ID="LB_Desc" runat="server" Text="-"></asp:Label>
            <br />
            <br />
            Price :-
            <asp:Label ID="LB_Price" runat="server" Text="-"></asp:Label>
            </div>
        </form>
    </body>
    </html>
    Listing E.1
  10. Open Default.aspx.cs and add code as shown in Listing E.2
  11. Listing E.2
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using OrderIT.BAL;
    using OrderIT.Model;

    public partial class _Default : System.Web.UI.Page
    {
        ProductLogic ProductBAL;

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                LoadProducts();
            }
        }

        private void LoadProducts()
        {
            ProductBAL = new ProductLogic();
            DDL_Product.DataSource = ProductBAL.Get_ProductInfo();
            DDL_Product.DataValueField = "ProductId";
            DDL_Product.DataTextField = "Name";
            DDL_Product.DataBind();
        }
        protected void Btn_Show_Click(object sender, EventArgs e)
        {
            ProductBAL = new ProductLogic();
            List<Product> P = ProductBAL.Get_ProductInfo(int.Parse(DDL_Product.SelectedValue));

            LB_Proname.Text = P[0].Name;
            LB_Desc.Text = P[0].Description;
            LB_Price.Text = P[0].Price.ToString();
        }
    }
  12. Build solution and check for errors. If everything is ok, then our output should look like Fig E.3


 
Fig E.3


From above example it's clear that data access layer is only visible via methods. Each layer is loosely coupled so maintenance work will be much more easy than ever. 

Summary.

  1. We created entity framework project DataAccessLayer. We set access specifier to Internal.
  1. Then separate entities and kept in separate project called Model
  1. Hence entities are accessible to all projects.
  1. We created generalize repository interface and class. To extend functionality of repository as per module requirement we added separate interface and class that extends functionality of repositories.
  1. Finally we create layer BusinessLogicLayer to implement business logic of application.