CRUD Operations Using the Generic Repository Pattern and Unit of Work in MVC

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 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 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 the DRY (every piece of knowledge must have a single, unambiguous, authoritative representation within a system) principle in software engineering; that's why we need a generic repository pattern. As in Figure 1, two developers have a question “Whether to create a new part or to reuse an existing one.” One developer chose the option to create a new one as discussed in the article CRUD Operations Using the Repository Pattern in MVC but another developer chose 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.

Reuse and Reduce

Figure 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 use 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.

Repository Pattern

Figure 2. Generic Repository Pattern and Unit of Work Workflow

Why Unit of Work?

A Unit of Work, as its 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 shortcode and only describe the concepts, I removed the error handling code from the controller but you should always use error handling in your controller.

In this section of the 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 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. The 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.

Project structure

Figure 3. Project structure in solution

Now, we define the configuration for the book entity that will be used when the database table is 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.

using System;
using System.Data.Entity;
using System.Data.Entity.Validation;
using System.Linq;
using EF.Core;

namespace EF.Data
{
    public class Repository<T> where T : BaseEntity
    {
        private readonly EFDbContext context;
        private IDbSet<T> entities;
        string errorMessage = string.Empty;

        public Repository(EFDbContext context)
        {
            this.context = context;
        }

        public T GetById(object id)
        {
            return this.Entities.Find(id);
        }

        public void Insert(T entity)
        {
            try
            {
                if (entity == null)
                {
                    throw new ArgumentNullException("entity");
                }
                this.Entities.Add(entity);
                this.context.SaveChanges();
            }
            catch (DbEntityValidationException dbEx)
            {

                foreach (var validationErrors in dbEx.EntityValidationErrors)
                {
                    foreach (var validationError in validationErrors.ValidationErrors)
                    {
                        errorMessage += string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage) + Environment.NewLine;
                    }
                }
                throw new Exception(errorMessage, dbEx);
            }
        }

        public void Update(T entity)
        {
            try
            {
                if (entity == null)
                {
                    throw new ArgumentNullException("entity");
                }
                this.context.SaveChanges();
            }
            catch (DbEntityValidationException dbEx)
            {
                foreach (var validationErrors in dbEx.EntityValidationErrors)
                {
                    foreach (var validationError in validationErrors.ValidationErrors)
                    {
                        errorMessage += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
                    }
                }

                throw new Exception(errorMessage, dbEx);
            }
        }

        public void Delete(T entity)
        {
            try
            {
                if (entity == null)
                {
                    throw new ArgumentNullException("entity");
                }

                this.Entities.Remove(entity);
                this.context.SaveChanges();
            }
            catch (DbEntityValidationException dbEx)
            {

                foreach (var validationErrors in dbEx.EntityValidationErrors)
                {
                    foreach (var validationError in validationErrors.ValidationErrors)
                    {
                        errorMessage += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
                    }
                }
                throw new Exception(errorMessage, dbEx);
            }
        }

        public virtual IQueryable<T> Table
        {
            get
            {
                return this.Entities;
            }
        }

        private IDbSet<T> Entities
        {
            get
            {
                if (entities == null)
                {
                    entities = context.Set<T>();
                }
                return entities;
            }
        }
    }
}

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 an MVC application (EF.Web) as in Figure 3 This is our third project of the application, this project contains a 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 requests 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 the date picker.

(function ($) {
    function Book() {
        var $this = 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 4 to add a new book to the application.

Add new book UI

Figure 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 in Figure 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 the 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 5. This UI has options for CRUD operations.

Books Listing UI

Figure 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 6.

Book UI

Figure 6. Edit a book UI

Now we change the 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 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>

Book Detail

Figure 7. Book Detail UI

Delete Book

Deleting 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 asks “You are sure you want to delete this?” after clicking on the Delete button that exists in the Delete view such as in Figure 8 When we click the Delete button of the book list it makes a HttpGet request that calls the DeleteBook() action method that shows a delete view then clicking on the Delete button of the view an HttpPost request makes that call to ConfirmDeleteBook() action method 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>

Delete

Figure 8. Delete a Book View.

Conclusion

This article introduced the generic repository pattern with the 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.


Similar Articles