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 blogLet'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: }
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
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:
Post a Comment