Introduction
This article introduces the Generic Repository pattern and Unit of Work in ASP.NET MVC applications. We are developing an application for a Book entity on which we can do Create, Read, Update and Delete operations. To keep the article simple and to make it easy to understand the Generic Repository pattern and Unit of Work, we use a single book entity in the application.
In the previous article “CRUD Operations Using the Repository Pattern in MVC” , we created a book repository for a book entity and it's good for one entity that does CRUD operations but suppose we have an enterprise level application and that application has more than one entity so we need to create a repository for each entity. In short we can say that we are repeating the code for each entity's repository against DRY (every piece of knowledge must have a single, unambiguous, authoritative representation within a system) principle in the software engineering; that's why we need a generic repository pattern. As in Figure 1.1, two developers have question “Whether to create a new part or to reuse an existing one.” One developer choose the option to create a new one as discussed in the article CRUD Operations Using the Repository Pattern in MVC but another developer choose the second option, to reuse an existing code so that you can reduce code. So now you are the second developer and your approach is Generic Repository Pattern and Unit of Work and it's also the main goal of this article as well.
Figure 1.1 Reuse and Reduce (Courtesy: http://perspectives.3ds.com/ )
The Repository Pattern
The Repository pattern is intended to create an abstraction layer between the data access layer and the business logic layer of an application. It is a data access pattern that prompts a more loosely coupled approach to data access. We create the data access logic in a separate class, or set of classes, called a repository, with the responsibility of persisting the application's business model.
In this article we design a common generic repository for all entities and a Unit of work class. The Unit of Work class creates a repository instance for each entity and the repository is used to do CURD operations. We create an instance of the UnitOfWork class in the controller then create a repository instance depending on the entity and thereafter uses the methods of the repository as per the operations.
The following diagram shows the relationship between the repository and the Entity Framework data context, in which MVC controllers interact with the repository by a Unit of Work rather than directly with the Entity Framework.
Figure 1.2 Generic Repository Pattern and Unit of Work workflow
Why Unit of Work
A Unit of Work, as it's name applies, does something. In this article a Unit of Work does whatever that we create an instance of it then it instantiates our DbContext thereafter each repository instance uses the same DbContext for database operations. So the Unit of Work is the pattern that ensures that all repositories use the same database context.
Implement a Generic Repository and a Unit of Work Class
Note: In this article your user interface uses a concrete class object, not an interface because that concept I will describe in the next article. To keep this short code and only describe the concepts, I removed error handling code from the controller but you should always use error handling in your controller.
In this section of articles we create two projects, one is EF.Core and another is EF.Data. In this article we are working with Entity Framework Code First Approach so the project EF.Core contains entities that are needed in the application's database. In this EF.Data project, we create two entities, one is the BaseEntity class that has common properties that will be inherited by each entity and the other is Book. Let's see each entity. The following is a code snippet for the BaseEntity class.
- using System;
-
- namespace EF.Core
- {
- public abstract class BaseEntity
- {
- public Int64 ID { get; set; }
- public DateTime AddedDate { get; set; }
- public DateTime ModifiedDate { get; set; }
- public string IP { get; set; }
- }
- }
Now we create a Book entity under the Data folder of the EF.Core project which inherits from the BaseEntity class. The following is a code snippet for the Book entity.
- using System;
-
- namespace EF.Core.Data
- {
- public class Book : BaseEntity
- {
- public string Title { get; set; }
- public string Author { get; set; }
- public string ISBN { get; set; }
- public DateTime Published { get; set; }
- }
- }
The EF.Data project contains DataContext, Book entity Mapping, Repository and Unit of Work classes. The ADO.NET Entity Framework Code First data access approach requires us to create a data access context class that inherits from the DbContext class so we create a context class EFDbContext (EFDbContext.cs) class. In this class, we override the OnModelCreating() method. This method is called when the model for a context class (EFDbContext) has been initialized, but before the model has been locked down and used to initialize the context such that the model can be further configured before it is locked down. The following is the code snippet for the context class.
- using System;
- using System.Data.Entity;
- using System.Data.Entity.ModelConfiguration;
- using System.Linq;
- using System.Reflection;
- using EF.Core;
-
- namespace EF.Data
- {
- public class EFDbContext : DbContext
- {
- public EFDbContext()
- : base("name=DbConnectionString")
- {
- }
-
- public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity
- {
- return base.Set<TEntity>();
- }
-
- protected override void OnModelCreating(DbModelBuilder modelBuilder)
- {
- var typesToRegister = Assembly.GetExecutingAssembly().GetTypes()
- .Where(type => !String.IsNullOrEmpty(type.Namespace))
- .Where(type => type.BaseType != null && type.BaseType.IsGenericType
- && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));
- foreach (var type in typesToRegister)
- {
- dynamic configurationInstance = Activator.CreateInstance(type);
- modelBuilder.Configurations.Add(configurationInstance);
- }
- base.OnModelCreating(modelBuilder);
- }
- }
- }
As you know, the EF Code First approach follows convention over configuration, so in the constructor, we just pass the connection string name, the same as an App.Config file and it connects to that server. In the OnModelCreating() method, we used reflection to map an entity to its configuration class in this specific project.
Figure 1.3 Project structure in solution
Now, we define the configuration for the book entity that will be used when the database table will be created by the entity. The configuration defines the class library project EF.Data under the Mapping folder. Now create configuration classes for the entity. For the Book entity, we create the BookMap entity.
The following is a code snippet for the BookMap class.
- using System.ComponentModel.DataAnnotations.Schema;
- using System.Data.Entity.ModelConfiguration;
- using EF.Core.Data;
-
- namespace EF.Data.Mapping
- {
- public class BookMap : EntityTypeConfiguration<Book>
- {
- public BookMap()
- {
- HasKey(t => t.ID);
- Property(t => t.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
- Property(t => t.Title).IsRequired();
- Property(t => t.Author).IsRequired();
- Property(t => t.ISBN).IsRequired();
- Property(t => t.Published).IsRequired();
- ToTable("Books");
- }
- }
- }
Now we create a generic repository class. We are not creating an interface for a repository so that our article will be easy to understand. This generic repository has all CURD operation methods. This repository contains a parameterized constructor with a parameter as Context so when we create an instance of the repository we pass a context so that all the repositories for each entity has the same context. We are using the saveChanges() method of the context but you can also use the save method of the Unit of Work class because both have the same context. The following is a code snippet for the Generic Repository.
Now to create a class for the Unit of Work; that class is UnitOfWork. This class inherits from an IDisposable interface so that its instance will be disposed of in each controller. This class initiates the application DataContext. This class heart is the Repository<T>() method that returns a repository for the entity and that entity inherits from the BaseEntity class. The following is a code snippet for the UnitOfWork class.
- using System;
- using System.Collections.Generic;
- using EF.Core;
-
- namespace EF.Data
- {
- public class UnitOfWork : IDisposable
- {
- private readonly EFDbContext context;
- private bool disposed;
- private Dictionary<string,object> repositories;
-
- public UnitOfWork(EFDbContext context)
- {
- this.context = context;
- }
-
- public UnitOfWork()
- {
- context = new EFDbContext();
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- public void Save()
- {
- context.SaveChanges();
- }
-
- public virtual void Dispose(bool disposing)
- {
- if (!disposed)
- {
- if (disposing)
- {
- context.Dispose();
- }
- }
- disposed = true;
- }
-
- public Repository<T> Repository<T>() where T : BaseEntity
- {
- if (repositories == null)
- {
- repositories = new Dictionary<string,object>();
- }
-
- var type = typeof(T).Name;
-
- if (!repositories.ContainsKey(type))
- {
- var repositoryType = typeof(Repository<>);
- var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), context);
- repositories.Add(type, repositoryInstance);
- }
- return (Repository<T>)repositories[type];
- }
- }
- }
An MVC Application Using the Generic Repository Pattern
Now we create a MVC application (EF.Web) as in Figure 1.3. This is our third project of the application, this project contains user interface for a Book entity's CURD operations and the controller to do these operations. First we proceed to the controller. Create a BookController under the Controllers folder of the application. This controller has all ActionResult methods for each user interface of a CRUD operation. We first create a Unit of Work class instance then the controller's constructor initiates the repository as per the required entity. The following is a code snippet for the BookController.
- using System.Collections.Generic;
- using System.Linq;
- using System.Web.Mvc;
- using EF.Core.Data;
- using EF.Data;
-
- namespace EF.Web.Controllers
- {
- public class BookController : Controller
- {
- private UnitOfWork unitOfWork = new UnitOfWork();
- private Repository<Book> bookRepository;
-
- public BookController()
- {
- bookRepository = unitOfWork.Repository<Book>();
- }
-
- public ActionResult Index()
- {
- IEnumerable<Book> books = bookRepository.Table.ToList();
- return View(books);
- }
-
- public ActionResult CreateEditBook(int? id)
- {
- Book model = new Book();
- if (id.HasValue)
- {
- model = bookRepository.GetById(id.Value);
- }
- return View(model);
- }
-
- [HttpPost]
- public ActionResult CreateEditBook(Book model)
- {
- if (model.ID == 0)
- {
- model.ModifiedDate = System.DateTime.Now;
- model.AddedDate = System.DateTime.Now;
- model.IP = Request.UserHostAddress;
- bookRepository.Insert(model);
- }
- else
- {
- var editModel = bookRepository.GetById(model.ID);
- editModel.Title = model.Title;
- editModel.Author = model.Author;
- editModel.ISBN = model.ISBN;
- editModel.Published = model.Published;
- editModel.ModifiedDate = System.DateTime.Now;
- editModel.IP = Request.UserHostAddress;
- bookRepository.Update(editModel);
- }
-
- if (model.ID > 0)
- {
- return RedirectToAction("Index");
- }
- return View(model);
- }
-
- public ActionResult DeleteBook(int id)
- {
- Book model = bookRepository.GetById(id);
- return View(model);
- }
-
- [HttpPost,ActionName("DeleteBook")]
- public ActionResult ConfirmDeleteBook(int id)
- {
- Book model = bookRepository.GetById(id);
- bookRepository.Delete(model);
- return RedirectToAction("Index");
- }
-
- public ActionResult DetailBook(int id)
- {
- Book model = bookRepository.GetById(id);
- return View(model);
- }
-
- protected override void Dispose(bool disposing)
- {
- unitOfWork.Dispose();
- base.Dispose(disposing);
- }
- }
- }
We have now developed the BookController to handle CURD operation request for a book entity. Now to develop the user interface for the CRUD operations. We develop it for the views for adding and editing a book, a book listing, book delete and book details. Let's see each one by one.
Create / Edit Book View
We create a common view for creating and editing a book such as CreateEditBook.cshtml under the Book folder of the views. We use a date picker to choose the date for the book published date. That is why we write JavaScript code for date picker.
- (function ($) {
- function Book() {
- var $thisthis = this;
- function initializeAddEditBook() {
- $('.datepicker').datepicker({
- "setDate": new Date(),
- "autoclose": true
- });
- }
- $this.init = function () {
- initializeAddEditBook();
- }
- }
- $(function () {
- var self = new Book();
- self.init();
- });
- }(jQuery))
Now define a create/edit book view and the following is a code snippet for CreateEditBook.cshtml.
- @model EF.Core.Data.Book
-
- @{
- ViewBag.Title = "Create Edit Book";
- }
- <div class="book-example panel panel-primary">
- <div class="panel-heading panel-head">Add / Edit Book</div>
- <div class="panel-body">
- @using (Html.BeginForm())
- {
- <div class="form-horizontal">
- <div class="form-group">
- @Html.LabelFor(model => model.Title, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.TextBoxFor(model => model.Title, new { @class = "form-control" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.ISBN, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.TextBoxFor(model => model.ISBN, new { @class = "form-control" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.Author, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.TextBoxFor(model => model.Author, new { @class = "form-control" })
- </div>
- </div>
- <div class="form-group">
- @Html.LabelFor(model => model.Published, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.TextBoxFor(model => model.Published, new { @class = "form-control datepicker" })
- </div>
- </div>
- <div class="form-group">
- <div class="col-lg-8"></div>
- <div class="col-lg-3">
- @Html.ActionLink("Back to List", "Index", null, new { @class = "btn btn-default" })
- <button class="btn btn-success" id="btnSubmit" type="submit">
- Submit
- </button>
- </div>
- </div>
- </div>
- }
- </div>
- </div>
- @section scripts
- {
- <script src="~/Scripts/bootstrap-datepicker.js" type="text/javascript"></script>
- <script src="~/Scripts/book-create-edit.js" type="text/javascript"></script>
- }
Now run the application and call the CreateEditBook() action method with a HttpGet request, then we get the UI as in Figure 1.4 to add a new book to the application.
Figure 1.4 : Add new book UI
When we click on the submit button then the CreateEditBook() action method is called with a HttpPost request and the new data is saved to the database.
Book List View
This is the first view when the application is accessed or the entry point of the application is executed. It shows the book listing as in Figure 1.5. We display book data in tabular format and on this view we create links to add a new book, edit a book, delete a book and the details of a book. This view is an index view and the following is a code snippet for index.cshtml under the Book folder of View.
- @model IEnumerable<EF.Core.Data.Book>
- @using EF.Web.Models
-
- <div class="book-example panel panel-primary">
- <div class="panel-heading panel-head">Books Listing</div>
- <div class="panel-body">
- <a id="createEditBookModal" href="@Url.Action("CreateEditBook")" class="btn btn-success">
- <span class="glyphicon glyphicon-plus"></span>Book
- </a>
-
- <table class="table" style="margin: 4px">
- <tr>
- <th>
- @Html.DisplayNameFor(model => model.Title)
- </th>
- <th>
- @Html.DisplayNameFor(model => model.Author)
- </th>
- <th>
- @Html.DisplayNameFor(model => model.ISBN)
- </th>
- <th>Action
- </th>
-
- <th></th>
- </tr>
- @foreach (var item in Model)
- {
- <tr>
- <td>
- @Html.DisplayFor(modelItem => item.Title)
- </td>
- <td>
- @Html.DisplayFor(modelItem => item.Author)
- </td>
- <td>
- @Html.DisplayFor(modelItem => item.ISBN)
- </td>
- <td>
- @Html.ActionLink("Edit", "CreateEditBook", new { id = item.ID }, new { @class = "btn btn-success" }) |
- @Html.ActionLink("Details", "DetailBook", new { id = item.ID }, new { @class = "btn btn-primary" }) |
- @Html.ActionLink("Delete", "DeleteBook", new { id = item.ID }, new { @class = "btn btn-danger" })
- </td>
- </tr>
- }
-
- </table>
- </div>
- </div>
When we run the application and call the index() action with a HttpGet request then we get all the books listed in the UI as in Figure 1.5. This UI has options for CRUD operations.
Figure 1.5 Books Listing UI.
As in the figure above each book has an option for Edit; when we click on the Edit button then the CreateEditBook() action method is called with a HttpGet request and the UI is shown as in Figure 1.6.
Figure 1.6 Edit a book UI.
Now we change input field data and click on the submit button then the CreateEditBook() action method is called with a HttpPost request and that book data is successfully updated in the database.
Book Detail View
We create a view that shows specific book details when the details button is clicked in the book listing data. We call the DetailBook() action method with a HttpGet request that shows a “Details” view such as in Figure 1.7 so we create a view DetailBook and the following is a code snippet for DetailBook.cshtml.
- @model EF.Core.Data.Book
- @{
- ViewBag.Title = "Detail Book";
- }
- <div class="book-example panel panel-primary">
- <div class="panel-heading panel-head">Book Detail</div>
- <div class="panel-body">
- <div class="form-horizontal">
- <div class="form-group">
- @Html.LabelFor(model => model.Title, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.DisplayFor(model => model.Title, new { @class = "form-control" })
- </div>
- </div>
-
- <div class="form-group">
- @Html.LabelFor(model => model.Author, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.DisplayFor(model => model.Author, new { @class = "form-control" })
- </div>
- </div>
-
- <div class="form-group">
- @Html.LabelFor(model => model.ISBN, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.DisplayFor(model => model.ISBN, new { @class = "form-control" })
- </div>
- </div>
-
- <div class="form-group">
- @Html.LabelFor(model => model.Published, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.DisplayFor(model => model.Published, new { @class = "form-control" })
- </div>
- </div>
-
- <div class="form-group">
- @Html.LabelFor(model => model.AddedDate, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.DisplayFor(model => model.AddedDate, new { @class = "form-control" })
- </div>
- </div>
-
- <div class="form-group">
- @Html.LabelFor(model => model.ModifiedDate, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.DisplayFor(model => model.ModifiedDate, new { @class = "form-control" })
- </div>
- </div>
-
- <div class="form-group">
- @Html.LabelFor(model => model.IP, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.DisplayFor(model => model.IP, new { @class = "form-control" })
- </div>
- </div>
-
- @using (Html.BeginForm())
- {
- <div class="form-group">
- <div class="col-lg-1"></div>
- <div class="col-lg-9">
- @Html.ActionLink("Edit", "CreateEditBook", new { id = Model.ID }, new { @class = "btn btn-primary" })
- @Html.ActionLink("Back to List", "Index", null, new { @class = "btn btn-success" })
- </div>
- </div>
- }
- </div>
- </div>
- </div>
Figure 1.7 Book Detail UI
Delete Book
Delete a book is the last operation of this article. To delete a book we follow the process of clicking on the Delete button that exists in the Book listing data then the book detail view shows to ask “You are sure you want to delete this?” after clicking on the Delete button that exists in the Delete view such as in Figure 1.8. When we click the Delete button of the book list then it makes a HttpGet request that calls the DeleteBook() action method that shows a delete view then clicks on the Delete button of the view then an HttpPost request makes that call to ConfirmDeleteBook() action methods that delete that book.
The following is a code snippet for DeleteBook.cshtml.
- @model EF.Core.Data.Book
-
- @{
- ViewBag.Title = "Delete Book";
- }
-
- <div class="book-example panel panel-primary">
- <div class="panel-heading panel-head">Delete Book</div>
- <div class="panel-body">
- <h3>Are you sure you want to delete this?</h3>
- <h1>@ViewBag.ErrorMessage</h1>
- <div class="form-horizontal">
- <div class="form-group">
- @Html.LabelFor(model => model.Title, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.DisplayFor(model => model.Title, new { @class = "form-control" })
- </div>
- </div>
-
- <div class="form-group">
- @Html.LabelFor(model => model.Author, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.DisplayFor(model => model.Author, new { @class = "form-control" })
- </div>
- </div>
-
- <div class="form-group">
- @Html.LabelFor(model => model.ISBN, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.DisplayFor(model => model.ISBN, new { @class = "form-control" })
- </div>
- </div>
-
- <div class="form-group">
- @Html.LabelFor(model => model.Published, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.DisplayFor(model => model.Published, new { @class = "form-control" })
- </div>
- </div>
-
- <div class="form-group">
- @Html.LabelFor(model => model.AddedDate, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.DisplayFor(model => model.AddedDate, new { @class = "form-control" })
- </div>
- </div>
-
- <div class="form-group">
- @Html.LabelFor(model => model.ModifiedDate, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.DisplayFor(model => model.ModifiedDate, new { @class = "form-control" })
- </div>
- </div>
-
- <div class="form-group">
- @Html.LabelFor(model => model.IP, new { @class = "col-lg-1 control-label" })
- <div class="col-lg-9">
- @Html.DisplayFor(model => model.IP, new { @class = "form-control" })
- </div>
- </div>
-
- @using (Html.BeginForm())
- {
- <div class="form-group">
- <div class="col-lg-1"></div>
- <div class="col-lg-9">
- <input type="submit" value="Delete" class="btn btn-danger" />
- @Html.ActionLink("Back to List", "Index", null, new { @class = "btn btn-success" })
- </div>
- </div>
- }
- </div>
- </div>
- </div>
Figure 1.8 Delete a Book View.
Conclusion
This article introduced the generic repository pattern with unit of work. I used bootstrap CSS and JavaScript for the user interface design in this application. What do you think about this article? Please provide your feedback.