In previous
tutorial you used basic CRUD (Create, Read, Update and Delete)
functionality with entity framework and by using jquery dialog(s) in asp.net
mvc. In this you’ll see some ways to use the repository and unit of work
patterns for CRUD operations.
The repository and unit of work patterns are intended to
create an abstraction layer between the data access layer and the business
logic layer of an application. Implementing these patterns can help insulate
your application from changes in the data store and facilitate automated unit
testing or test-driven development (TDD).
Let’s start with the implementation of Repository and Unit
of Work patterns in a tutorial. If you have gone through my previous
tutorial then start with where it ends and if you do not have experience
about previous tutorial please download the sample of previous
tutorial and start with that.
1) In
the current solution, add three new class library project:
a) Sample.Model
b) Sample.Data.Contracts
c) Sample.Data
Our solution explorer will something look like this:
ManagingMultijQueryForm
Sample.Data
Sample.Data.Contracts
Sample.Model
2) Delete
Category, SubCategory and Product class from the web project i.e. ManagingMultijQueryForm
and add these classes in Sample.Model project. Also add reference of System.ComponentModel.DataAnnotations in
order to use the (Required and Display) attribute.
a) Category
class (Category.cs):
public class Category
{
public int
CategoryID { get; set; }
[Required]
public string Name
{ get; set; }
}
b) SubCategory
class (SubCategory.cs):
public class SubCategory
{
public int
SubCategoryID { get; set; }
public virtual Category
Category { get; set; }
[Display(Name
= "Category")]
public int
CategoryID { get; set; }
[Required]
public string Name
{ get; set; }
}
c) Product
class (Product.cs):
public class Product
{
public int
ProductID { get; set; }
[Required]
public string Name
{ get; set; }
[Required]
public string
Description { get; set; }
public virtual SubCategory
SubCategory { get; set; }
[Display(Name
= "Sub Category")]
public int
SubCategoryID { get; set; }
}
3) In
Sample.Data.Contracts add two interface IRepository (for repositories) and IProductUow
( for unit of work). Before creating the interfaces add the reference of Sample.Model
in Sample.Data.Contracts
a) IRepository.cs,
it is generic interface:
public interface IRepository<T>
where T: class
{
IQueryable<T>
GetAll();
T GetById(int id);
void
Add(T entity);
void
Update(T entity);
void
Delete(T entity);
void
Delete(int id);
}
b) IProductUow.cs
:
public interface IProductUow
{
//
Save pending changes to the data store.
void
Commit();
//
Repositories
IRepository<Product>
Products { get; }
IRepository<Category>
Categories { get; }
IRepository<SubCategory>
SubCategories { get; }
}
4)
In Sample.Data project, add the reference of Sample.Model and
Sample.Data.Contracts and install entity framework via package manager console (Tools
à Library Package Manager à Package Manager Console) by using
following command:
Install-package entityframework
5) Delete
ProductDbContext, ProductDbContextInitializer from the web project and add
these classes in Sample.Data project:
a) ProductDbContext
class (ProductDbContext.cs):
public class ProductDbContext : DbContext
{
public
ProductDbContext()
: base(nameOrConnectionString:
"Data Source=SONY-VAIO\\SQLEXPRESS;Initial
Catalog=ProductDbContext;Integrated Security=true;")
{
Database.SetInitializer(new ProductDbContextInitializer());
}
protected override void
OnModelCreating(DbModelBuilder modelBuilder)
{
//
Use singular table names
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
public DbSet<Product>
Product { get; set; }
public DbSet<Category>
Category { get; set; }
public DbSet<SubCategory>
SubCategory { get; set; }
}
b) ProductDbContextInitializer
class (ProductDbContextInitializer.cs)
public class ProductDbContextInitializer : DropCreateDatabaseIfModelChanges<ProductDbContext>
{
protected override void
Seed(ProductDbContext context)
{
List<Category>
categories = new List<Category>
{
new Category {
Name = "Food" },
new Category {
Name = "Electronics" },
new Category {
Name = "Beverages" }
};
List<SubCategory>
subcategories = new List<SubCategory>
{
new SubCategory {
Name = "Vegetables", Category =
categories.Find(m => m.Name.Equals("Food")) },
new SubCategory {
Name = "Fruits", Category =
categories.Find(m => m.Name.Equals("Food")) },
new SubCategory {
Name = "Computer", Category =
categories.Find(m => m.Name.Equals("Electronics")) },
new SubCategory {
Name = "Television", Category =
categories.Find(m => m.Name.Equals("Electronics")) },
new SubCategory {
Name = "Cold Drinks", Category =
categories.Find(m => m.Name.Equals("Beverages")) },
new SubCategory {
Name = "Beer", Category =
categories.Find(m => m.Name.Equals("Beverages")) },
};
List<Product>
products = new List<Product>
{
new Product {
Name = "Wheat", Description = "Wheat",
SubCategory = subcategories.Find(m => m.Name.Equals("Vegetables")) },
new Product {
Name = "Apple", Description = "Apple",
SubCategory = subcategories.Find(m => m.Name.Equals("Fruits")) },
new Product {
Name = "Desktop", Description = "Desktop",
SubCategory = subcategories.Find(m => m.Name.Equals("Computer")) },
new Product {
Name = "LG", Description = "LG",
SubCategory = subcategories.Find(m => m.Name.Equals("Television")) },
new Product {
Name = "Sprite", Description = "Sprite",
SubCategory = subcategories.Find(m => m.Name.Equals("Cold
Drinks")) },
new Product {
Name = "Kingfisher", Description = "Kingfisher",
SubCategory = subcategories.Find(m => m.Name.Equals("Beer")) },
};
categories.ForEach(m => context.Category.Add(m));
subcategories.ForEach(m => context.SubCategory.Add(m));
products.ForEach(m => context.Product.Add(m));
context.SaveChanges();
base.Seed(context);
}
}
c) Create
new EFRepository class which implements IRepository interface for
doing CRUD by using entity framework:
/// <summary>
/// The
EF-dependent, generic repository for data access
/// </summary>
/// <typeparam
name="T">Type of entity for this Repository.</typeparam>
public class EFRepository<T>
: IRepository<T> where T : class
{
public
EFRepository(DbContext dbContext)
{
if
(dbContext == null)
throw new ArgumentNullException("dbContext");
DbContext
= dbContext;
DbSet =
DbContext.Set<T>();
}
protected DbContext
DbContext { get; set; }
protected DbSet<T>
DbSet { get; set; }
public virtual IQueryable<T>
GetAll()
{
return
DbSet;
}
public virtual T
GetById(int id)
{
return
DbSet.Find(id);
}
public virtual void
Add(T entity)
{
DbEntityEntry
dbEntityEntry = DbContext.Entry(entity);
if
(dbEntityEntry.State != EntityState.Detached)
{
dbEntityEntry.State = EntityState.Added;
}
else
{
DbSet.Add(entity);
}
}
public virtual void
Update(T entity)
{
DbEntityEntry
dbEntityEntry = DbContext.Entry(entity);
if
(dbEntityEntry.State == EntityState.Detached)
{
DbSet.Attach(entity);
}
dbEntityEntry.State = EntityState.Modified;
}
public virtual void
Delete(T entity)
{
DbEntityEntry
dbEntityEntry = DbContext.Entry(entity);
if
(dbEntityEntry.State != EntityState.Deleted)
{
dbEntityEntry.State = EntityState.Deleted;
}
else
{
DbSet.Attach(entity);
DbSet.Remove(entity);
}
}
public virtual void
Delete(int id)
{
var
entity = GetById(id);
if
(entity == null) return; //
not found; assume already deleted.
Delete(entity);
}
}
6) Add Helpers
folder in Sample.Data project and in that folder create a interface
IRepositoryProvider and two classes (RepositoryProvider and RepositoryFactories)
in order to create and find repository by using factory pattern:
a) IRepositoryProvider.cs
/// <summary>
///
Interface for a class that can provide repositories by type.
/// The
class may create the repositories dynamically if it is unable
/// to
find one in its cache of repositories.
/// </summary>
/// <remarks>
///
Repositories created by this provider tend to require a <see
cref="DbContext"/>
/// to
retrieve data.
/// </remarks>
public interface IRepositoryProvider
{
/// <summary>
/// Get
and set the <see cref="DbContext"/> with
which to initialize a repository
/// if
one must be created.
/// </summary>
DbContext
DbContext { get; set; }
/// <summary>
/// Get
an <see cref="IRepository{T}"/> for
entity type, T.
/// </summary>
/// <typeparam
name="T">
/// Root
entity type of the <see cref="IRepository{T}"/>.
/// </typeparam>
IRepository<T>
GetRepositoryForEntityType<T>() where T : class;
/// <summary>
/// Get
a repository of type T.
/// </summary>
/// <typeparam
name="T">
/// Type
of the repository, typically a custom repository interface.
/// </typeparam>
/// <param
name="factory">
/// An
optional repository creation function that takes a <see
cref="DbContext"/>
/// and
returns a repository of T. Used if the repository must be created.
/// </param>
/// <remarks>
///
Looks for the requested repository in its cache, returning if found.
/// If
not found, tries to make one with the factory, fallingback to
/// a
default factory if the factory parameter is null.
/// </remarks>
T
GetRepository<T>(Func<DbContext, object>
factory = null) where T : class;
/// <summary>
/// Set
the repository to return from this provider.
/// </summary>
/// <remarks>
/// Set
a repository if you don't want this provider to create one.
///
Useful in testing and when developing without a backend
///
implementation of the object returned by a repository of type T.
/// </remarks>
void
SetRepository<T>(T repository);
}
b) RepositoryProvider.cs
/// <summary>
///
Provides an <see cref="IRepository{T}"/> for
a client request.
/// </summary>
/// <remarks>
///
Caches repositories of a given type so that repositories are only created once
per provider.
///
Product application creates a new provider per client request.
/// </remarks>
public class RepositoryProvider : IRepositoryProvider
{
public
RepositoryProvider(RepositoryFactories
repositoryFactories)
{
_repositoryFactories = repositoryFactories;
Repositories = new Dictionary<Type, object>();
}
/// <summary>
/// Get
and set the <see cref="DbContext"/> with
which to initialize a repository
/// if
one must be created.
/// </summary>
public DbContext
DbContext { get; set; }
/// <summary>
/// Get
or create-and-cache the default <see
cref="IRepository{T}"/> for an entity of type T.
/// </summary>
/// <typeparam
name="T">
/// Root
entity type of the <see cref="IRepository{T}"/>.
/// </typeparam>
/// <remarks>
/// If
can't find repository in cache, use a factory to create one.
/// </remarks>
public IRepository<T>
GetRepositoryForEntityType<T>() where T : class
{
return
GetRepository<IRepository<T>>(
_repositoryFactories.GetRepositoryFactoryForEntityType<T>());
}
/// <summary>
/// Get
or create-and-cache a repository of type T.
/// </summary>
/// <typeparam
name="T">
/// Type
of the repository, typically a custom repository interface.
/// </typeparam>
/// <param
name="factory">
/// An
optional repository creation function that takes a DbContext argument
/// and
returns a repository of T. Used if the repository must be created and
///
caller wants to specify the specific factory to use rather than one
/// of
the injected <see cref="RepositoryFactories"/>.
/// </param>
/// <remarks>
///
Looks for the requested repository in its cache, returning if found.
/// If
not found, tries to make one using <see
cref="MakeRepository{T}"/>.
/// </remarks>
public virtual T
GetRepository<T>(Func<DbContext, object>
factory = null) where T : class
{
//
Look for T dictionary cache under typeof(T).
object
repoObj;
Repositories.TryGetValue(typeof(T), out
repoObj);
if
(repoObj != null)
{
return
(T)repoObj;
}
//
Not found or null; make one, add to dictionary cache, and return it.
return
MakeRepository<T>(factory, DbContext);
}
/// <summary>
/// Get
the dictionary of repository objects, keyed by repository type.
/// </summary>
/// <remarks>
///
Caller must know how to cast the repository object to a useful type.
/// <p>This
is an extension point. You can register fully made repositories here
/// and
they will be used instead of the ones this provider would otherwise create.</p>
/// </remarks>
protected Dictionary<Type, object>
Repositories { get; private set; }
/// <summary>Make
a repository of type T.</summary>
/// <typeparam
name="T">Type of repository to make.</typeparam>
/// <param
name="dbContext">
/// The <see
cref="DbContext"/> with which to initialize the
repository.
/// </param>
/// <param
name="factory">
///
Factory with <see cref="DbContext"/>
argument. Used to make the repository.
/// If
null, gets factory from <see cref="_repositoryFactories"/>.
/// </param>
/// <returns></returns>
protected virtual T
MakeRepository<T>(Func<DbContext, object>
factory, DbContext dbContext)
{
var f =
factory ?? _repositoryFactories.GetRepositoryFactory<T>();
if (f
== null)
{
throw new NotImplementedException("No
factory for repository type, " + typeof(T).FullName);
}
var repo
= (T)f(dbContext);
Repositories[typeof(T)] = repo;
return
repo;
}
/// <summary>
/// Set
the repository for type T that this provider should return.
/// </summary>
/// <remarks>
/// Plug
in a custom repository if you don't want this provider to create one.
///
Useful in testing and when developing without a backend
///
implementation of the object returned by a repository of type T.
/// </remarks>
public void
SetRepository<T>(T repository)
{
Repositories[typeof(T)] = repository;
}
/// <summary>
/// The <see
cref="RepositoryFactories"/> with which to create a new
repository.
/// </summary>
/// <remarks>
///
Should be initialized by constructor injection
/// </remarks>
private RepositoryFactories
_repositoryFactories;
}
c) RepositoryFactories.cs
/// <summary>
/// A
maker of Product app Repositories.
/// </summary>
/// <remarks>
/// An
instance of this class contains repository factory functions for different
types.
/// Each
factory function takes an EF <see cref="DbContext"/> and
returns
/// a
repository bound to that DbContext.
/// <para>
///
Designed to be a "Singleton", configured at web application start
with
/// all
of the factory functions needed to create any type of repository.
///
Should be thread-safe to use because it is configured at app start,
///
before any request for a factory, and should be immutable thereafter.
/// </para>
/// </remarks>
public class RepositoryFactories
{
/// <summary>
///
Return the runtime Product app repository factory functions,
/// each
one is a factory for a repository of a particular type.
/// </summary>
/// <remarks>
///
MODIFY THIS METHOD TO ADD CUSTOM FACTORY FUNCTIONS
/// </remarks>
private IDictionary<Type, Func<DbContext, object>>
GetProductFactories()
{
return new Dictionary<Type, Func<DbContext, object>>
{
};
}
/// <summary>
///
Constructor that initializes with runtime repository factories
/// </summary>
public
RepositoryFactories()
{
_repositoryFactories = GetProductFactories();
}
/// <summary>
///
Constructor that initializes with an arbitrary collection of factories
/// </summary>
/// <param
name="factories">
/// The
repository factory functions for this instance.
/// </param>
/// <remarks>
/// This
ctor is primarily useful for testing this class
/// </remarks>
public
RepositoryFactories(IDictionary<Type, Func<DbContext, object>>
factories)
{
_repositoryFactories = factories;
}
/// <summary>
/// Get
the repository factory function for the type.
/// </summary>
/// <typeparam
name="T">Type serving as the repository factory lookup
key.</typeparam>
/// <returns>The
repository function if found, else null.</returns>
/// <remarks>
/// The
type parameter, T, is typically the repository type
/// but
could be any type (e.g., an entity type)
/// </remarks>
public Func<DbContext, object>
GetRepositoryFactory<T>()
{
Func<DbContext, object>
factory;
_repositoryFactories.TryGetValue(typeof(T), out
factory);
return
factory;
}
/// <summary>
/// Get
the factory for <see cref="IRepository{T}"/>
where T is an entity type.
/// </summary>
/// <typeparam
name="T">The root type of the repository, typically an
entity type.</typeparam>
/// <returns>
/// A
factory that creates the <see cref="IRepository{T}"/>,
given an EF <see cref="DbContext"/>.
/// </returns>
/// <remarks>
/// Looks
first for a custom factory in <see
cref="_repositoryFactories"/>.
/// If
not, falls back to the <see
cref="DefaultEntityRepositoryFactory{T}"/>.
/// You
can substitute an alternative factory for the default one by adding
/// a
repository factory for type "T" to <see
cref="_repositoryFactories"/>.
/// </remarks>
public Func<DbContext, object>
GetRepositoryFactoryForEntityType<T>() where T : class
{
return
GetRepositoryFactory<T>() ?? DefaultEntityRepositoryFactory<T>();
}
/// <summary>
///
Default factory for a <see cref="IRepository{T}"/>
where T is an entity.
/// </summary>
/// <typeparam
name="T">Type of the repository's root entity</typeparam>
protected virtual Func<DbContext, object>
DefaultEntityRepositoryFactory<T>() where T : class
{
return
dbContext => new EFRepository<T>(dbContext);
}
/// <summary>
/// Get
the dictionary of repository factory functions.
/// </summary>
/// <remarks>
/// A
dictionary key is a System.Type, typically a repository type.
/// A
value is a repository factory function
/// that
takes a <see cref="DbContext"/>
argument and returns
/// a
repository object. Caller must know how to cast it.
/// </remarks>
private readonly IDictionary<Type, Func<DbContext, object>>
_repositoryFactories;
}
7)
Create ProductUow class that implements IProductUow in Sample.Data
project:
/// <summary>
/// The
Product "Unit of Work"
///
1) decouples the repos from the controllers
///
2) decouples the DbContext and EF from the controllers
///
3) manages the UoW
/// </summary>
/// <remarks>
/// This
class implements the "Unit of Work" pattern in which
/// the
"UoW" serves as a facade for querying and saving to the database.
///
Querying is delegated to "repositories".
/// Each
repository serves as a container dedicated to a particular
/// root
entity type such as a <see cref="Person"/>.
/// A
repository typically exposes "Get" methods for querying and
/// will
offer add, update, and delete methods if those features are supported.
/// The
repositories rely on their parent UoW to provide the interface to the
/// data
layer (which is the EF DbContext).
/// </remarks>
public class ProductUow : IProductUow, IDisposable
{
public
ProductUow(IRepositoryProvider repositoryProvider)
{
CreateDbContext();
repositoryProvider.DbContext = DbContext;
RepositoryProvider = repositoryProvider;
}
//
repositories
public IRepository<Product>
Products { get { return
GetStandardRepo<Product>(); } }
public IRepository<Category>
Categories { get { return
GetStandardRepo<Category>(); } }
public IRepository<SubCategory>
SubCategories { get { return
GetStandardRepo<SubCategory>(); } }
/// <summary>
/// Save
pending changes to the database
/// </summary>
public void
Commit()
{
DbContext.SaveChanges();
}
protected void
CreateDbContext()
{
DbContext
= new ProductDbContext();
}
protected IRepositoryProvider
RepositoryProvider { get; set; }
private IRepository<T>
GetStandardRepo<T>() where T : class
{
return
RepositoryProvider.GetRepositoryForEntityType<T>();
}
private T
GetRepo<T>() where T : class
{
return
RepositoryProvider.GetRepository<T>();
}
private ProductDbContext
DbContext { get; set; }
#region
IDisposable
public void
Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void
Dispose(bool disposing)
{
if
(disposing)
{
if
(DbContext != null)
{
DbContext.Dispose();
}
}
}
#endregion
}
8) Now in
order to use repository and unit of work in web application project (ManagingMultijQueryForm),
configure Ninject via package manager console, use of following command in
order to configure Ninject in an application:
a. Install-package
Ninject
b. Install-package
Ninject.MVC3
9)
Replace the Global.asax.cs MvcApplication class with the
following:
public class MvcApplication : NinjectHttpApplication
{
protected override IKernel
CreateKernel()
{
var
kernel = new StandardKernel();
kernel.Bind<RepositoryFactories>().To<RepositoryFactories>()
.InSingletonScope();
kernel.Bind<IRepositoryProvider>().To<RepositoryProvider>();
kernel.Bind<IProductUow>().To<ProductUow>();
return
kernel;
}
protected override void
OnApplicationStarted()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
}
}
10)
Last and final step, create a BaseController and inherit it in
all (ProductController, CategoryController and SubCategoryController)
controllers in order to use IProductUow:
a) BaseController:
public abstract class BaseController : Controller
{
protected IProductUow Uow
{ get; set; }
}
b) ProductController:
public class ProductController : BaseController
{
public
ProductController(IProductUow uow)
{
Uow = uow;
}
public ActionResult
Index()
{
return
View();
}
public ActionResult
List()
{
return
PartialView("_ProductList",
Uow.Products.GetAll().ToList());
}
public PartialViewResult
Add()
{
return
PartialView("_Product");
}
[HttpPost, ValidationActionFilter]
public PartialViewResult Add(Product
model)
{
Uow.Products.Add(model);
Uow.Commit();
return
Add();
}
public PartialViewResult
Edit(int id)
{
return
PartialView("_Product",
Uow.Products.GetById(id));
}
[HttpPost, ValidationActionFilter]
public PartialViewResult
Edit(Product model)
{
Uow.Products.Update(model);
Uow.Commit();
return
Add();
}
[HttpPost]
public JsonResult
Delete(int id)
{
var
product = Uow.Products.GetById(id);
Uow.Products.Delete(product);
Uow.Commit();
return
Json(new { success = true,
message = "Record deleted successfully!" }, "application/json", JsonRequestBehavior.AllowGet);
}
}
c) CategoryController:
public class CategoryController : BaseController
{
public
CategoryController(IProductUow uow)
{
Uow = uow;
}
public ActionResult
Index()
{
return
View();
}
public JsonResult Get(int id)
{
var
category = Uow.Categories.GetById(id);
return
Json(new { Id = category.CategoryID, Name = category.Name
}, "application/json", JsonRequestBehavior.AllowGet);
}
public PartialViewResult
List()
{
return
PartialView("_CategoryList",
Uow.Categories.GetAll().ToList());
}
[HttpPost]
public PartialViewResult
List(string s)
{
return
PartialView("_CategoryList",
Uow.Categories.GetAll().Where(m => m.Name.Contains(s)).ToList());
}
public PartialViewResult
Add()
{
return
PartialView("_Category");
}
[HttpPost, ValidationActionFilter]
public PartialViewResult Add(Category
model)
{
Uow.Categories.Add(model);
Uow.Commit();
return
Add();
}
[HttpPost]
public JsonResult
Delete(int id)
{
var
category = Uow.Categories.GetById(id);
Uow.Categories.Delete(category);
Uow.Commit();
return
Json(new { success = true,
message = "Record deleted successfully!" }, "application/json", JsonRequestBehavior.AllowGet);
}
}
d) SubCategoryController:
public class SubCategoryController : BaseController
{
public
SubCategoryController(IProductUow uow)
{
Uow = uow;
}
public ActionResult
Index()
{
return
View();
}
public JsonResult Get(int id)
{
var
subcategory = Uow.SubCategories.GetById(id);
return
Json(new { Id = subcategory.SubCategoryID, Name =
subcategory.Name }, "application/json", JsonRequestBehavior.AllowGet);
}
public PartialViewResult
List()
{
return
PartialView("_SubCategoryList",
Uow.SubCategories.GetAll().ToList());
}
[HttpPost]
public PartialViewResult
List(string s)
{
return
PartialView("_SubCategoryList",
Uow.SubCategories.GetAll().Where(m => m.Name.Contains(s) ||
m.Category.Name.Contains(s)).ToList());
}
public PartialViewResult
Add()
{
return
PartialView("_SubCategory");
}
[HttpPost, ValidationActionFilter]
public PartialViewResult Add(SubCategory
model)
{
Uow.SubCategories.Add(model);
Uow.Commit();
return
Add();
}
[HttpPost]
public JsonResult
Delete(int id)
{
var
subcategory = Uow.SubCategories.GetById(id);
Uow.SubCategories.Delete(subcategory);
Uow.Commit();
return
Json(new { success = true,
message = "Record deleted successfully!" }, "application/json", JsonRequestBehavior.AllowGet);
}
}
Before executing an application please do replace ManagingMultijQueryForm.Models with Sample.Model in all the views and remove
the reference of ManagingMultijQueryForm.Models
wherever it gives error.
Now you can run and test the application I hope it works
fine. You can download the source code from the link given below:
Download source code
I had taken a reference of Code Camper Spa application which
is developed and thought by great John
Papa.
Thanks for reading this article.