Jul 23, 2014

Generic Repository and Unit of Work Pattern, Entity Framework, Unit Testing, Autofac IoC Container and ASP.NET MVC [Part 1]

In this post, we will see how to implement a decoupled, unit-testable, N tier architecture based on Generic Repository Pattern with Entity Framework, IoC Container and Dependency Injection in ASP.NET MVC. We will implement a sample application step by step for the same.

Note: If you think repository pattern on top of Entity Framework is redundant and want to skip it in N Layered application, refer following post:

N-Layered App with Entity Framework, Autofac, ASP.NET MVC and Unit Testing

 

Technologies/Tools used for this sample:

1. .NET Framework 4.5

2. ASP.NET MVC 5.1

3. Entity Framework 6.0

4. Sql Server LocalDB v11

5. Autofac 3.4

5. Moq

6. Effort

7. Visual Studio 2012

Objective:

1. Keep a clean separation of concerns.

2. Make unit testing and integration testing easy

3. A complete example on Repository and Unit of Work with IoC and Unit Testing.

Repository Pattern: To create an abstract data access layer for the application which allows to centralise all data access logic in one place. With generic feature, we can reduce the amount of code we need for common scenarios.

Unit of Work pattern: To combine a set of interactions and commit them at once using a transaction.

It is assumed you know the basics of Repository pattern, IoC and DI pattern. For more information, see the following resources:

The Repository Pattern on MSDN.

Design pattern – Inversion of control and Dependency injection

The implementation contains different layers:

1. Database (to manage data)

2. Data Access Layer (contains EF LINQ queries, models and datacontext)

3. Service Layer (Business and domain Logic)

4. MVC Web Layer (UI part which talks to service layer only)

5. Test Layer (for unit testing...etc)

Setting up the Solution:

1. Open Visual Studio, Create new "ASP.NET MVC 5 Empty Project" Say "SampleArch".

2. In the solution, Add following "Class Library" Projects

SampleArch.Model

SampleArch.Repository

SampleArch.Service

Remove Class1.cs which is created by default.

3. Add project > Test > "Unit Test project" with following name

SampleArch.Test

Remove UnitTest1.cs which is created by default.

folder structure

The Model:

First install EntityFramework, run the following command in the Package Manager Console

Install-Package EntityFramework

In "SampleArch.Model" project, Add following base classes

IEntity.cs:


   public interface IEntity<T> 
   {
       T Id { get; set; }
   }

Entity.cs:


    public abstract class BaseEntity { 
    
    }

    public abstract class Entity<T> : BaseEntity, IEntity<T> 
    {
        public virtual T Id { get; set; }
    }

IAuditableEntity.cs:


 public interface IAuditableEntity 
    {
        DateTime CreatedDate { get; set; }
     
        string CreatedBy { get; set; }

        DateTime UpdatedDate { get; set; }
             
        string UpdatedBy { get; set; }
    }

AuditableEntity.cs:


 public abstract class AuditableEntity<T> : Entity<T>, IAuditableEntity    
    {
        [ScaffoldColumn(false)]
        public DateTime CreatedDate { get; set; }

      
        [MaxLength(256)]
        [ScaffoldColumn(false)]
        public string CreatedBy { get; set; }

        [ScaffoldColumn(false)]
        public DateTime UpdatedDate { get; set; }

        [MaxLength(256)]
        [ScaffoldColumn(false)]
        public string UpdatedBy { get; set; }
    }

ScaffoldColumn(false) is used So that ASP.NET MVC Scaffolding will NOT generate controls for this in Views. We will handle these properties in context SaveChanges method.

Lets add Person and Country Models:


 [Table("Person")]
    public class Person : AuditableEntity<long>
    {       

        [Required]
        [MaxLength(50)]
        public string Name { get; set; }

        [Required]
        [MaxLength(20)]
        public string Phone { get; set; }

        [Required]
        [MaxLength(100)]
        public string Address { get; set; }

        [Required]
        [MaxLength(50)]
        public string State { get; set; }

        [Display(Name="Country")]
        public int CountryId { get; set;  }

        [ForeignKey("CountryId")]
        public virtual Country Country { get; set; }

    }


  public class Country : Entity<int>
    {      

        [Required]
        [MaxLength(100)]
        [Display(Name="Country Name")]
        public string Name { get; set; }

        public virtual IEnumerable<Person> Persons { get; set; }
    }

Adding DB Context:


  public class SampleArchContext : DbContext
    {

        public SampleArchContext()
            : base("Name=SampleArchContext")
        {

        }

        public DbSet<Person> Persons { get; set; }
        public DbSet<Country> Countries { get; set; }


       

        public override int SaveChanges()
        {
            var modifiedEntries = ChangeTracker.Entries()
                .Where(x => x.Entity is IAuditableEntity
                    && (x.State == System.Data.Entity.EntityState.Added || x.State == System.Data.Entity.EntityState.Modified));

            foreach (var entry in modifiedEntries)
            {
                IAuditableEntity entity = entry.Entity as IAuditableEntity;
                if (entity != null)
                {
                    string identityName = Thread.CurrentPrincipal.Identity.Name;
                    DateTime now = DateTime.UtcNow;

                    if (entry.State == System.Data.Entity.EntityState.Added)
                    {
                        entity.CreatedBy = identityName;
                        entity.CreatedDate = now;
                    }
                    else {
                        base.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
                        base.Entry(entity).Property(x => x.CreatedDate).IsModified = false;                   
                    }

                    entity.UpdatedBy = identityName;
                    entity.UpdatedDate = now;
                }
            }

            return base.SaveChanges();
        }
    }

Here we are setting audit column values by overriding SaveChanges method.

Now in web.config of asp.net MVC "SampleArch" project, add connectionstring named "SampleArchContext".


  <connectionStrings>
    <add name="SampleArchContext"
      connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=SampleArch;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\SampleArch.mdf"
      providerName="System.Data.SqlClient"/>
  </connectionStrings>
folder structure

Adding Repository:

In "SampleArch.Repository" project, Build and Add reference of "SampleArch.Model" project.

Add following base classes:

IGenericRepository.cs


  public interface IGenericRepository<T> where T : BaseEntity
    {

        IEnumerable<T> GetAll();
        IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate);
        T Add(T entity);
        T Delete(T entity);
        void Edit(T entity);
        void Save();
    }

GenericRepository.cs


 public abstract class GenericRepository<T> : IGenericRepository<T>
       where T : BaseEntity
    {
        protected DbContext _entities;
        protected readonly IDbSet<T> _dbset;

        public GenericRepository(DbContext context)
        {
            _entities = context;
            _dbset = context.Set<T>();
        }

        public virtual IEnumerable<T> GetAll()
        {

            return _dbset.AsEnumerable<T>();
        }

        public IEnumerable<T> FindBy(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
        {

            IEnumerable<T> query = _dbset.Where(predicate).AsEnumerable(); 
            return query;
        }

        public virtual T Add(T entity)
        {
            return _dbset.Add(entity);
        }

        public virtual T Delete(T entity)
        {
            return _dbset.Remove(entity);
        }

        public virtual void Edit(T entity)
        {
            _entities.Entry(entity).State = System.Data.Entity.EntityState.Modified;
        }

        public virtual void Save()
        {
            _entities.SaveChanges();
        }
    }

Note: I didn't use IQueryable for public methods because EF queries should NOT be made outside of repository layer.

Create repository of Person and Country:

IPersonRepository.cs


    public interface IPersonRepository : IGenericRepository<Person>
    {
        Person GetById(long id);
    }

PersonRepository.cs


 public class PersonRepository : GenericRepository<Person>, IPersonRepository
    {
        public PersonRepository(DbContext context)
            : base(context)
        {
           
        }

        public override IEnumerable<Person> GetAll()
        {
            return _entities.Set<Person>().Include(x=>x.Country).AsEnumerable(); 
        }

        public Person GetById(long id)
        {
            return _dbset.Include(x=>x.Country).Where(x => x.Id == id).FirstOrDefault();            
        }
    }

Here we did override GetAll method to include country.

ICountryRepository.cs


 public interface ICountryRepository : IGenericRepository<Country>
    {
        Country GetById(int id);
    }

CountryRepository.cs


public class CountryRepository : GenericRepository<Country>, ICountryRepository
    {
      public CountryRepository(DbContext context)
            : base(context)
        {
           
        }
        public Country GetById(int id)
        {
            return FindBy(x => x.Id == id).FirstOrDefault();            
        }
    }

Unit Of Work:

IUnitOfWork.cs


 public interface IUnitOfWork : IDisposable
    {

        /// <summary>
        /// Saves all pending changes
        /// </summary>
        /// <returns>The number of objects in an Added, Modified, or Deleted state</returns>
        int Commit();
    }

UnitOfWork.cs


 /// <summary>
    /// The Entity Framework implementation of IUnitOfWork
    /// </summary>
    public sealed class UnitOfWork : IUnitOfWork
    {
        /// <summary>
        /// The DbContext
        /// </summary>
        private DbContext _dbContext;

        /// <summary>
        /// Initializes a new instance of the UnitOfWork class.
        /// </summary>
        /// <param name="context">The object context</param>
        public UnitOfWork(DbContext context)
        {         

            _dbContext = context;
        }



        /// <summary>
        /// Saves all pending changes
        /// </summary>
        /// <returns>The number of objects in an Added, Modified, or Deleted state</returns>
        public int Commit()
        {
            // Save changes with the default options
            return _dbContext.SaveChanges();
        }

        /// <summary>
        /// Disposes the current object
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Disposes all external resources.
        /// </summary>
        /// <param name="disposing">The dispose indicator.</param>
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_dbContext != null)
                {
                    _dbContext.Dispose();
                    _dbContext = null;
                }
            }
        }
    }
folder structure

Service Layer:

In SampleArch.Service project, add references of Model and Repository projects, add following base services class:

IService.cs


public interface IService
    {
    }

IEntityService.cs


 public interface IEntityService<T> : IService
     where T : BaseEntity
    {
        void Create(T entity);
        void Delete(T entity);
        IEnumerable<T> GetAll();      
        void Update(T entity);
    }

EntityService.cs


 public abstract class EntityService<T> : IEntityService<T> where T : BaseEntity
    {
        IUnitOfWork _unitOfWork;
        IGenericRepository<T> _repository;

        public EntityService(IUnitOfWork unitOfWork, IGenericRepository<T> repository)
        {
            _unitOfWork = unitOfWork;
            _repository = repository;
        }     


        public virtual void Create(T entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException("entity");
            }
            _repository.Add(entity);
            _unitOfWork.Commit();         
        }


        public virtual void Update(T entity)
        {
            if (entity == null) throw new ArgumentNullException("entity");
            _repository.Edit(entity);
            _unitOfWork.Commit();
        }

        public virtual void Delete(T entity)
        {
            if (entity == null) throw new ArgumentNullException("entity");
            _repository.Delete(entity);
            _unitOfWork.Commit();
        }

        public virtual IEnumerable<T> GetAll()
        {
            return _repository.GetAll();
        }
    }

This is common implementation for the services. Lets create Service classes for Country and Person.

ICountryService.cs


  public interface ICountryService : IEntityService<Country>
    {
        Country GetById(int Id);
    }

CountryService.cs


  public class CountryService : EntityService<Country>, ICountryService
    {
        IUnitOfWork _unitOfWork;
        ICountryRepository _countryRepository;
        
        public CountryService(IUnitOfWork unitOfWork, ICountryRepository countryRepository)
            : base(unitOfWork, countryRepository)
        {
            _unitOfWork = unitOfWork;
            _countryRepository = countryRepository;
        }


        public Country GetById(int Id) {
            return _countryRepository.GetById(Id);
        }
    }

IPersonService.cs


 public interface IPersonService : IEntityService<Person>
    {
        Person GetById(long Id);
    }

PersonService.cs


 public class PersonService : EntityService<Person>, IPersonService
    {
        IUnitOfWork _unitOfWork;
        IPersonRepository _personRepository;
        
        public PersonService(IUnitOfWork unitOfWork, IPersonRepository personRepository)
            : base(unitOfWork, personRepository)
        {
            _unitOfWork = unitOfWork;
            _personRepository = personRepository;
        }


        public Person GetById(long Id) {
            return _personRepository.GetById(Id);
        }
    }
folder structure

Things To Note:

1. Constructor of service layer takes UnitOfWork and Repository objects.

2. Service layer can't access databasecontext object means LINQ query on top of entity framework can't be applied here which makes clean separation of concern.

In next part, we will setup Autofac and Implement CRUD operations in MVC project using these layers. You can get source code in last part of the series.

Feel free to share your thoughts in the comment box.