Friday, June 8, 2012

Implemention of the Repository and Unit of Work Patterns in an ASP.NET MVC Application

Today, I'm posting about implementing unit of work patterns repository in an Asp.net mvc application

I believe Ninject repository patterns and Unit of Work repository patterns are the best repository patterns in asp.net mvc application

The repository patterns are an abstarction layer. Implementing these patterns perform between the data access layer and the business logic layer of an application. And The repository is very useful in automated unit testing or test-driven developement(TDD).

The diagram image below explains where repository patterns perform in an application

Reference: Tom Dykstra blog

Let's see example of :

create a sample book object repository class with Generic Repoistory class

In DAL folder or service folder or repository folder, create this class file.

1. this code should be in interface folder ( without UnitofWork class)

   1:  using System; 
   2:  using System.Collections.Generic; 
   3:  using System.Linq; 
   4:  using System.Web; 
   5:  using MyApplication.Models; 
   6:   
   7:  namespace MyApplication.DAL 
   8:  { 
   9:      public interface IBookRepository : IDisposable 
  10:      { 
  11:          IEnumerable<Student> GetBooks(); 
  12:          Book GetBookByID(int bookId); 
  13:          void InsertBook(Book book); 
  14:          void DeleteBook(int bookID); 
  15:          void UpdateBook(Book book); 
  16:          void Save(); 
  17:      } 
  18:  }

2. this code should be in repository folder ( without UnitofWork class)

   1:  using System; 
   2:  using System.Collections.Generic; 
   3:  using System.Linq; 
   4:  using System.Data; 
   5:  using MyApplication.Models; 
   6:   
   7:  namespace MyApplication.DAL 
   8:  { 
   9:      public class BookRepository : IBookRepository, IDisposable 
  10:      { 
  11:          private SchoolContext context; 
  12:   
  13:          public BookRepository(SchoolContext context) 
  14:          { 
  15:              this.context = context; 
  16:          } 
  17:   
  18:          public IEnumerable<Book> GetBooks() 
  19:          { 
  20:              return context.Books.ToList(); 
  21:          } 
  22:   
  23:          public Book GetBookByID(int id) 
  24:          { 
  25:              return context.Books.Find(id); 
  26:          } 
  27:   
  28:          public void InsertBook(Book book) 
  29:          { 
  30:              context.Books.Add(book); 
  31:          } 
  32:   
  33:          public void DeleteBook(int bookID) 
  34:          { 
  35:              Book book = context.Books.Find(bookID); 
  36:              context.Books.Remove(book); 
  37:          } 
  38:   
  39:          public void UpdateBook(Book book) 
  40:          { 
  41:              context.Entry(book).State = EntityState.Modified; 
  42:          } 
  43:   
  44:          public void Save() 
  45:          { 
  46:              context.SaveChanges(); 
  47:          } 
  48:   
  49:          private bool disposed = false; 
  50:   
  51:          protected virtual void Dispose(bool disposing) 
  52:          { 
  53:              if (!this.disposed) 
  54:              { 
  55:                  if (disposing) 
  56:                  { 
  57:                      context.Dispose(); 
  58:                  } 
  59:              } 
  60:              this.disposed = true; 
  61:          } 
  62:   
  63:          public void Dispose() 
  64:          { 
  65:              Dispose(true); 
  66:              GC.SuppressFinalize(this); 
  67:          } 
  68:      } 
  69:  }

Then, the following codes show how to use the repository in the book controller.

Using book respoitory in book controller ( without UnitofWork class)

   1:  using System; 
   2:  using System.Collections.Generic; 
   3:  using System.Data; 
   4:  using System.Data.Entity; 
   5:  using System.Linq; 
   6:  using System.Web; 
   7:  using System.Web.Mvc; 
   8:  using MyApplication.Models; 
   9:  using MyApplication.DAL; 
  10:  using PagedList; 
  11:   
  12:  namespace MyApplication.Controllers 
  13:  { 
  14:      public class BookController : Controller 
  15:      { 
  16:          private IBookRepository bookRepository; 
  17:   
  18:   
  19:          public BookController() 
  20:          { 
  21:              this.bookRepository = new BookRepository(new BookStoreContext()); 
  22:          } 
  23:   
  24:          public StudentController(IBookRepository bookRepository) 
  25:          { 
  26:              this.bookRepository = bookRepository; 
  27:          } 
  28:   
  29:   
  30:          // 
  31:          // GET: /Book/ 
  32:   
  33:          public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page) 
  34:          { 
  35:              ViewBag.CurrentSort = sortOrder; 
  36:              ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "Name desc" : ""; 
  37:              ViewBag.DateSortParm = sortOrder == "Date" ? "Date desc" : "Date"; 
  38:   
  39:              if (Request.HttpMethod == "GET") 
  40:              { 
  41:                  searchString = currentFilter; 
  42:              } 
  43:              else 
  44:              { 
  45:                  page = 1; 
  46:              } 
  47:              ViewBag.CurrentFilter = searchString; 
  48:               
  49:              var books = from s in bookRepository.GetBooks() 
  50:                             select s; 
  51:              if (!String.IsNullOrEmpty(searchString)) 
  52:              { 
  53:                  books = books.Where(s => s.Name.ToUpper().Contains(searchString.ToUpper()) 
  54:                                         || s.AutherName.ToUpper().Contains(searchString.ToUpper())); 
  55:              } 
  56:              switch (sortOrder) 
  57:              { 
  58:                  case "Name desc": 
  59:                      books = books.OrderByDescending(s => s.Name); 
  60:                      break; 
  61:                  case "Date": 
  62:                      books = books.OrderBy(s => s.PublishedDate); 
  63:                      break; 
  64:                  case "Date desc": 
  65:                      books = books.OrderByDescending(s => s.PublishedDate); 
  66:                      break; 
  67:                  default: 
  68:                      books = books.OrderBy(s => s.Name); 
  69:                      break; 
  70:              } 
  71:   
  72:              int pageSize = 3; 
  73:              int pageNumber = (page ?? 1); 
  74:              return View(books.ToPagedList(pageNumber, pageSize)); 
  75:          } 
  76:   
  77:   
  78:          // 
  79:          // GET: /Book/Details/5 
  80:   
  81:          public ViewResult Details(int id) 
  82:          { 
  83:              Book book = bookRepository.GetBookByID(id); 
  84:              return View(book); 
  85:          } 
  86:   
  87:          // 
  88:          // GET: /Book/Create 
  89:   
  90:          public ActionResult Create() 
  91:          { 
  92:              return View(); 
  93:          } 
  94:   
  95:          // 
  96:          // POST: /Book/Create 
  97:   
  98:          [HttpPost] 
  99:          public ActionResult Create(Book book) 
 100:          { 
 101:              try 
 102:              { 
 103:                  if (ModelState.IsValid) 
 104:                  { 
 105:                      BookRepository.InsertBook(book); 
 106:                      bookRepository.Save(); 
 107:                      return RedirectToAction("Index"); 
 108:                  } 
 109:              } 
 110:              catch (DataException) 
 111:              { 
 112:                  //Log the error (add a variable name after DataException) 
 113:                  ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator."); 
 114:              } 
 115:              return View(book); 
 116:          } 
 117:   
 118:          // 
 119:          // GET: /Book/Edit/5 
 120:   
 121:          public ActionResult Edit(int id) 
 122:          { 
 123:              Book book = bookRepository.GetBookByID(id); 
 124:              return View(book); 
 125:          } 
 126:   
 127:          // 
 128:          // POST: /Book/Edit/5 
 129:   
 130:          [HttpPost] 
 131:          public ActionResult Edit(Book book) 
 132:          { 
 133:              try 
 134:              { 
 135:                  if (ModelState.IsValid) 
 136:                  { 
 137:                      bookRepository.UpdateBook(book); 
 138:                      bookRepository.Save(); 
 139:                      return RedirectToAction("Index"); 
 140:                  } 
 141:              } 
 142:              catch (DataException) 
 143:              { 
 144:                  //Log the error (add a variable name after DataException) 
 145:                  ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator."); 
 146:              } 
 147:              return View(book); 
 148:          } 
 149:   
 150:          
 151:      // 
 152:          // GET: /Book/Delete/5 
 153:   
 154:          public ActionResult Delete(int id, bool? saveChangesError) 
 155:          { 
 156:              if (saveChangesError.GetValueOrDefault()) 
 157:              { 
 158:                  ViewBag.ErrorMessage = "Unable to save changes. Try again, and if the problem persists see your system administrator."; 
 159:              } 
 160:              Book book = bookRepository.GetBookByID(id); 
 161:              return View(book); 
 162:          } 
 163:   
 164:   
 165:          // 
 166:          // POST: /Book/Delete/5 
 167:   
 168:          [HttpPost, ActionName("Delete")] 
 169:          public ActionResult DeleteConfirmed(int id) 
 170:          { 
 171:              try 
 172:              { 
 173:                  Book book = bookRepository.GetBookByID(id); 
 174:                  bookRepository.DeleteBook(id); 
 175:                  bookRepository.Save(); 
 176:              } 
 177:              catch (DataException) 
 178:              { 
 179:                  //Log the error (add a variable name after DataException) 
 180:                  return RedirectToAction("Delete", 
 181:                      new System.Web.Routing.RouteValueDictionary {  
 182:                  { "id", id },  
 183:                  { "saveChangesError", true } }); 
 184:              } 
 185:              return RedirectToAction("Index"); 
 186:          } 
 187:     
 188:   
 189:          protected override void Dispose(bool disposing) 
 190:          { 
 191:              BookRepository.Dispose(); 
 192:              base.Dispose(disposing); 
 193:          } 
 194:      } 
 195:  }
------------------------- End (Without UnitofWork repository) ------------------

In the CRUD methods, the repository is now called instead of the entity model context ( I've assumed that you are familiar with entity edmx model).

Creating a repository class for each entity could result in a lot of redundant code. If you look up IBookRepository class and BookRepository class, you may think this repository is wasting the time writing all (GetbyId, InsertBook, updateBook, etc ..)

In this case, you need to use Generic repository class which redundants these functions (GetbyId, InsertBook, updateBook, etc ..) don't need to write every object respositorys that you create. You only need to write them once in the generic repoistory class. Then, all the other entity object repository can inheritances from generic repository class.

Here is an example why the generic repository should use:

Suppose you have to update two different entities that part of the same transaction. If each entity uses a separate database context instance, One might success and the other might fail. So, one way to minimize the redundant code and to ensure all repositories use the same database context are to use a unit of work class with Generic repository class.

The following codes are shown how to create generic repository class.

   1:  using System; 
   2:  using System.Collections.Generic; 
   3:  using System.Linq; 
   4:  using System.Data; 
   5:  using System.Data.Entity; 
   6:  using MyApplication.Models; 
   7:  using System.Linq.Expressions; 
   8:   
   9:  namespace MyApplication.DAL 
  10:  { 
  11:      public class GenericRepository<TEntity> where TEntity : class 
  12:      { 
  13:          internal SchoolContext context; 
  14:          internal DbSet<TEntity> dbSet; 
  15:   
  16:          public GenericRepository(SchoolContext context) 
  17:          { 
  18:              this.context = context; 
  19:              this.dbSet = context.Set<TEntity>(); 
  20:          } 
  21:   
  22:          public virtual IEnumerable<TEntity> Get( 
  23:              Expression<Func<TEntity, bool>> filter = null, 
  24:              Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, 
  25:              string includeProperties = "") 
  26:          { 
  27:              IQueryable<TEntity> query = dbSet; 
  28:   
  29:              if (filter != null) 
  30:              { 
  31:                  query = query.Where(filter); 
  32:              } 
  33:   
  34:              foreach (var includeProperty in includeProperties.Split 
  35:                  (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) 
  36:              { 
  37:                  query = query.Include(includeProperty); 
  38:              } 
  39:   
  40:              if (orderBy != null) 
  41:              { 
  42:                  return orderBy(query).ToList(); 
  43:              } 
  44:              else 
  45:              { 
  46:                  return query.ToList(); 
  47:              } 
  48:          } 
  49:   
  50:          public virtual TEntity GetByID(object id) 
  51:          { 
  52:              return dbSet.Find(id); 
  53:          } 
  54:   
  55:          public virtual void Insert(TEntity entity) 
  56:          { 
  57:              dbSet.Add(entity); 
  58:          } 
  59:   
  60:          public virtual void Delete(object id) 
  61:          { 
  62:              TEntity entityToDelete = dbSet.Find(id); 
  63:              Delete(entityToDelete); 
  64:          } 
  65:   
  66:          public virtual void Delete(TEntity entityToDelete) 
  67:          { 
  68:              if (context.Entry(entityToDelete).State == EntityState.Detached) 
  69:              { 
  70:                  dbSet.Attach(entityToDelete); 
  71:              } 
  72:              dbSet.Remove(entityToDelete); 
  73:          } 
  74:   
  75:          public virtual void Update(TEntity entityToUpdate) 
  76:          { 
  77:              dbSet.Attach(entityToUpdate); 
  78:              context.Entry(entityToUpdate).State = EntityState.Modified; 
  79:          } 
  80:      } 
  81:  }

I assume you notice that "Expression> filter" and "Func, IOrderedQueryable> orderBy" in the Get in the generic repository class.

   1:  Expression<Func<TEntity, bool>>

It means the caller will provide a lambda expression based on the TEntity type, and this expression returns a Boolean value. For example, if the repository is instantiated for the Book entity type, the code in the calling method might specify book => book.Name == "Skyfall" for the filter parameter.

   1:  Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy

It means the caller will provide a lambda expression. And the input to the expression is an IQueryable object for the TEntity type. The expression returns an ordered version of that IQueryable object. For example, if the repository is instantiated for the Student entity type, the code in the calling method might specify m => m.OrderBy(book => book.Name) for the orderBy parameter.

Creating Unit of Work class with generic repository class

In DAL folder, replace all the entity types repository class ( E.g BookRepository, CustomerRepoistory ) with this UnitofWork class and generic repository class

   1:  using System; 
   2:  using MyApplication.Models; 
   3:   
   4:  namespace MyApplication.DAL 
   5:  { 
   6:      public class UnitOfWork : IDisposable 
   7:      { 
   8:          private BookStoreContext context = new BookStoreContext(); 
   9:          private GenericRepository<Book> bookRepository; 
  10:          private GenericRepository<Customer> customerRepository; 
  11:   
  12:          public GenericRepository<Book> BookRepository 
  13:          { 
  14:              get 
  15:              { 
  16:   
  17:                  if (this.bookRepository == null) 
  18:                  { 
  19:                      this.bookRepository = new GenericRepository<Book>(context); 
  20:                  } 
  21:                  return bookRepository; 
  22:              } 
  23:          } 
  24:   
  25:          public GenericRepository<Customer> CustomerRepository 
  26:          { 
  27:              get 
  28:              { 
  29:   
  30:                  if (this.customerRepository == null) 
  31:                  { 
  32:                      this.customerRepository = new GenericRepository<Customer>(context); 
  33:                  } 
  34:                  return customerRepository; 
  35:              } 
  36:          } 
  37:   
  38:          public void Save() 
  39:          { 
  40:              context.SaveChanges(); 
  41:          } 
  42:   
  43:          private bool disposed = false; 
  44:   
  45:          protected virtual void Dispose(bool disposing) 
  46:          { 
  47:              if (!this.disposed) 
  48:              { 
  49:                  if (disposing) 
  50:                  { 
  51:                      context.Dispose(); 
  52:                  } 
  53:              } 
  54:              this.disposed = true; 
  55:          } 
  56:   
  57:          public void Dispose() 
  58:          { 
  59:              Dispose(true); 
  60:              GC.SuppressFinalize(this); 
  61:          } 
  62:      } 
  63:  }
  64:   
  65:   
  66:   
  67:  <h3>Creating CustomerController using with UnitofWork class</h3>
  68:   
  69:  using System; 
  70:  using System.Collections.Generic; 
  71:  using System.Data; 
  72:  using System.Data.Entity; 
  73:  using System.Linq; 
  74:  using System.Web; 
  75:  using System.Web.Mvc; 
  76:  using MyApplicaiton.Models; 
  77:  using MyApplicaiton.DAL; 
  78:   
  79:  namespace MyApplicaiton.Controllers 
  80:  {  
  81:      public class CustomerController : Controller 
  82:      { 
  83:          private UnitOfWork unitOfWork = new UnitOfWork(); 
  84:   
  85:          // 
  86:          // GET: /Customer/ 
  87:   
  88:          public ViewResult Index() 
  89:          { 
  90:              var customers = unitOfWork.CustomerRepository.Get(includeProperties: "Department"); 
  91:              return View(customers.ToList()); 
  92:          } 
  93:   
  94:          // 
  95:          // GET: /Customer/Details/5 
  96:   
  97:          public ViewResult Details(int id) 
  98:          { 
  99:              Customer customer = unitOfWork.CustomerRepository.GetByID(id); 
 100:              return View(customer); 
 101:          } 
 102:   
 103:          // 
 104:          // GET: /Customer/Create 
 105:   
 106:          public ActionResult Create() 
 107:          { 
 108:              PopulateDepartmentsDropDownList(); 
 109:              return View(); 
 110:          } 
 111:   
 112:          [HttpPost] 
 113:          public ActionResult Create(Customer customer) 
 114:          { 
 115:              try 
 116:              { 
 117:                  if (ModelState.IsValid) 
 118:                  { 
 119:                      unitOfWork.CustomerRepository.Insert(customer); 
 120:                      unitOfWork.Save(); 
 121:                      return RedirectToAction("Index"); 
 122:                  } 
 123:              } 
 124:              catch (DataException) 
 125:              { 
 126:                  //Log the error (add a variable name after DataException) 
 127:                  ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator."); 
 128:              } 
 129:              PopulateDepartmentsDropDownList(customer.ID); 
 130:              return View(customer); 
 131:          } 
 132:   
 133:          public ActionResult Edit(int id) 
 134:          { 
 135:              Customer customer = unitOfWork.CustomerRepository.GetByID(id); 
 136:              PopulateDepartmentsDropDownList(customer.ID); 
 137:              return View(customer); 
 138:          } 
 139:   
 140:          [HttpPost] 
 141:          public ActionResult Edit(Customer customer) 
 142:          { 
 143:              try 
 144:              { 
 145:                  if (ModelState.IsValid) 
 146:                  { 
 147:                      unitOfWork.CustomerRepository.Update(customer); 
 148:                      unitOfWork.Save(); 
 149:                      return RedirectToAction("Index"); 
 150:                  } 
 151:              } 
 152:              catch (DataException) 
 153:              { 
 154:                  //Log the error (add a variable name after DataException) 
 155:                  ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator."); 
 156:              } 
 157:              
 158:              return View(customer); 
 159:          } 
 160:   
 161:          
 162:   
 163:          // 
 164:          // GET: /Customer/Delete/5 
 165:    
 166:          public ActionResult Delete(int id) 
 167:          { 
 168:              Customer customer = unitOfWork.CustomerRepository.GetByID(id); 
 169:              return View(customer); 
 170:          } 
 171:   
 172:          // 
 173:          // POST: /Customer/Delete/5 
 174:   
 175:          [HttpPost, ActionName("Delete")] 
 176:          public ActionResult DeleteConfirmed(int id) 
 177:          { 
 178:              Customer customer = unitOfWork.CustomerRepository.GetByID(id); 
 179:              unitOfWork.CustomerRepository.Delete(id); 
 180:              unitOfWork.Save(); 
 181:              return RedirectToAction("Index"); 
 182:          } 
 183:   
 184:          protected override void Dispose(bool disposing) 
 185:          { 
 186:              unitOfWork.Dispose(); 
 187:              base.Dispose(disposing); 
 188:          } 
 189:      } 
 190:  }

reference: Tom Dykstra blog
Url:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

No comments: