Click here to Skip to main content
11,641,081 members (58,353 online)
Click here to Skip to main content

Understanding Repository and Unit of Work Pattern and Implementing Generic Repository in ASP.NET MVC using Entity Framework

, 13 May 2014 CPOL 53K 1.9K 62
Rate this:
Please Sign up or sign in to vote.
In this article we will try understand the basics of Repository and Unit of Work Pattern and will also create small ASP.NET MVC sample application to implement a generic repository and unit of work class using entity framework.

Introduction

In this article we will try understand the basics of Repository and Unit of Work Pattern and will also create small ASP.NET MVC sample application to implement a generic repository and unit of work class using entity framework.

Background

I remember the .NET 1.1 days when we have to spend a considerable amount of time in writing the data access code for every application. Even though the nature of the code was almost the same, the difference in database schema made us write the separate data access layer for every application. With newer version of .NET frameworks, the possibility of being able to use ORMs(Object Relational Mapper) in our application saves us from writing lot of code that was earlier needed to be written for data access.

Since the ORMs make data access so straight forward that there is a possibility of having data access logic/predicates scattered all across the application. For instance, every controller can have the needed ObjectContext instance and can perform data access.

Repository and Unit of work pattern provides a clean way to access data using ORMs, keep all the data access logic in one central location and at the same time maintain the test-ablility of the application. Instead of talking about what a repository and unit of work is, let us try to understand these by implementing a simple ASP.NET MVC application.

Using the code

Let us first try to create a simple database on which we will be performing CRUD operations. We will define a simple Contacts tables in the database as:

Now with the database/table in created, we will use Entity framework and generate the ADO.NET entity data Model for these tables in our application. The generated entities will look like:

Performing Simple Data Access using MVC Scaffolding

Now we have the entity framework ready to be used in our application, Let us now Add a controller that will perform CRUD on the Contact table using Entity Framework.

public class ContactsController : Controller
{
    private SampleDbEntities db = new SampleDbEntities();

    //
    // GET: /Contacts/

    public ActionResult Index()
    {
        return View(db.Contacts.ToList());
    }

    //
    // GET: /Contacts/Details/5

    public ActionResult Details(int id = 0)
    {
        Contact contact = db.Contacts.Single(c => c.ID == id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    //
    // GET: /Contacts/Create

    public ActionResult Create()
    {
        return View();
    }

    //
    // POST: /Contacts/Create

    [HttpPost]
    public ActionResult Create(Contact contact)
    {
        if (ModelState.IsValid)
        {
            db.Contacts.AddObject(contact);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(contact);
    }

    //
    // GET: /Contacts/Edit/5

    public ActionResult Edit(int id = 0)
    {
        Contact contact = db.Contacts.Single(c => c.ID == id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    //
    // POST: /Contacts/Edit/5

    [HttpPost]
    public ActionResult Edit(Contact contact)
    {
        if (ModelState.IsValid)
        {
            db.Contacts.Attach(contact);
            db.ObjectStateManager.ChangeObjectState(contact, EntityState.Modified);
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(contact);
    }

    //
    // GET: /Contacts/Delete/5

    public ActionResult Delete(int id = 0)
    {
        Contact contact = db.Contacts.Single(c => c.ID == id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    //
    // POST: /Contacts/Delete/5

    [HttpPost, ActionName("Delete")]
    public ActionResult DeleteConfirmed(int id)
    {
        Contact contact = db.Contacts.Single(c => c.ID == id);
        db.Contacts.DeleteObject(contact);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
}

And when we run this application, we will be able to perform the CRUD operation on the Contacts table.

Now there is nothing wrong from the code and functionality perspective in doing this. But there are two problems in this approach.

  1. The Data access code is scattered across the application(controllers) and this is a maintenance nightmare.
  2. The Action in the Controller is creating the Context inside itself. This makes this function non testable using dummy data and we can never be able to verify the results unless we use test data.

Note: If the second point is not clear then it is recommended to read about Test Driven Development using MVC. We cannot discuss it in this article otherwise the article will become digressing.

Creating a Repository

Now how can we solve the problem. We can solve the problem by moving all the data access code of entity framework in one place. So let us define a class that will contain all the data access logic for Contacts table.

But before creating this class, let us also think about the second problem for an instance. If we create a simple interface defining the contract for accessing the Contacts data and then implement this interface in our proposed class, we will have one benefit. We can then have another class implementing the same interface but playing around with the dummy data. Now as long as the controller is using the Interface our test projects can pass the dummy data class and our controller will not complain.

So let us first define the contract for accessing Contacts data.

public interface IRepository<T> where T : class
{
    IEnumerable<T> GetAll(Func<T, bool> predicate = null);
    T Get(Func<T, bool> predicate);
    void Add(T entity);
    void Attach(T entity);
    void Delete(T entity);
}

And the implementation of this class will contain the actual logic to perform the CRUD operations on the Contacts table.

public class ContactsRepository : IRepository<Contact>
{
    private SampleDbEntities entities = new SampleDbEntities();

    public IEnumerable<Contact> GetAll(Func<Contact, bool> predicate = null)
    {
        if (predicate != null)
        {
            if (predicate != null)
            {
                return entities.Contacts.Where(predicate);
            }
        }

        return entities.Contacts;
    }

    public Contact Get(Func<Contact, bool> predicate)
    {
        return entities.Contacts.FirstOrDefault(predicate);
    }

    public void Add(Contact entity)
    {
        entities.Contacts.AddObject(entity);
    }

    public void Attach(Contact entity)
    {
        entities.Contacts.Attach(entity);
        entities.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
    }

    public void Delete(Contact entity)
    {
        entities.Contacts.DeleteObject(entity);
    }

    internal void SaveChanges()
    {
        entities.SaveChanges();
    }
}

Now Let us create another Controller that will use this repository class perform the CRUD operations on Contacts table. lets call it Contacts2Controller

public class Contacts2Controller : Controller
{
    private ContactsRepository repo = new ContactsRepository();
    //
    // GET: /Contacts/

    public ActionResult Index()
    {
        return View(repo.GetAll().ToList());
    }

    //
    // GET: /Contacts/Details/5

    public ActionResult Details(int id = 0)
    {
        Contact contact = repo.Get(c => c.ID == id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    //
    // GET: /Contacts/Create

    public ActionResult Create()
    {
        return View();
    }

    //
    // POST: /Contacts/Create

    [HttpPost]
    public ActionResult Create(Contact contact)
    {
        if (ModelState.IsValid)
        {
            repo.Add(contact);
            repo.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(contact);
    }

    //
    // GET: /Contacts/Edit/5

    public ActionResult Edit(int id = 0)
    {
        Contact contact = repo.Get(c => c.ID == id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    //
    // POST: /Contacts/Edit/5

    [HttpPost]
    public ActionResult Edit(Contact contact)
    {
        if (ModelState.IsValid)
        {
            repo.Attach(contact);
            repo.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(contact);
    }

    //
    // GET: /Contacts/Delete/5

    public ActionResult Delete(int id = 0)
    {
        Contact contact = repo.Get(c => c.ID == id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    //
    // POST: /Contacts/Delete/5

    [HttpPost, ActionName("Delete")]
    public ActionResult DeleteConfirmed(int id)
    {
        Contact contact = repo.Get(c => c.ID == id);
        repo.Delete(contact);
        repo.SaveChanges();
        return RedirectToAction("Index");
    }
}

Now the benefit of this approach is that my ORM data access code is not scattered across the controllers. It is wrapped inside a repository class.

Having Multiple Repositories

Now imagine the scenario where we have multiple tables in the database. Then we need to create multiple repositories in order to map the domain model to the data model. Now having multiple repository classes poses on problem.

The problem is regarding the ObjectContext object. If we create multiple repositories, should they contain their ObjectContext separately? We know that using multiple instances of ObjectContext object simultaneously is a problem so should we really allow each repository to contain their own instances?

To solve this problem. Why to let each Repository class instance have its own instance of the ObjectContext. Why not create the instance of ObjectContext in some central location and then pass this instance to the repository classes whenever they are being instantiated. Now this new class will be called as UnitOfWork and this class will be responsible for creating the ObjectContext instance and handing over all the repository instances to the controllers.

Unit Of Work

So let us create a separate Repository to which will be used via UnitOfWork class and the ObjectContext will be passed to this class from outside.

public class ContactsRepositoryWithUow : IRepository<Contact>
{
    private SampleDbEntities entities = null;

    public ContactsRepositoryWithUow(SampleDbEntities _entities)
    {
        entities = _entities;
    }

    public IEnumerable<Contact> GetAll(Func<Contact, bool> predicate = null)
    {
        if (predicate != null)
        {
            if (predicate != null)
            {
                return entities.Contacts.Where(predicate);
            }
        }

        return entities.Contacts;
    }

    public Contact Get(Func<Contact, bool> predicate)
    {
        return entities.Contacts.FirstOrDefault(predicate);
    }

    public void Add(Contact entity)
    {
        entities.Contacts.AddObject(entity);
    }

    public void Attach(Contact entity)
    {
        entities.Contacts.Attach(entity);
        entities.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
    }

    public void Delete(Contact entity)
    {
        entities.Contacts.DeleteObject(entity);
    }       
}

Now this Repository class is taking the ObjectContext object from outside(whenever it is being created).

Now if we have to create multiple repositories, we can simply have all the repositories take the ObjectContext object at the time of construction. Now let us see how the UnitOfWork class creates the repository and passes it on to the Controller.

public class UnitOfWork : IDisposable
{
    private SampleDbEntities entities = null;
    public UnitOfWork()
    {
        entities = new SampleDbEntities();
    }

    // Add all the repository handles here
    IRepository<Contact> contactRepository = null;

    // Add all the repository getters here
    public IRepository<Contact> ContactRepository
    {
        get
        {
            if (contactRepository == null)
            {
                contactRepository = new ContactsRepositoryWithUow(entities);
            }
            return contactRepository;
        }
    }

    public void SaveChanges()
    {
        entities.SaveChanges();
    }


    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                entities.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

}

Now let us create one more Controller ContactsUowController that will use the unit of work class to perform the CRUD operations on the contact table.

public class ContactUowController : Controller
{
    private UnitOfWork uow = null; 
    //
    // GET: /Contacts/

    public ContactUowController()
    {
        uow = new UnitOfWork();
    }

    public ContactUowController(UnitOfWork uow_)
    {
        this.uow = uow_;
    }

    public ActionResult Index()
    {
        return View(uow.ContactRepository.GetAll().ToList());
    }

    //
    // GET: /Contacts/Details/5

    public ActionResult Details(int id = 0)
    {
        Contact contact = uow.ContactRepository.Get(c => c.ID == id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    //
    // GET: /Contacts/Create

    public ActionResult Create()
    {
        return View();
    }

    //
    // POST: /Contacts/Create

    [HttpPost]
    public ActionResult Create(Contact contact)
    {
        if (ModelState.IsValid)
        {
            uow.ContactRepository.Add(contact);
            uow.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(contact);
    }

    //
    // GET: /Contacts/Edit/5

    public ActionResult Edit(int id = 0)
    {
        Contact contact = uow.ContactRepository.Get(c => c.ID == id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    //
    // POST: /Contacts/Edit/5

    [HttpPost]
    public ActionResult Edit(Contact contact)
    {
        if (ModelState.IsValid)
        {
            uow.ContactRepository.Attach(contact);
            uow.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(contact);
    }

    //
    // GET: /Contacts/Delete/5

    public ActionResult Delete(int id = 0)
    {
        Contact contact = uow.ContactRepository.Get(c => c.ID == id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    //
    // POST: /Contacts/Delete/5

    [HttpPost, ActionName("Delete")]
    public ActionResult DeleteConfirmed(int id)
    {
        Contact contact = uow.ContactRepository.Get(c => c.ID == id);
        uow.ContactRepository.Delete(contact);
        uow.SaveChanges();
        return RedirectToAction("Index");
    }
}

Now the test-ablity of this controller is still maintained by having the combination of default and parametrized constructor. i.e. the test projects can pass on the UnitOfWork operating in dummy data rather than actual data. Also, the data access code is now centralized in one place with the possibility of having multiple repository classes being instantiated at the same time.

Generic Repository and Unit of Work

Now we have a Repository and UnitOfWork class in place. But the catch here is that if my database contain many tables then I will have to create those many repository classes and my UnitOfWork class need to have those many accessors properties for the repositories.

Wouldn't it be better if we make our repository and UnitOfwork generic that we they will work with all the Model classes. So let us go ahead and implement a generic repository class.

class GenericRepository<T> : IRepository<T> where T : class
{
    private SampleDbEntities entities = null;
    IObjectSet<T> _objectSet;

    public GenericRepository(SampleDbEntities _entities)
    {
        entities = _entities;
        _objectSet = entities.CreateObjectSet<T>();
    }

    public IEnumerable<T> GetAll(Func<T, bool> predicate = null)
    {
        if (predicate != null)
        {
            return _objectSet.Where(predicate);
        }

        return _objectSet.AsEnumerable();
    }

    public T Get(Func<T, bool> predicate)
    {
        return _objectSet.First(predicate);
    }

    public void Add(T entity)
    {
        _objectSet.AddObject(entity);
    }

    public void Attach(T entity)
    {
        _objectSet.Attach(entity);
    }

    public void Delete(T entity)
    {
        _objectSet.DeleteObject(entity);
    }
}

UPDATE: Found a very interesting comment for this article which i think is worth putting in the article.

There are at least 2 overloads for 'Where' method:

public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

When we are using

Func<T, bool>

the query will use the 'IEnumerable' version. In this case, the whole table records will be fetched from the database first and then the predicated will be applied to the final result. To prove it, just check out the produced SQL. It has no where clause.

To fix this issue, we need to change the 'Func' to 'Expression Func'.

Expression<Func<T, bool>> predicate 

Now the 'IQueryable' version of the 'Where' method will be used.

Note: Thus perhaps using Expression Func better idea than using Func.

Now with this generic Repository, we will create a generic Unit of work class that will work with this generic repository. This unit of work class will check if the repository class for a particular type has been create already, same instance will be returned. else a new instance will be returned.

public class GenericUnitOfWork : IDisposable
{
    private SampleDbEntities entities = null;
        
    public GenericUnitOfWork()
    {
        entities = new SampleDbEntities();
    }

    public Dictionary<Type, object> repositories = new Dictionary<Type, object>();

    public IRepository<T> Repository<T>() where T : class
    {
        if (repositories.Keys.Contains(typeof(T)) == true)
        {
            return repositories[typeof(T)] as IRepository<T>
        }
        IRepository<T> repo = new GenericRepository<T>(entities);
        repositories.Add(typeof(T), repo);
        return repo;
    }

    public void SaveChanges()
    {
        entities.SaveChanges();
    }

    private bool disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                entities.Dispose();
            }
        }
        this.disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Now let us create one more Controller GenericContactsController that will use the GenericUnitOfWork class to perform the CRUD operations on the contact table.

public class GenericContactsController : Controller
{
    private GenericUnitOfWork uow = null; 
    //
    // GET: /Contacts/

    public GenericContactsController()
    {
        uow = new GenericUnitOfWork();
    }

    public GenericContactsController(GenericUnitOfWork uow_)
    {
        this.uow = uow_;
    }

    public ActionResult Index()
    {
        return View(uow.Repository<Contact>().GetAll().ToList());
    }

    //
    // GET: /Contacts/Details/5

    public ActionResult Details(int id = 0)
    {
        Contact contact = uow.Repository<Contact>().Get(c => c.ID == id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    //
    // GET: /Contacts/Create

    public ActionResult Create()
    {
        return View();
    }

    //
    // POST: /Contacts/Create

    [HttpPost]
    public ActionResult Create(Contact contact)
    {
        if (ModelState.IsValid)
        {
            uow.Repository<Contact>().Add(contact);
            uow.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(contact);
    }

    //
    // GET: /Contacts/Edit/5

    public ActionResult Edit(int id = 0)
    {
        Contact contact = uow.Repository<Contact>().Get(c => c.ID == id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    //
    // POST: /Contacts/Edit/5

    [HttpPost]
    public ActionResult Edit(Contact contact)
    {
        if (ModelState.IsValid)
        {
            uow.Repository<Contact>().Attach(contact);
            uow.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(contact);
    }

    //
    // GET: /Contacts/Delete/5

    public ActionResult Delete(int id = 0)
    {
        Contact contact = uow.Repository<Contact>().Get(c => c.ID == id);
        if (contact == null)
        {
            return HttpNotFound();
        }
        return View(contact);
    }

    //
    // POST: /Contacts/Delete/5

    [HttpPost, ActionName("Delete")]
    public ActionResult DeleteConfirmed(int id)
    {
        Contact contact = uow.Repository<Contact>().Get(c => c.ID == id);
        uow.Repository<Contact>().Delete(contact);
        uow.SaveChanges();
        return RedirectToAction("Index");
    }
}

And now we have a generic repository class with a unit of work in place.

Point of interest

In this article we saw what is Repository and Unit of work pattern. We have also seen a rudimentary implementation of Repository and unit of work pattern in an ASP.NET MVC application using entity framework. Then we saw how we can implement a generic repository class and get it to work with Unit of work class so that we don't need to create multiple repository classes. I hope this has been informative.

History

  • 07 May 2014: First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Rahul Rajat Singh
Architect
India India
I Started my Programming career with C++. Later got a chance to develop Windows Form applications using C#. Currently using C#, ASP.NET & ASP.NET MVC to create Information Systems, e-commerce/e-governance Portals and Data driven websites.

My interests involves Programming, Website development and Learning/Teaching subjects related to Computer Science/Information Systems. IMO, C# is the best programming language and I love working with C# and other Microsoft Technologies.
  • Microsoft Certified Technology Specialist (MCTS): Web Applications Development with Microsoft .NET Framework 4
  • Microsoft Certified Technology Specialist (MCTS): Accessing Data with Microsoft .NET Framework 4
  • Microsoft Certified Technology Specialist (MCTS): Windows Communication Foundation Development with Microsoft .NET Framework 4

If you like my articles, please visit my website for more: www.rahulrajatsingh.com[^]

  • Microsoft MVP 2015

You may also be interested in...

Comments and Discussions

 
QuestionError on _objectSet = entities.CreateObjectSet<T>(); Pin
manojsitapara9-Jul-15 5:01
membermanojsitapara9-Jul-15 5:01 
AnswerRe: Error on _objectSet = entities.CreateObjectSet<T>(); Pin
vassudev18-Jul-15 9:27
membervassudev18-Jul-15 9:27 
Questionwhose the unit inch goat humper who posted this worthless piece of crapola article Pin
Member 1177111116-Jun-15 14:48
memberMember 1177111116-Jun-15 14:48 
QuestionData enter the DB before Create Action is executed? Pin
Aman Thakur8-May-15 5:16
professionalAman Thakur8-May-15 5:16 
QuestionMy vote of 5 Pin
okanyoman19-Mar-15 23:31
memberokanyoman19-Mar-15 23:31 
Answerrofl okanyoman a dick up a nigger ass is a 5 to you Pin
Member 1177111117-Jun-15 12:50
memberMember 1177111117-Jun-15 12:50 
QuestionTry inject repository in identity context Pin
Member 78101205-Mar-15 16:03
memberMember 78101205-Mar-15 16:03 
GeneralMy vote of 1 Pin
Aurimas15-Nov-14 4:27
memberAurimas15-Nov-14 4:27 
GeneralRe: My vote of 1 Pin
sgissinger20-Nov-14 4:43
membersgissinger20-Nov-14 4:43 
GeneralRe: My vote of 1 Pin
Aurimas20-Nov-14 8:23
memberAurimas20-Nov-14 8:23 
GeneralRe: My vote of 1 Pin
sgissinger20-Nov-14 9:47
membersgissinger20-Nov-14 9:47 
QuestionGenericRepository Pin
jose_leal24-Oct-14 3:25
memberjose_leal24-Oct-14 3:25 
AnswerArticle of the Day on Microsoft's site Pin
Rahul Rajat Singh8-Jun-14 19:18
mvpRahul Rajat Singh8-Jun-14 19:18 
GeneralNice article Pin
devcovato7-Jun-14 7:52
memberdevcovato7-Jun-14 7:52 
Question[My vote of 1] EF DbContext IS a UOW Pin
alec.whittington5-Jun-14 4:23
memberalec.whittington5-Jun-14 4:23 
GeneralGeneric Repo Pin
Member 800608929-May-14 5:27
memberMember 800608929-May-14 5:27 
QuestionDbContext Pin
RahulMisra200017-May-14 16:09
memberRahulMisra200017-May-14 16:09 
AnswerRe: DbContext Pin
Pentamentum17-May-14 22:47
professionalPentamentum17-May-14 22:47 
GeneralMy vote of 1 Pin
RahulMisra200017-May-14 16:08
memberRahulMisra200017-May-14 16:08 
GeneralRe: My vote of 1 Pin
sgissinger20-Nov-14 4:47
membersgissinger20-Nov-14 4:47 
AnswerNice work Pin
Pentamentum15-May-14 7:13
memberPentamentum15-May-14 7:13 
GeneralRe: Nice work Pin
azwags15-May-14 10:48
memberazwags15-May-14 10:48 
GeneralRe: Nice work Pin
Pentamentum17-May-14 9:42
professionalPentamentum17-May-14 9:42 
QuestionThe repositories are passing back Entity Framework objects, not business objects.. Pin
Brent Jenkins15-May-14 1:46
memberBrent Jenkins15-May-14 1:46 
GeneralMy vote of 2 Pin
Dave Boross14-May-14 6:46
memberDave Boross14-May-14 6:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150731.1 | Last Updated 14 May 2014
Article Copyright 2014 by Rahul Rajat Singh
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid