Let’s first establish the purpose of the code.
For this article, the purpose of the code is how to structure your MVC code with repository and unity of work pattern, using Entity Framework.
What is repository and unity of work ?
The repository and unity of work pattern are used to create an abstraction layer between the data access layer and the business logic layer of an application.
For this article, I have the following two tables.
Book Table
- BookID -- bigint -- Primary Key
- BookName -- nvarchar(MAX)
- Descrption -- nvarchar(MAX)
- Price -- decimal(10, 2)
- AutherID -- bigint -- Foregine Key With Auther Table
Auther Table
- AutherID -- bigint -- Primary Key
- AutherName -- nvarchar(50)
The below script is used for generating the above tables.
- USE[DemoUnityRepository]
- GO
- /****** Object: Table [dbo].[Auther] Script Date: 11-01-2017 12:25:22 PM ******/
- SET ANSI_NULLS ON
- GO
- SET QUOTED_IDENTIFIER ON
- GO
- CREATE TABLE[dbo].[Auther](
- [AutherID][bigint] IDENTITY(1, 1) NOT NULL, [AutherName][nvarchar](50) NOT NULL, CONSTRAINT[PK_Auther] PRIMARY KEY CLUSTERED(
- [AutherID] ASC) WITH(PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON[PRIMARY]) ON[PRIMARY]
- GO
- /****** Object: Table [dbo].[Book] Script Date: 11-01-2017 12:25:22 PM ******/
- SET ANSI_NULLS ON
- GO
- SET QUOTED_IDENTIFIER ON
- GO
- CREATE TABLE[dbo].[Book](
- [BookID][bigint] IDENTITY(1, 1) NOT NULL, [BookName][nvarchar](max) NOT NULL, [Descrption][nvarchar](max) NOT NULL, [Price][decimal](10, 2) NOT NULL, [AutherID][bigint] NOT NULL, [TagID][bigint] NOT NULL, CONSTRAINT[PK_Book] PRIMARY KEY CLUSTERED(
- [BookID] ASC) WITH(PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON[PRIMARY]) ON[PRIMARY] TEXTIMAGE_ON[PRIMARY]
- GO
- ALTER TABLE[dbo].[Book] WITH CHECK ADD CONSTRAINT[FK_Book_Auther] FOREIGN KEY([AutherID])
- REFERENCES[dbo].[Auther]([AutherID])
- GO
- ALTER TABLE[dbo].[Book] CHECK CONSTRAINT[FK_Book_Auther]
- GO
- ALTER TABLE[dbo].[Book] WITH CHECK ADD CONSTRAINT[FK_Book_Tag] FOREIGN KEY([TagID])
- REFERENCES[dbo].[Tag]([TagID])
- GO
- ALTER TABLE[dbo].[Book] CHECK CONSTRAINT[FK_Book_Tag]
- GO
Step 1 - Create an new MVC Application
- On the File menu, click New >> Project.
- In the "New Project" dialog box under Project types, expand Visual C# >> Web and in the Name box, type "DemoUnityRepository" followed by OK.
- Now, in the dialog box, click on the "MVC" under the ASP.NET 4.5.2 Templates. Then, click on "Change Authentication" which stays on center of the right side and finally, select "No Authentication" and click on OK.
- Add two folders IRepository and Repository.
- IRepository Folder -- for classes that contain interfaces.
- Repository Folder -- for classes that contain interfaces implementation.
Step 2 - Now, it's code time.
Add EDMX
- On the right side, you can find the Solution Explorer.
- In there, right click on "Models" Folder. Then, click on Add >> New Item.
- Now, click on the Visual C# and Select ADO.NET Entity Data Model, Name it "DBModel", and click on "OK".
- Select EF Designer from Database and click on "Next".
- Now, click on new connection, define "Server name", select authentication mode either Windows or SQL Server ( if SQL Server, then enter the username or password) and finally, select database "DemoUnityRepository" under "Connect to a Database". Click OK.
- Now, declare a name of connection string as "DBEntities" under "Save connection" setting in Web.config, then click on Next.
- Select version of Entity Framework. Select "Entity Framework 6.x" then click on "Next".
- Select "Table". Now, give the name space as "Models" under Model Namespace: and then click on OK.
- Now, Build your project by pressing CTRL + B for updating every entity perfectly.
STEP 3 - Implement Code
- Create an IRepository interface in IRepository Folder.
IRepository interface contains all Generic method like Add | AddRange | Remove | Remove Range | Get | Get All
- public interface IRepository < TEntity > where TEntity: class {
- void Add(TEntity entity);
- void AddRange(IEnumerable < TEntity > entities);
- void Remove(TEntity entity);
- void RemoveRange(IEnumerable < TEntity > entities);
- TEntity Get(int id);
- IEnumerable < TEntity > GetAll();
- IEnumerable < TEntity > Find(Expression < Func < TEntity, bool >> predicate);
- }
- Add(TEntity entity) -- This method is used for inserting single row in single table.
- AddRange(IEnumerable<TEntity> entities) -- This method is used for inserting multiple rows in single table.
- Remove(TEntity entity) -- This method is used for removing single row from single table.
- RemoveRange(IEnumerable<TEntity> entities) -- This method is used for removing multiple row from single table.
- Get(int id) -- This method is used for retrieving single row according to id from table.
- GetAll() -- This method is used for retrieving all rows from table.
- Find(Expression<Func<TEntity, bool>> predicate) -- This method is used for retrieving all rows according to input condition from table.
- Now, implement IRepository interface. Create a Repository class in Repository Folder.
- public class Repository < TEntity > : IRepository < TEntity > where TEntity: class {
-
- protected readonly DBEntities db;
- public Repository(DBEntities context) {
- db = context;
- }
- public void Add(TEntity entity) {
- db.Set < TEntity > ().Add(entity);
- }
- public void AddRange(IEnumerable < TEntity > entities) {
- db.Set < TEntity > ().AddRange(entities);
- }
- public void Remove(TEntity entity) {
- db.Set < TEntity > ().Remove(entity);
- }
- public void RemoveRange(IEnumerable < TEntity > entities) {
- db.Set < TEntity > ().RemoveRange(entities);
- }
- public TEntity Get(int id) {
- return db.Set < TEntity > ().Find(id);
- }
- public IEnumerable < TEntity > GetAll() {
- return db.Set < TEntity > ().ToList();
- }
- public IEnumerable < TEntity > Find(Expression < Func < TEntity, bool >> predicate) {
- return db.Set < TEntity > ().Where(predicate);
- }
- }
Repository contains DbSet, So, it has Add, Get ,& Remove methods. While Unit of work contains DbContext, so it has Save Method.
- Create an IUnitOfWork interface in IRepository Folder and also inherit IDisposable interface in that class.
- public interface IUnitOfWork : IDisposable
- {
- int SaveChanges();
- }
- Now, implement IUnitOfWork interface. Create a UnitOfWork class in Repository Folder.
- public class UnitOfWork: IUnitOfWork {
- private readonly DBEntities db;
- public UnitOfWork(DBEntities context) {
- db = context;
- }
- public int SaveChanges() {
- return db.SaveChanges();
- }
- public void Dispose() {
- db.Dispose();
- }
- }
Why we use this pattern?
The first reason is to "Minimize the duplicate query logic".
How ?
For example, I have one situation that I want to display the top five most expensive books of a particular author in my project, at multiple places. So, my LINQ query is like.
- var top5CostlyBooks = db.Books
- .Where(s=>s.AutherID == id)
- .OrderByDescending(s=>s.Price)
- .Take(5);
Instead of this, if I create a repository for this situation, then I don't need to write this query again and again at different places but I need to call that repository.
It's like this:
- var top5CostlyBooks = unitOfWork.Customs.GetTop5CostlyBook();
The second reason is "to make a Business logic loosely typed".
How?
If we use Entity Framework directly, then our "Business Layer" is Tightly Coupled with "Data Access Layer". That means, if there are any changes or updates in EDMX, then we need to update our Business Logic.
Now here, our "Business Layer" is loosely coupled with "Data Access Layer" because "Business Layer" is interacting with "Unity Of Work" & "Unity Of Work" is interacting with "Data Access Layer".
Now, I have two situations.
--> I want to get all the data from "Book" Table
--> I want to get top 5 costly books Author wise from "Book" Table.
In the 1st condition, I used Generic Repository and for the 2nd condition, I am going to create a new Custom Repository.
- Create an ICustomRepository interface in IRepository Folder and also inherit IRepository interface in that class.
- public interface ICustomRepository : IRepository<Book>
- {
- IEnumerable<Book> GetTop5CostlyBookAutherWise(long AutherID);
- }
- Now, implement ICustomRepository interface. Create a CustomRepository class in Repository Folder.
- public class CustomRepository: Repository < Book > , ICustomRepository {
- public CustomRepository(DBEntities context): base(context) {}
- public DBEntities DBEntities {
- get {
- return db as DBEntities;
- }
- }
- public IEnumerable < Book > GetTop5CostlyBookAutherWise(long AutherID) {
- return DBEntities.Books.Where(s => s.AutherID == AutherID).OrderByDescending(s => s.Price).Take(5).ToList();
- }
- }
Our all "Data Access Layer" code is cooked. Now, it's time for "Business Layer".
Create the below Controller.
- public ActionResult Index(long BookID) {
-
-
-
-
- List < Book > books = new List < Book > ();
- try {
- using(var unitOfWork = new UnitOfWork(new DBEntities())) {
- if (BookID == 1) {
-
- books = unitOfWork.Customs.GetAll().ToList();
- } else if (BookID == 2) {
-
- books = unitOfWork.Customs.GetTop5CostlyBookAutherWise(1).ToList();
- }
- }
- return PartialView(books);
- } catch {
- throw;
- }
- }