Using the Repository Pattern with ASP.NET MVC and Entity Framework

原文:http://www.codeguru.com/csharp/.net/net_asp/mvc/using-the-repository-pattern-with-asp.net-mvc-and-entity-framework.htm

Introduction

Data driven web applications need to have a neat strategy for data access. One of the important aspects of this strategy is the separation between the physical database, queries and other data access logic from the rest of the application. Repository pattern is a popular way to achieve such an isolation. This article discusses the basics of Repository pattern in the context of Entity Framework and ASP.NET MVC. It also illustrates how a repository can be built around your entities.

Overview of the Repository Pattern?

Most of the business applications need to access data residing in one or the other data store. The simplest approach is to write all the data access code in the main application itself. Consider, for example, that you have an ASP.NET MVC controller named CustomerController. The Customer controller class has several action methods that ultimately perform typical CRUD (Create, Read, Update and Delete) operations on the underlying database. Let's further assume that you are using Entity Framework for database access. In this case your application would do something like this:

   

                           

       

            IBM Worklight Compared to "Do-It-Yourself" Mobile Platforms       

Download Now       

100%

 

 

 

As you can see, various actions of the Customer controller (not all are shown in the figure) directly instantiate the EF data context and fire queries to retrieve the data. They also INSERT, UPDATE and DELETE data using the data context and DbSet. The EF in turn talks with the underlying SQL Server database. Although the above application works as expected it suffers from the drawback that the database access code (creating data context, writing queries, manipulating data, persisting changes etc.) is embedded directly inside the action methods. This design can cause code duplication and the controller is susceptible to change even for a minute change in the data access logic. For example, if an application is modifying a customer from two controllers, each controller will repeat the same code. And any future modifications also need to be done at two places.

To tackle this shortcoming Repository pattern can be introduced. The following line summarizes the purpose of the Repository pattern:

Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.

(Read more about repository pattern here.)

Thus a repository acts like a middleman between the rest of the application and the data access logic. A repository isolates all the data access code from rest of the application. In doing so you are benefited by having a simple point of change in case modifications are necessary. Additionally, testing your controllers becomes easy because the testing framework need not run against the actual database access code. An in-memory or local pseudo-database would do the trick. With a repository introduced, the above figure can be changed to:

With the changed design, the Customer controller won't talk with EF data context directly. Additionally, there won't be queries or any other database operations in the action methods. All these operations are wrapped by the Customer repository. The Customer repository in turn uses EF data context to get the job done. Notice that the Customer repository has methods such as SelectAll(), SelectByID(), Insert(), Update() and Delete(). The Customer controller uses these methods to get its job done. If you see the Customer repository - it is offering an in-memory collection like interface to its consumer (for example, many collection classes expose Add() and Remove() methods and allow you to query them).

Creating Model using Entity Framework

Now that you understand the basics of Repository pattern, let's create a sample application that illustrates what we've discussed so far. Create a new ASP.NET MVC web application based on the empty template. Right click on the Models folder and add an ADO.NET Entity Data Model for the Customers table of the Northwind database. The following figure shows how the Customer entity looks:

Various views of the Customer controller need the Customer entity for their display so it needs to be passed between repository, controller and the views. 

Creating Customer Repository

A repository typically does at least five operations - Selecting all records from a table, selecting a single record based on its primary key, Insert, Update and Delete. This list is, however, not rigid. You may have more or fewer methods in the repository. For the sake of our example let's decide that these five operations are needed from the Customer repository. To enforce that all the repository classes have these five methods we will define an interface - ICustomerRepository - that has these methods and then we will implement this interface in a class. Creating an interface will also help us during testing where we may be required to define an alternate in-memory repository for the sake of simplicity. The ICustomerRepository interface looks like this:

  1. public interface ICustomerRepository
  2. {
  3. IEnumerable<Customer> SelectAll();
  4. Customer SelectByID(string id);
  5. void Insert(Customer obj);
  6. void Update(Customer obj);
  7. void Delete(string id);
  8. void Save();
  9. }

The ICustomerRepository interface has five methods as listed below:

  • SelectAll() : This method is intended to return all the Customer entities as an enumerable collection (such as a generic List).
  • SelectByID() : This method accepts a string representing a customer ID (CustomerID is a character column in the database) and returns a single Customer entity matching that ID.
  • Insert(): This method accepts a Customer object and adds it to the Customers DbSet.
  • Update() : This method accepts a Customer object and marks it as a modified Customer in the DbSet.
  • Delete() : This method accepts a CustomerID and removes that Customer entity from the Customers DbSet.
  • Save() : This method saves the changes to Northwind database.

Next, add CustomerRepository class to the project and implement ICustomerRepository in it. The following code shows the completed CustomerRepository class.

  1. public class CustomerRepository:ICustomerRepository
  2. {
  3. private NorthwindEntities db = null;
  4.  
  5. public CustomerRepository()
  6. {
  7. this.db = new NorthwindEntities();
  8. }
  9.  
  10. public CustomerRepository(NorthwindEntities db)
  11. {
  12. this.db = db;
  13. }
  14.  
  15. public IEnumerable<Customer> SelectAll()
  16. {
  17. return db.Customers.ToList();
  18. }
  19.  
  20. public Customer SelectByID(string id)
  21. {
  22. return db.Customers.Find(id);
  23. }
  24.  
  25. public void Insert(Customer obj)
  26. {
  27. db.Customers.Add(obj);
  28. }
  29.  
  30. public void Update(Customer obj)
  31. {
  32. db.Entry(obj).State = EntityState.Modified;
  33. }
  34.  
  35. public void Delete(string id)
  36. {
  37. Customer existing = db.Customers.Find(id);
  38. db.Customers.Remove(existing);
  39. }
  40.  
  41. public void Save()
  42. {
  43. db.SaveChanges();
  44. }
  45. }

The CustomerRepository class implements all the five methods discussed above. Notice that it has two constructor definitions - one that takes no parameters and the one that accepts the data context instance. This second version will be useful when you wish to pass the context from outside (such as during testing or while using the Unit of Work pattern). All the method implementations of CustomerRepository are quite straightforward and hence we won't go into detailed discussion of these methods.

Using Customer Repository in a Controller

Now that you have built the Customer repository, let's use it in a controller. So, add a controller class inside the Controllers folder and name it CustomerController. The following code shows how CustomerController looks:

  1. public class CustomerController : Controller
  2. {
  3. private ICustomerRepository repository = null;
  4. public CustomerController()
  5. {
  6. this.repository = new CustomerRepository();
  7. }
  8. public CustomerController(ICustomerRepository repository)
  9. {
  10. this.repository = repository;
  11. }
  12.  
  13.  
  14. public ActionResult Index()
  15. {
  16. List<Customer> model = (List<Customer>)repository.SelectAll();
  17. return View(model);
  18. }
  19.  
  20. public ActionResult New()
  21. {
  22. return View();
  23. }
  24.  
  25. public ActionResult Insert(Customer obj)
  26. {
  27. repository.Insert(obj);
  28. repository.Save();
  29. return View();
  30. }
  31.  
  32. public ActionResult Edit(string id)
  33. {
  34. Customer existing = repository.SelectByID(id);
  35. return View(existing);
  36. }
  37.  
  38. public ActionResult Update(Customer obj)
  39. {
  40. repository.Update(obj);
  41. repository.Save();
  42. return View();
  43. }
  44.  
  45. public ActionResult ConfirmDelete(string id)
  46. {
  47. Customer existing = repository.SelectByID(id);
  48. return View(existing);
  49. }
  50.  
  51. public ActionResult Delete(string id)
  52. {
  53. repository.Delete(id);
  54. repository.Save();
  55. return View();
  56. }
  57.  
  58. }

The Customer controller has two versions of the constructor and seven action methods. Notice that there is a private variable of type ICustomerRepository at the class level. The parameter less constructor sets this variable to an instance of CustomerRepository. The other version of the constructor accepts an implementation of ICustomerRepository from the external world and sets it to the private variable. This second version is useful during testing where you will supply a mock implementation of Customer repository from the test project.

The seven methods defined by the Customer controller are as follows:

  • Index() : Displays Index view and passes a List of Customer entities as its model.
  • New() : Displays New view.
  • Insert() : New view submits data to this method. It receives the data as a Customer instance and then inserts a customer using the repository.
  • Edit() : Displays Edit view. It accepts a CustomerID as an id route parameter and populates the Edit view with the data of the existing Customer.
  • Update() : Edit view submits data to this method. It receives the data as a Customer instance and then updates a customer using the repository.
  • ConfirmDelete() : Displays ConfirmDelete view.
  • Delete() : ConfirmDelete view submits to this action method. The action then deletes the Customer using the repository.

We won't go into the details of the four views mentioned above (Index, New, Edit and ConfirmDelete). They are quite simple and you can add them on your own for the sake of testing.

You just created and successfully used Repository pattern! As you can see from the controller code, nowhere did you use data context or EF operations. You always called some or the other method of the CustomerRepository to get the job done. Thus all your data access code is now separated into the repository.

Testing a Controller

Earlier we mentioned that repository pattern also helps during the testing phase. Let's see how by creating a simple test. Add a new Test project to the same solution and refer the MVC project into it. Inside the test project we will define another repository that works on some in-memory collection instead of the actual database. To create the repository add a class to the test project and write the following code to it:

  1. class TestCustomerRepository:ICustomerRepository
  2. {
  3. private List<Customer> data = new List<Customer>();
  4.  
  5. public IEnumerable<Customer> SelectAll()
  6. {
  7. return data;
  8. }
  9.  
  10. public Customer SelectByID(string id)
  11. {
  12. return data.Find(m => m.CustomerID == id);
  13. }
  14.  
  15. public void Insert(Customer obj)
  16. {
  17. data.Add(obj);
  18. }
  19.  
  20. public void Update(Customer obj)
  21. {
  22. Customer existing = data.Find(m => m.CustomerID == obj.CustomerID);
  23. existing = obj;
  24. }
  25.  
  26. public void Delete(string id)
  27. {
  28. Customer existing = data.Find(m => m.CustomerID == id);
  29. data.Remove(existing);
  30. }
  31.  
  32. public void Save()
  33. {
  34. //nothing here
  35. }
  36. }

As you can see TestCustomerRepository class implements the same ICustomerRepository interface defined in the MVC project. It then implements all five methods against an in-memory List of Customer entities. Although not shown in the above code, you could have pre-populated the List with some mock data.

Once TestCustomerRepository is created you can instantiate it in a test method and pass it to the CustomerController like this:

  1. [TestMethod]
  2. public void TestMethod1()
  3. {
  4. TestCustomerRepository repository = new TestCustomerRepository();
  5. CustomerController controller = new CustomerController(repository);
  6. var result = (ViewResult)controller.Index();
  7. List<Customer> data = (List<Customer>)result.ViewData.Model;
  8. Assert.IsFalse(data.Count <= 0);
  9. }

Recollect that CustomerController has an overloaded version of the constructor that takes any implementation of ICustomerRepository from the external world. The above code creates an instance of TestCustomerRepository and passes it to the CustomerController. It then invokes the Index() action method of the controller. The model of the Index view is obtained using the ViewData.Model property. The Assert checks whether there are any items in the model collection using IsFalse() method. In this case since no data is added to the generic List of Customer, the Count will be 0 and hence the condition will evaluate to true causing the assertion to fail.

Making the Repository Generic

Although the above example works great, it has a drawback. It expects you to have a separate repository for each entity in the application. For example, CustomerRepository for Customer entity, EmployeeRepository for Employee entity and so on. This can be too much work, especially if all the repositories are doing the same kind of operations (typical CRUD as in our example). Wouldn't it be nice to create a generic repository that can be used with any entity? Let's attempt to do just that.

Add the following interface to the ASP.NET MVC project:

  1. public interface IGenericRepository<T> where T:class
  2. {
  3. IEnumerable<T> SelectAll();
  4. T SelectByID(object id);
  5. void Insert(T obj);
  6. void Update(T obj);
  7. void Delete(object id);
  8. void Save();
  9. }

The IGenericRepository interface is a generic interface that defines the same set of methods as before. Notice that this time instead of Customer entity it uses T everywhere. Also notice that SelectByID() and Delete() methods now accept object parameter instead of string. This is necessary because different tables may have different types of primary keys (Customers table has a string primary key whereas Employees table has an integer primary key).

Now, add a class to the ASP.NET MVC project that implements IGenericRepository interface. This class is shown below:

  1. public class GenericRepository<T>:IGenericRepository<T> where T : class
  2. {
  3. private NorthwindEntities db = null;
  4. private DbSet<T> table = null;
  5.  
  6. public GenericRepository()
  7. {
  8. this.db = new NorthwindEntities();
  9. table = db.Set<T>();
  10. }
  11.  
  12. public GenericRepository(NorthwindEntities db)
  13. {
  14. this.db = db;
  15. table = db.Set<T>();
  16. }
  17.  
  18. public IEnumerable<T> SelectAll()
  19. {
  20. return table.ToList();
  21. }
  22.  
  23. public T SelectByID(object id)
  24. {
  25. return table.Find(id);
  26. }
  27.  
  28. public void Insert(T obj)
  29. {
  30. table.Add(obj);
  31. }
  32.  
  33. public void Update(T obj)
  34. {
  35. table.Attach(obj);
  36. db.Entry(obj).State = EntityState.Modified;
  37. }
  38.  
  39. public void Delete(object id)
  40. {
  41. T existing = table.Find(id);
  42. table.Remove(existing);
  43. }
  44.  
  45. public void Save()
  46. {
  47. db.SaveChanges();
  48. }
  49. }

The GenericRepository is a generic class and implements IGenericRepository. Notice that since this class uses generic type T you can't access a DbSet as a property of data context. That's why a generic DbSet variable is declared at the top that points to an appropriate DbSet based on the type of T.

Once GenericRepository is ready you can use it in the Customer controller like this:

  1. public class CustomerController : Controller
  2. {
  3. private IGenericRepository<Customer> repository = null;
  4.  
  5. public CustomerController()
  6. {
  7. this.repository = new GenericRepository<Customer>();
  8. }
  9.  
  10. public CustomerController(IGenericRepository<Customer> repository)
  11. {
  12. this.repository = repository;
  13. }
  14. ...
  15. ...
  16. }

As shown above, the IGenericRepository variable is declared with Customer as its type. The constrictor then assigns an instanced of GenericRepository() or some other implementation of IGenericRepository to this variable. 

Testing a Controller using Generic Repository

Just like you tested the CustomerController by creating a mock implementation of ICustomerRepository, you can also test it by creating a mock implementation of IGenericRepository. The following code shows one such implementation:

  1. class TestGenericRepository<T>:IGenericRepository<T> where T:class
  2. {
  3. private List<T> data = new List<T>();
  4.  
  5. public IEnumerable<T> SelectAll()
  6. {
  7. return data;
  8. }
  9.  
  10. public T SelectByID(object id)
  11. {
  12. return data.FirstOrDefault();
  13. }
  14.  
  15. public void Insert(T obj)
  16. {
  17. data.Add(obj);
  18. }
  19.  
  20. public void Update(T obj)
  21. {
  22. T existing = data.FirstOrDefault();
  23. existing = obj;
  24. }
  25.  
  26. public void Delete(object id)
  27. {
  28. data.RemoveAt(0);
  29. }
  30.  
  31. public void Save()
  32. {
  33. //nothing here
  34. }
  35. }

The TestGenericRepository class creates an implementation of IGenericRepository that works against an in-memory List of entities. Notice that just for the sake of testing, SelectByID(), Update() and Delete() methods return the first item from the List.

Now you can write another test method that uses TestGenericRepository repository.

  1. [TestMethod]
  2. public void TestMethod2()
  3. {
  4. TestGenericRepository<Customer> repository = new TestGenericRepository<Customer>();
  5. CustomerController controller = new CustomerController(repository);
  6. var result = (ViewResult)controller.Index();
  7. List<Customer> data = (List<Customer>)result.ViewData.Model;
  8. Assert.IsFalse(data.Count <= 0);
  9. }

As you can see this time TestGenericRepository is instantiated for the Customer entity and passed to the CustomerController. The remaining part of the test is identical to the earlier test method you wrote.

Summary

The Repository pattern allows you to separate data access code from the rest of the system. This isolation promotes code reuse, minimizes future modifications to code and also simplifies testing of the controller classes. This article gave you an understanding of the Repository pattern in the context of ASP.NET MVC and Entity Framework. You created entity specific repositories as well as generic repositories. You also used them in a test project.

    

Related Articles

时间: 2024-12-21 17:56:51

Using the Repository Pattern with ASP.NET MVC and Entity Framework的相关文章

Generic repository pattern and Unit of work with Entity framework

 原文 Generic repository pattern and Unit of work with Entity framework            Repository pattern is an abstraction layer between your business logic layer and data access layer. This abstract layer contains methods to server data from data layer t

ASP.NET MVC 应用提速的十种方法

[编者按]本文作者为 DZone 社区的最具价值博主(MVB) Jonathan Danylko,主要介绍为 ASP.NET MVC 应用提速的十种方法.由国内 ITOM 管理平台 OneAPM 编译呈现,以下为正文. 每个人都想快速掌握最新消息. 我是说,人们恨不得预知第二天的头条.没有人喜欢等待. 排队等待,遇到红灯要等待,开个网页要等待,等等等. 理所当然,没有人喜欢等待网页慢吞吞地加载,尤其是在移动端访问网站时.其实,Web 开发者敏感的神经决定了我们等待与否. 现在,快速响应不仅是来自

ASP.NET MVC 5 - 给数据模型添加校验器

  在本节中将会给Movie模型添加验证逻辑.并且确保这些验证规则在用户创建或编辑电影时被执行.   拒绝重复 DRY ASP.NET MVC 的核心设计信条之一是DRY: "不要重复自己(DRY --Don't Repeat Yourself)".ASP.NET MVC鼓励您指定功能或者行为,只做一次,然后将它应用到应用程序的各个地方.这可以减少您需要编写的代码量,并减少代码出错率,易于代码维护.   给ASP.NET MVC 和 Entity Framework Code Firs

【译】ASP.NET MVC 5 教程 - 10:添加验证

原文:[译]ASP.NET MVC 5 教程 - 10:添加验证 在本节中,我们将为Movie模型添加验证逻辑,并确认验证规则在用户试图使用程序创建和编辑电影时有效. DRY 原则 ASP.NET MVC 的一个核心原则是DRY(Don't Repeat Yourself - 不做重复的事情).ASP.NET MVC 鼓励你一次性的指定功能或行为,然后应用程序的其它地方通过映射得到它,这样一来就减少了大量的代码,从而减少了出错误的可能性,并且更易于维护. ASP.NET  MVC  和 Enti

ASP.NET MVC 5 学习教程:添加验证

原文 ASP.NET MVC 5 学习教程:添加验证 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符串 通过控制器访问模型的数据 生成的代码详解 使用 SQL Server LocalDB Edit方法和Edit视图详解 添加查询 Entity Framework 数据迁移之添加字段 添加验证 Details 和 Delete 方法详解 在本节中,我们将为Movie模型添加验证逻辑,并确认验证规则在用户试图

快速搞懂ASP.NET MVC

有一只企鹅首先发现地球暖化.冰山在融化,回来告诉其它企鹅,却没有任 一只企鹅愿意相信:因为企鹅们觉得现在生活过得很好,都不想费心思考如何改 变.改变何时会来临,因此仍每天做例行性的工作,不愿去研究别人的观点.「 有时人们会抗拒改变,其实只是不想改变:但是愿意接收新观念的技术人员,通 常会活得比较久」.本帖只提供 MVC Pattern 的观念和架构介绍,仅供未接触 过 MVC Framework 的 .NET 技术人员作为参考之用,以评估是否要深入学习或 在将来的项目导入. 常在网络上,看到有网

一起谈.NET技术,ASP.NET MVC验证框架中关于属性标记的通用扩展方法

之前写过一篇文章<ASP.NET MVC中的验证>,唯一的遗憾就是在使用Data Annotation Validators方式验证的时候,如果数据库是Entityframework等自动生成的文件,就没有办法使用扩展属性标记进行标记.现在已经开始有了一些其它的Asp.net MVC 验证框架,使用上跟Data Annotation Validators差不太多,但是普遍有这样的问题,如果数据库是Entityframework生成的edm文件,没有办法进行扩展属性标记. 今天在网上发现了另外一

ASP.NET MVC 1.0浅析

为什么要用ASP.NET MVC 1.0?当我刚知道1.0发布的时候,经常这样问. 最近正在考虑是否在我们的企业级应用中使用ASP.NET MVC 1.0框架,因此会一直找使用它的理由,希 望大家在关注技术的同时,结合企业应用谈谈自己的看法. 1.MVC的组成 Models:访问数据库,装载数据.处理业务逻辑.在项目中体现为数据实体类加业务代理类. Views:显示数据,用户界面.在项目中体现为aspx页面,偶尔可以加上code-behind. Controller:按路由规则将请求的数据传送给

ASP.NET MVC Contact Manager开发之旅迭代5

迭代5 建立单元测试 本次迭代 在上一次对Contact Manager的迭代中,我们通过使用一些设计模式对 程序进行了重构,松散了类之间的耦合.我们将controller.service和repository层分别 独立出来.每层都基于接口与其他层进行交互. 通过重构,应用程序变得更以维护 和修改.假如某天你需要使用其他的数据存储技术,那么只要简单的替换repository层即可 ,并不需要去碰controller或service层中的代码. 但当我们需要向Contact Manager中添加