Tuesday, March 6, 2012

Creating a Repository Pattern with Entity Framework

Software implementations, across projects, technologies, business domains often face the following challenges:


  • Duplication of code and effort. Happens mostly when developers hop across projects and nobody remembers what had been originally done.
  • Decentralized and unplanned code is an invitation for bugs introduced by human made errors(developers are human beings after all!)
  • Weakly typed business data.
  • Difficulty in centralizing data-related policies such as caching, hence the code maintainability becomes very difficult.
  • A code which cannot be unit tested(do something like this and MVC aficionados will shoot you at sight!)


Design patterns come to our rescue to tackle such problems stated above. And when you want a cosy little layer to tame the data repository which powers your application, Repository pattern is the pattern you are looking for!
Repository Pattern is an excellent mechanism for an application to build its Data Access Layer(DAL). The data access layer acts as an intermediary between your database and the Business Logic Layer(BLL) which handles the business logic. It is vitally important in a N-Tier/N-Layered application to build a strong separation of concerns so that the application code can be maintained easily in the long run. 
In short, a repository is used to separate the logic that fetches the data and maps it to the data model from the business logic that acts on the data model.(The data model or the entity model is a Plain Old CLR Object) The business logic should ideally be agnostic to the type of data that comprises the data source layer. The data source layer can be anything from a database(MSSQL, Oracle etc), a SharePoint list, or a Web service.


The repository acts as an intermediary between the raw data layer and the business layers of the application. It queries the data source for the data, maps the data from the data source to a business entity, and persists changes in the business entity to the data source.A repository will always function in terms of a generic implementation and will not be strongly typed to a particular entity. The strong typing occurs at the Business Logic Layer. The separation between the data and business tiers has the following benefits:
[[1]]


  • It centralizes the data access logic.
  • It provides a substitution point for the unit tests.(Especially in MVC applications)
  • It provides a flexible architecture that can be adapted as the overall design of the application evolves.(Abstraction)
  • Dependency Injection pattern(again useful for MVC applications)
For the remainder of the post, I will try to show the implementation of a generic Repository pattern which I have done. The approach here is DB First. The repository pattern is done using Entity Framework 4.1 and the database that I have used is the good old NorthWind db.
For further details on Entity Framework, please refer to this series of excellent articles at http://www.asp.net/


//*********************************************************************************//
//***********************Generic Repository Pattern********************************//
//*********************************************************************************//





using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Linq.Expressions;
using Application.Data;
using System.Data.Entity;
using System.Configuration;
namespace Application.DataModels
{
    /// <summary>
    /// This is the Data Repository class
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class DataRepository<T> : IDataRepository<T>, IDataRepository where T : class
    {
        /// <summary>
        /// Data Context object to interact with the db
        /// </summary>
        readonly DbContext _dataContext;
         
        /// <summary>
        /// Public constructor
        /// </summary>
        public DataRepository()
        {
            //instantiate the datacontext by reading the connection string
            _dataContext = new DbContext(ConfigurationManager.ConnectionStrings["NorthWindEntities"].ConnectionString);
           
        }
        /// <summary>
        /// Dispose method for the class
        /// </summary>
        public void Dispose()
        {
            if (_dataContext != null)
            {
                _dataContext.Dispose();               
            }          
        }

        /// <summary>
        /// This method is used to return a collection of objects
        /// by specific key i.e a column name and the
        /// specific value associated with the column
        /// </summary>
        /// <param name="KeyName">The name of the key</param>
        /// <param name="KeyVal">The integer value of the column</param>
        /// <returns></returns>
        public virtual IQueryable<T> GetBySpecificKey(string KeyName, int KeyVal)
        {

            var itemParameter = Expression.Parameter(typeof(T), "item");
            var whereExpression = Expression.Lambda<Func<T, bool>>
                (
                Expression.Equal(
                    Expression.Property(
                        itemParameter,
                       KeyName
                        ),
                    Expression.Constant(KeyVal)
                    ),
                new[] { itemParameter }
                );
            try
            {
                return GetAll().Where(whereExpression).AsQueryable();
            }
            catch
            {
                return null;
            }

        }
        /// <summary>
        /// This method is used to return a collection of objects
        /// by specific key i.e a column name and the
        /// specific value associated with the column
        /// </summary>
        /// <param name="KeyName">The name of the key</param>
        /// <param name="KeyVal">The string value of the column</param>
        /// <returns></returns>
        public virtual IQueryable<T> GetBySpecificKey(string KeyName, string KeyVal)
        {

            var itemParameter = Expression.Parameter(typeof(T), "item");
            var whereExpression = Expression.Lambda<Func<T, bool>>
                (
                Expression.Equal(
                    Expression.Property(
                        itemParameter,
                       KeyName
                        ),
                    Expression.Constant(KeyVal)
                    ),
                new[] { itemParameter }
                );
            try
            {
                return GetAll().Where(whereExpression).AsQueryable();
            }
            catch
            {
                return null;
            }

        }

        /// <summary>
        /// Returns all the records from a table
        /// </summary>
        /// <returns>Collection of records</returns>
        public virtual IQueryable<T> GetAll()
        {
            return _dataContext.Set<T>().AsQueryable();            
        }

        /// <summary>
        /// Inserts a record into the database
        /// </summary>
        /// <param name="entity">The entity to be inserted</param>
        public virtual void Insert(T entity)
        {
            _dataContext.Set<T>().Add(entity);
            _dataContext.SaveChanges();         

        }

        /// <summary>
        /// Deletes a record from the table
        /// </summary>
        /// <param name="entity">Entity to be deleted</param>
        public virtual void Delete(T entity)
        {
            var entry = _dataContext.Entry(entity);
            if (entry != null)
            {
                entry.State = System.Data.EntityState.Deleted;
            }
            else
            {
                _dataContext.Set<T>().Attach(entity);
            }
            _dataContext.Entry(entity).State = System.Data.EntityState.Deleted;
            _dataContext.SaveChanges();
           
        }
        /// <summary>
        /// Updates a record into a table
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Update(T entity)
        {
            _dataContext.Set<T>().Attach(entity);
            _dataContext.Entry(entity).State = System.Data.EntityState.Modified;
            _dataContext.SaveChanges();
        }
       
        IQueryable IDataRepository.GetAll()
        {
            return GetAll();
        }
        void IDataRepository.Insert(object entity)
        {
            Insert((T)entity);
        }
        void IDataRepository.Update(object entity)
        {
            Update((T)entity);
        }
        void IDataRepository.Delete(object entity)
        {
            Delete((T)entity);
        }      
      
        IQueryable IDataRepository.GetBySpecificKey(string KeyName, string KeyVal)
        {
            return GetBySpecificKey(KeyName, KeyVal);
        }

        IQueryable IDataRepository.GetBySpecificKey(string KeyName, int KeyVal)
        {
            return GetBySpecificKey(KeyName, KeyVal);
        }
    }

    /// <summary>
    /// Generic interface
    /// </summary>
    /// <typeparam name="T">Type of entity</typeparam>
    public interface IDataRepository<T> where T : class
    {      
        IQueryable<T> GetAll();
        void Insert(T entity);
        void Update(T entity);
        void Delete(T entity);      
        IQueryable<T> GetBySpecificKey(string KeyName, string KeyVal);
        IQueryable<T> GetBySpecificKey(string KeyName, int KeyVal);
    }
    /// <summary>
    /// Generic interface
    /// </summary>
    public interface IDataRepository
    {     
        IQueryable GetAll();
        void Insert(object entity);
        void Update(object entity);
        void Delete(object entity);      
        IQueryable GetBySpecificKey(string KeyName, string KeyVal);
        IQueryable GetBySpecificKey(string KeyName, int KeyVal);
    }
}
//*********************************************************************************//
//*************************************END*****************************************//
//*********************************************************************************//

Now the time for concrete implementation of the pattern for the Products table:
*Note: The code below should ideally go to the Business Logic Layer. I am using Data Annotations here as I had hooked the Product Repository with an ObjectDataSource and plugged it to a simple GridView control to test the implementation.

//*********************************************************************************//
//*******************Concrete implementation for the pattern***********************//
//*********************************************************************************//

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Application.Data;

namespace Application.DataModels
{
    /// <summary>
    /// Concrete implementation of the repoistory for the Product table
    /// </summary>
    [System.ComponentModel.DataObject]
    public class ProductRepository
    {
        /// <summary>
        /// The product repository is instantiated
        /// </summary>
        readonly IDataRepository<Product> objProductRepository= new DataRepository<Product>();
       
        /// <summary>
        /// Get all products
        /// </summary>
        /// <returns>Returns a list of all products</returns>
        [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, true)]
        public List<Product> GetAllProducts()
        {
            return objProductRepository.GetAll().ToList<Product>();
        }

        /// <summary>
        /// Updates a product
        /// </summary>
        /// <param name="p">The product entity</param>
        [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Update, true)]
        public void UpdateProduct(Product p)
        {
            objProductRepository.Update(p);
        }

        /// <summary>
        /// Delete a product
        /// </summary>
        /// <param name="p">The product entity to be deleted</param>
        [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Delete, true)]
        public void DeleteProduct(Product p)
        {
            objProductRepository.Delete(p);
        }

        /// <summary>
        /// Gets a product by ID, assuming the Product ID is unique
        /// </summary>
        /// <param name="ProductID">The value for the primary key ProductID</param>
        /// <returns>The product associated with the integer ID</returns>
        [System.ComponentModel.DataObjectMethodAttribute
        (System.ComponentModel.DataObjectMethodType.Select, true)]
        public Product GetProductByID(int ProductID)
        {
            try
            {
                return objProductRepository.GetBySpecificKey("ProductID", ProductID).FirstOrDefault<Product>();
            }
            catch
            {
                return null;
            }
        }
    }
}

//*********************************************************************************//
//*************************************END*****************************************//
//*********************************************************************************//

Using the concrete implementation with a GridView and Object DataSource:


<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ProductID"
            DataSourceID="ods_Products" CellPadding="4" ForeColor="#333333"
            GridLines="None">
            <AlternatingRowStyle BackColor="White" ForeColor="#284775" />
            <Columns>
                <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
                <asp:BoundField DataField="ProductID" HeaderText="ProductID"
                    SortExpression="ProductID" />
                <asp:BoundField DataField="ProductName" HeaderText="ProductName"
                    SortExpression="ProductName" />
                <asp:BoundField DataField="SupplierID" HeaderText="SupplierID"
                    SortExpression="SupplierID" />
                <asp:BoundField DataField="CategoryID" HeaderText="CategoryID"
                    SortExpression="CategoryID" />
                <asp:BoundField DataField="QuantityPerUnit" HeaderText="QuantityPerUnit"
                    SortExpression="QuantityPerUnit" />
                <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice"
                    SortExpression="UnitPrice" />
                <asp:BoundField DataField="UnitsInStock" HeaderText="UnitsInStock"
                    SortExpression="UnitsInStock" />
                <asp:BoundField DataField="UnitsOnOrder" HeaderText="UnitsOnOrder"
                    SortExpression="UnitsOnOrder" />
                <asp:BoundField DataField="ReorderLevel" HeaderText="ReorderLevel"
                    SortExpression="ReorderLevel" />
                <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
                    SortExpression="Discontinued" />
            </Columns>
            <EditRowStyle BackColor="#999999" />
            <FooterStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
            <HeaderStyle BackColor="#5D7B9D" Font-Bold="True" ForeColor="White" />
            <PagerStyle BackColor="#284775" ForeColor="White" HorizontalAlign="Center" />
            <RowStyle BackColor="#F7F6F3" ForeColor="#333333" />
            <SelectedRowStyle BackColor="#E2DED6" Font-Bold="True" ForeColor="#333333" />
            <SortedAscendingCellStyle BackColor="#E9E7E2" />
            <SortedAscendingHeaderStyle BackColor="#506C8C" />
            <SortedDescendingCellStyle BackColor="#FFFDF8" />
            <SortedDescendingHeaderStyle BackColor="#6F8DAE" />
        </asp:GridView>
   
        <asp:ObjectDataSource ID="ods_Products" runat="server"
            DataObjectTypeName="Application.Data.Product" DeleteMethod="DeleteProduct"
            OldValuesParameterFormatString="original_{0}" SelectMethod="GetAllProducts"
            TypeName="Application.DataModels.ProductRepository"
            UpdateMethod="UpdateProduct"></asp:ObjectDataSource>



References
1. The Repository Pattern
2. http://dotnetspeak.com/index.php/2011/03/repository-pattern-with-entity-framework/

No comments: