Wdrażanie z repozytorium i jednostki pracy w aplikacji ASP.NET MVC (9, 10)Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application (9 of 10)

przez Tom Dykstraby Tom Dykstra

Pobierz ukończony projektDownload Completed Project

Przykładową aplikację sieci web firmy Contoso University przedstawia sposób tworzenia aplikacji ASP.NET MVC 4 przy użyciu Entity Framework 5 Code First i programu Visual Studio 2012.The Contoso University sample web application demonstrates how to create ASP.NET MVC 4 applications using the Entity Framework 5 Code First and Visual Studio 2012. Aby uzyskać informacji na temat tej serii samouczka, zobacz pierwszym samouczku tej serii.For information about the tutorial series, see the first tutorial in the series. Można uruchomić tej serii samouczka od początku lub pobrać projekt startowy w tym rozdziale i zacznij tutaj.You can start the tutorial series from the beginning or download a starter project for this chapter and start here.

Note

Jeśli napotkasz problem, nie można rozpoznać Pobieranie ukończone rozdział i spróbuj odtworzyć problem.If you run into a problem you can't resolve, download the completed chapter and try to reproduce your problem. Rozwiązanie tego problemu można znaleźć zwykle porównując swój kod, aby kompletny kod.You can generally find the solution to the problem by comparing your code to the completed code. Niektóre typowe błędy i sposobu rozwiązania tych problemów można znaleźć błędów i rozwiązania problemu.For some common errors and how to solve them, see Errors and Workarounds.

W poprzednim samouczku użyto dziedziczenia zmniejszyć nadmiarowy kod w Student i Instructor klas jednostek.In the previous tutorial you used inheritance to reduce redundant code in the Student and Instructor entity classes. W tym samouczku zobaczysz niektóre sposoby korzystania z repozytorium i jednostki pracy wzorce dla operacji CRUD.In this tutorial you'll see some ways to use the repository and unit of work patterns for CRUD operations. Tak jak w poprzednim samouczku w tym jednym poznasz, jak zmienić sposób działania swojego kodu za pomocą stron można już utworzyć zamiast tworzenia nowych stron.As in the previous tutorial, in this one you'll change the way your code works with pages you already created rather than creating new pages.

Repozytorium i jednostki pracyThe Repository and Unit of Work Patterns

Repozytorium i jednostki pracy wzorce są przeznaczone do tworzenia warstwę abstrakcji od warstwy dostępu do danych i warstwy logiki biznesowej aplikacji.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. Implementacji tych wzorców może pomóc ochronić aplikację przed zmianami w magazynie danych i może ułatwić automatyczne testy jednostkowe i Programowanie oparte na testach (TDD).Implementing these patterns can help insulate your application from changes in the data store and can facilitate automated unit testing or test-driven development (TDD).

W tym samouczku będziesz implementuje klasę repozytorium dla każdego typu jednostki.In this tutorial you'll implement a repository class for each entity type. Aby uzyskać Student typu jednostki, utworzysz interfejs repozytorium i klasę repozytorium.For the Student entity type you'll create a repository interface and a repository class. Podczas tworzenia wystąpienia repozytorium w kontrolerze użyjesz interfejsu tak, aby kontroler będzie akceptować odwołania do dowolnego obiektu, który implementuje interfejs repozytorium.When you instantiate the repository in your controller, you'll use the interface so that the controller will accept a reference to any object that implements the repository interface. Po uruchomieniu na serwerze sieci web kontrolera odbiera repozytorium, która współdziała z programu Entity Framework.When the controller runs under a web server, it receives a repository that works with the Entity Framework. Po uruchomieniu kontroler w obszarze klasę testu jednostkowego odbiera repozytorium, który działa z danymi przechowywanymi w taki sposób, że możesz łatwo manipulować do testowania, takie jak pamięci w pamięci.When the controller runs under a unit test class, it receives a repository that works with data stored in a way that you can easily manipulate for testing, such as an in-memory collection.

W dalszej części tego samouczka użyjesz wielu repozytoriów i jednostki pracy klasa Course i Department wpisuje jednostki Course kontrolera.Later in the tutorial you'll use multiple repositories and a unit of work class for the Course and Department entity types in the Course controller. Klasa pracy jednostki koordynuje pracy z wieloma repozytoriami, tworząc pojedynczą bazę danych klasy kontekstu, współużytkowane przez wszystkie z nich.The unit of work class coordinates the work of multiple repositories by creating a single database context class shared by all of them. Jeśli chcesz mieć możliwość wykonania automatycznych testów jednostkowych, można będzie tworzenie i używanie interfejsów dla tych klas w taki sam sposób jak w Student repozytorium.If you wanted to be able to perform automated unit testing, you'd create and use interfaces for these classes in the same way you did for the Student repository. Jednak w celu uproszczenia tego samouczka można tworzyć i używać tych klas bez interfejsów.However, to keep the tutorial simple, you'll create and use these classes without interfaces.

Poniższa ilustracja przedstawia sposób wyobrażenie relacje między kontrolerem i klasy kontekst, w porównaniu do nie używa repozytorium lub jednostki wzorzec pracy na wszystkich.The following illustration shows one way to conceptualize the relationships between the controller and context classes compared to not using the repository or unit of work pattern at all.

Repository_pattern_diagram

Testy jednostkowe nie utworzy w tej serii samouczków.You won't create unit tests in this tutorial series. Aby zapoznać się z TDD z aplikacją MVC, która używa wzorca repozytorium, zobacz instruktażu: Projektowanie oparte na testach przy użyciu platformy ASP.NET MVC.For an introduction to TDD with an MVC application that uses the repository pattern, see Walkthrough: Using TDD with ASP.NET MVC. Aby uzyskać więcej informacji na temat wzorca repozytorium zobacz następujące zasoby:For more information about the repository pattern, see the following resources:

Note

Istnieje wiele sposobów implementowania repozytorium i jednostki pracy.There are many ways to implement the repository and unit of work patterns. Można użyć klasy repozytorium, z lub bez niego jednostkę pracy klasy.You can use repository classes with or without a unit of work class. Możesz zaimplementować pojedynczego repozytorium na potrzeby wszystkie typy jednostek i jeden dla każdego typu.You can implement a single repository for all entity types, or one for each type. W przypadku zastosowania dla każdego typu można użyć osobnych klas, rodzajowego klasy podstawowej i klasy pochodne lub abstrakcyjną klasę bazową i klasach pochodnych.If you implement one for each type, you can use separate classes, a generic base class and derived classes, or an abstract base class and derived classes. Można uwzględnić logikę biznesową w repozytorium lub ograniczyć jego logiką dostępu do danych.You can include business logic in your repository or restrict it to data access logic. Można również wbudować warstwę abstrakcji do klasy kontekstu bazy danych, za pomocą IDbSet interfejsy istnieje zamiast DbSet typów dla zestawów jednostek.You can also build an abstraction layer into your database context class by using IDbSet interfaces there instead of DbSet types for your entity sets. Podejścia do wdrażania warstwę abstrakcji przedstawiona w tym samouczku jest jedną z opcji należy rozważyć nie zalecenie dla wszystkich scenariuszach i środowiskach.The approach to implementing an abstraction layer shown in this tutorial is one option for you to consider, not a recommendation for all scenarios and environments.

Tworzenie klasy repozytorium dla uczniówCreating the Student Repository Class

W DAL folderze utwórz plik klasy o nazwie IStudentRepository.cs i Zastąp istniejący kod następującym kodem:In the DAL folder, create a class file named IStudentRepository.cs and replace the existing code with the following code:

using System;
using System.Collections.Generic;
using ContosoUniversity.Models;

namespace ContosoUniversity.DAL
{
    public interface IStudentRepository : IDisposable
    {
        IEnumerable<Student> GetStudents();
        Student GetStudentByID(int studentId);
        void InsertStudent(Student student);
        void DeleteStudent(int studentID);
        void UpdateStudent(Student student);
        void Save();
    }
}

Ten kod deklaruje typowy zestaw metod CRUD, w tym dwie metody odczytu — taki, który zwraca wszystkie Student jednostki a, który umożliwia znalezienie jednej Student jednostki według identyfikatora.This code declares a typical set of CRUD methods, including two read methods — one that returns all Student entities, and one that finds a single Student entity by ID.

W DAL folderze utwórz plik klasy o nazwie StudentRepository.cs pliku.In the DAL folder, create a class file named StudentRepository.cs file. Zastąp istniejący kod poniższym kodem, który implementuje IStudentRepository interfejsu:Replace the existing code with the following code, which implements the IStudentRepository interface:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using ContosoUniversity.Models;

namespace ContosoUniversity.DAL
{
    public class StudentRepository : IStudentRepository, IDisposable
    {
        private SchoolContext context;

        public StudentRepository(SchoolContext context)
        {
            this.context = context;
        }

        public IEnumerable<Student> GetStudents()
        {
            return context.Students.ToList();
        }

        public Student GetStudentByID(int id)
        {
            return context.Students.Find(id);
        }

        public void InsertStudent(Student student)
        {
            context.Students.Add(student);
        }

        public void DeleteStudent(int studentID)
        {
            Student student = context.Students.Find(studentID);
            context.Students.Remove(student);
        }

        public void UpdateStudent(Student student)
        {
            context.Entry(student).State = EntityState.Modified;
        }

        public void Save()
        {
            context.SaveChanges();
        }

        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    context.Dispose();
                }
            }
            this.disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

Kontekst bazy danych jest zdefiniowany w zmiennej klasy i konstruktora oczekuje, że obiekt wywołujący, aby przekazać wystąpienia kontekstu:The database context is defined in a class variable, and the constructor expects the calling object to pass in an instance of the context:

private SchoolContext context;

public StudentRepository(SchoolContext context)
{
    this.context = context;
}

Można utworzyć wystąpienia nowy kontekst w repozytorium, ale następnie użycie wielu repozytoriów w jednym kontrolerze każdego pojawiłyby się oddzielny kontekst.You could instantiate a new context in the repository, but then if you used multiple repositories in one controller, each would end up with a separate context. Później użyjesz wielu repozytoriów w Course kontrolera, a zobaczysz, jak jednostki pracy klasy można zagwarantować, że wszystkie repozytoria przy użyciu ten sam kontekst.Later you'll use multiple repositories in the Course controller, and you'll see how a unit of work class can ensure that all repositories use the same context.

Implementuje repozytorium interfejsu IDisposable i usuwa kontekst bazy danych, jak zanotowane wcześniej z kontrolera, a jego metod CRUD wykonywać wywołania kontekst bazy danych w taki sam sposób, który wcześniej.The repository implements IDisposable and disposes the database context as you saw earlier in the controller, and its CRUD methods make calls to the database context in the same way that you saw earlier.

Zmień kontroler uczniów do użycia w repozytoriumChange the Student Controller to Use the Repository

W StudentController.cs, Zastąp kod aktualnie w klasie, z następującym kodem.In StudentController.cs, replace the code currently in the class with the following code. Zmiany są wyróżnione.The changes are highlighted.

using System;
using System.Data;
using System.Linq;
using System.Web.Mvc;
using ContosoUniversity.Models;
using ContosoUniversity.DAL;
using PagedList;

namespace ContosoUniversity.Controllers
{
   public class StudentController : Controller
   {
      private IStudentRepository studentRepository;

      public StudentController()
      {
         this.studentRepository = new StudentRepository(new SchoolContext());
      }

      public StudentController(IStudentRepository studentRepository)
      {
         this.studentRepository = studentRepository;
      }

      //
      // GET: /Student/

      public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
      {
         ViewBag.CurrentSort = sortOrder;
         ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
         ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";

         if (searchString != null)
         {
            page = 1;
         }
         else
         {
            searchString = currentFilter;
         }
         ViewBag.CurrentFilter = searchString;

         var students = from s in studentRepository.GetStudents()
                        select s;
         if (!String.IsNullOrEmpty(searchString))
         {
            students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
                                   || s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
         }
         switch (sortOrder)
         {
            case "name_desc":
               students = students.OrderByDescending(s => s.LastName);
               break;
            case "Date":
               students = students.OrderBy(s => s.EnrollmentDate);
               break;
            case "date_desc":
               students = students.OrderByDescending(s => s.EnrollmentDate);
               break;
            default:  // Name ascending 
               students = students.OrderBy(s => s.LastName);
               break;
         }

         int pageSize = 3;
         int pageNumber = (page ?? 1);
         return View(students.ToPagedList(pageNumber, pageSize));
      }

      //
      // GET: /Student/Details/5

      public ViewResult Details(int id)
      {
         Student student = studentRepository.GetStudentByID(id);
         return View(student);
      }

      //
      // GET: /Student/Create

      public ActionResult Create()
      {
         return View();
      }

      //
      // POST: /Student/Create

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Create(
         [Bind(Include = "LastName, FirstMidName, EnrollmentDate")]
           Student student)
      {
         try
         {
            if (ModelState.IsValid)
            {
               studentRepository.InsertStudent(student);
               studentRepository.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
            ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
         }
         return View(student);
      }

      //
      // GET: /Student/Edit/5

      public ActionResult Edit(int id)
      {
         Student student = studentRepository.GetStudentByID(id);
         return View(student);
      }

      //
      // POST: /Student/Edit/5

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Edit(
         [Bind(Include = "LastName, FirstMidName, EnrollmentDate")]
         Student student)
      {
         try
         {
            if (ModelState.IsValid)
            {
               studentRepository.UpdateStudent(student);
               studentRepository.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
            ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
         }
         return View(student);
      }

      //
      // GET: /Student/Delete/5

      public ActionResult Delete(bool? saveChangesError = false, int id = 0)
      {
         if (saveChangesError.GetValueOrDefault())
         {
            ViewBag.ErrorMessage = "Delete failed. Try again, and if the problem persists see your system administrator.";
         }
         Student student = studentRepository.GetStudentByID(id);
         return View(student);
      }

      //
      // POST: /Student/Delete/5

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Delete(int id)
      {
         try
         {
            Student student = studentRepository.GetStudentByID(id);
            studentRepository.DeleteStudent(id);
            studentRepository.Save();
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
            return RedirectToAction("Delete", new { id = id, saveChangesError = true });
         }
         return RedirectToAction("Index");
      }

      protected override void Dispose(bool disposing)
      {
         studentRepository.Dispose();
         base.Dispose(disposing);
      }
   }
}

Kontroler teraz deklaruje zmienną klasy dla obiektu, który implementuje IStudentRepository interfejsu, a nie z klasy kontekstu:The controller now declares a class variable for an object that implements the IStudentRepository interface instead of the context class:

private IStudentRepository studentRepository;

Domyślnego (bezparametrowego) konstruktora tworzy nowe wystąpienie kontekstu i opcjonalnie konstruktor umożliwia obiektowi wywołującemu Przekaż wystąpienie kontekstu.The default (parameterless) constructor creates a new context instance, and an optional constructor allows the caller to pass in a context instance.

public StudentController()
{
    this.studentRepository = new StudentRepository(new SchoolContext());
}

public StudentController(IStudentRepository studentRepository)
{
    this.studentRepository = studentRepository;
}

(Jeśli używano wstrzykiwanie zależności, lub DI, nie należy konstruktora domyślnego, ponieważ oprogramowanie DI będzie upewnij się, obiekt poprawnego repozytorium zawsze będą dostarczane.)(If you were using dependency injection, or DI, you wouldn't need the default constructor because the DI software would ensure that the correct repository object would always be provided.)

W metodach CRUD repozytorium jest teraz nazywana zamiast kontekstu:In the CRUD methods, the repository is now called instead of the context:

var students = from s in studentRepository.GetStudents()
               select s;
Student student = studentRepository.GetStudentByID(id);
studentRepository.InsertStudent(student);
studentRepository.Save();
studentRepository.UpdateStudent(student);
studentRepository.Save();
studentRepository.DeleteStudent(id);
studentRepository.Save();

I Dispose metoda teraz usuwa repozytorium zamiast kontekstu:And the Dispose method now disposes the repository instead of the context:

studentRepository.Dispose();

Uruchamianie witryny, a następnie kliknij przycisk studentów kartę.Run the site and click the Students tab.

Students_Index_page

Strona wygląda i działa tak samo jak przed zmieniony kod, aby użyć repozytorium i innych stron dla uczniów również działać tak samo.The page looks and works the same as it did before you changed the code to use the repository, and the other Student pages also work the same. Dostępna jest ważna różnica w sposobie Index metody kontrolera jest filtrowanie i kolejności.However, there's an important difference in the way the Index method of the controller does filtering and ordering. Oryginalną wersję tej metody zawiera następujący kod:The original version of this method contained the following code:

var students = from s in context.Students
               select s;
if (!String.IsNullOrEmpty(searchString))
{
    students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
                           || s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
}

Zaktualizowany interfejs Index metoda zawiera następujący kod:The updated Index method contains the following code:

var students = from s in studentRepository.GetStudents()
                select s;
if (!String.IsNullOrEmpty(searchString))
{
    students = students.Where(s => s.LastName.ToUpper().Contains(searchString.ToUpper())
                        || s.FirstMidName.ToUpper().Contains(searchString.ToUpper()));
}

Wyróżniony kod został zmieniony.Only the highlighted code has changed.

W pierwotnej wersji kodu students jest IQueryable obiektu.In the original version of the code, students is typed as an IQueryable object. Zapytanie nie jest wysyłana do bazy danych, aż zostanie przekonwertowany do kolekcji przy użyciu metody, takie jak ToList, która nie występuje aż widok indeksu uzyskuje dostęp do modelu dla uczniów.The query isn't sent to the database until it's converted into a collection using a method such as ToList, which doesn't occur until the Index view accesses the student model. Where Staje się metody w powyższym kodzie oryginalnym WHERE klauzuli w kwerendzie SQL, które są wysyłane do bazy danych.The Where method in the original code above becomes a WHERE clause in the SQL query that is sent to the database. Z kolei oznacza, że tylko wybrane jednostki są zwracane przez bazę danych.That in turn means that only the selected entities are returned by the database. Jednak w wyniku zmiany context.Students do studentRepository.GetStudents(), students zmiennej po tej instrukcji IEnumerable kolekcję zawierającą wszystkich studentów w bazie danych.However, as a result of changing context.Students to studentRepository.GetStudents(), the students variable after this statement is an IEnumerable collection that includes all students in the database. Efekt zastosowania Where metoda jest taka sama, ale teraz praca odbywa się w pamięci na serwerze sieci web, a nie przez bazę danych.The end result of applying the Where method is the same, but now the work is done in memory on the web server and not by the database. Dla zapytań zwracających duże ilości danych może to być mało wydajne.For queries that return large volumes of data, this can be inefficient.

Tip

Element IQueryable programu vs. IEnumerableIQueryable vs. IEnumerable

Po zaimplementowaniu repozytorium jak pokazano poniżej, nawet wtedy, gdy element wprowadź wyszukiwania okno zapytania wysyłane do programu SQL Server zwraca wszystkie wiersze dla uczniów, ponieważ nie zawiera kryteria wyszukiwania:After you implement the repository as shown here, even if you enter something in the Search box the query sent to SQL Server returns all Student rows because it doesn't include your search criteria:

SELECT 
'0X0X' AS [C1], 
[Extent1].[PersonID] AS [PersonID], 
[Extent1].[LastName] AS [LastName], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[EnrollmentDate] AS [EnrollmentDate]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[Discriminator] = N'Student'

To zapytanie zwraca wszystkie dane dla uczniów, ponieważ repozytorium wykonywane zapytanie, nie wiedząc o tym dotyczącą kryteriów wyszukiwania.This query returns all of the student data because the repository executed the query without knowing about the search criteria. Proces sortowania, stosując kryteria wyszukiwania i wybierając podzbiór danych dla stronicowania (w tym przypadku wyświetlanie tylko 3 wiersze) odbywa się w pamięci po późniejszym ToPagedList wywoływana jest metoda IEnumerable kolekcji.The process of sorting, applying search criteria, and selecting a subset of the data for paging (showing only 3 rows in this case) is done in memory later when the ToPagedList method is called on the IEnumerable collection.

W poprzedniej wersji kodu (przed zastosowaniem repozytorium), zapytania nie są wysyłane do bazy danych, aż po zastosowaniu kryteriów wyszukiwania podczas ToPagedList jest wywoływana w IQueryable obiektu.In the previous version of the code (before you implemented the repository), the query is not sent to the database until after you apply the search criteria, when ToPagedList is called on the IQueryable object.

Gdy wywoływana jest ToPagedList IQueryable obiekt zapytania wysyłane do programu SQL Server określa ciąg wyszukiwania, w wyniku zwracane są tylko wiersze, które spełniają kryteria wyszukiwania i filtrowanie nie musi odbywać się w pamięci.When ToPagedList is called on an IQueryable object, the query sent to SQL Server specifies the search string, and as a result only rows that meet the search criteria are returned, and no filtering needs to be done in memory.

exec sp_executesql N'SELECT TOP (3) 
[Project1].[StudentID] AS [StudentID], 
[Project1].[LastName] AS [LastName], 
[Project1].[FirstName] AS [FirstName], 
[Project1].[EnrollmentDate] AS [EnrollmentDate]
FROM ( SELECT [Project1].[StudentID] AS [StudentID], [Project1].[LastName] AS [LastName], [Project1].[FirstName] AS [FirstName], [Project1].[EnrollmentDate] AS [EnrollmentDate], row_number() OVER (ORDER BY [Project1].[LastName] ASC) AS [row_number]
FROM ( SELECT 
    [Extent1].[StudentID] AS [StudentID], 
    [Extent1].[LastName] AS [LastName], 
    [Extent1].[FirstName] AS [FirstName], 
    [Extent1].[EnrollmentDate] AS [EnrollmentDate]
    FROM [dbo].[Student] AS [Extent1]
    WHERE (( CAST(CHARINDEX(UPPER(@p__linq__0), UPPER([Extent1].[LastName])) AS int)) > 0) OR (( CAST(CHARINDEX(UPPER(@p__linq__1), UPPER([Extent1].[FirstName])) AS int)) > 0)
)  AS [Project1]
)  AS [Project1]
WHERE [Project1].[row_number] > 0
ORDER BY [Project1].[LastName] ASC',N'@p__linq__0 nvarchar(4000),@p__linq__1 nvarchar(4000)',@p__linq__0=N'Alex',@p__linq__1=N'Alex'

(Następującego samouczka wyjaśniono, jak sprawdzić zapytania wysyłane do programu SQL Server).(The following tutorial explains how to examine queries sent to SQL Server.)

W poniższej sekcji pokazano, jak implementować metody repozytorium, które umożliwiają określenie, że ta praca ma się odbywać przez bazę danych.The following section shows how to implement repository methods that enable you to specify that this work should be done by the database.

Utworzono warstwę abstrakcji między kontrolerem i kontekst bazy danych programu Entity Framework.You've now created an abstraction layer between the controller and the Entity Framework database context. Jeśli zostały zamierza przeprowadzić zautomatyzowane testy jednostkowe za pomocą tej aplikacji, można utworzyć klasę alternatywnych repozytorium w projekcie testu jednostki, która implementuje IStudentRepository .If you were going to perform automated unit testing with this application, you could create an alternative repository class in a unit test project that implements IStudentRepository. Zamiast wywoływania kontekst do odczytu i zapisu danych, ta klasa makiety repozytorium można manipulować kolekcji w pamięci w celu przetestowania funkcje kontrolera.Instead of calling the context to read and write data, this mock repository class could manipulate in-memory collections in order to test controller functions.

Implementowanie ogólnego repozytorium i jednostki pracy, klasaImplement a Generic Repository and a Unit of Work Class

Tworzenie klasy repozytorium dla każdego typu jednostki może spowodować wiele nadmiarowy kod, a może to spowodować aktualizacje częściowe.Creating a repository class for each entity type could result in a lot of redundant code, and it could result in partial updates. Na przykład załóżmy, że musisz zaktualizować dwa typy różne jednostki w ramach tej samej transakcji.For example, suppose you have to update two different entity types as part of the same transaction. Jeśli każda używa wystąpienie kontekstu oddzielnej bazy danych, jeden może się powieść, a druga może zakończyć się niepowodzeniem.If each uses a separate database context instance, one might succeed and the other might fail. Jednym ze sposobów, aby zminimalizować nadmiarowy kod jest użycie ogólnego repozytorium i jednym ze sposobów, aby upewnić się, że wszystkie repozytoria używać ten sam kontekst bazy danych (a zatem skoordynować wszystkie aktualizacje) jest użycie jednostki pracy klasy.One way to minimize redundant code is to use a generic repository, and one way to ensure that all repositories use the same database context (and thus coordinate all updates) is to use a unit of work class.

W tej części samouczka utworzysz GenericRepository klasy i UnitOfWork klasy i używać ich w Course kontrolera dostęp zarówno do Department i Course zestawy jednostek.In this section of the tutorial, you'll create a GenericRepository class and a UnitOfWork class, and use them in the Course controller to access both the Department and the Course entity sets. Jak wyjaśniono wcześniej, w celu uproszczenia tej części samouczka, możesz nie są Tworzenie interfejsów dla tych klas.As explained earlier, to keep this part of the tutorial simple, you aren't creating interfaces for these classes. Ale były ma być używane do ułatwienia TDD, można będzie zazwyczaj wdrożyć je przy użyciu interfejsów taki sam sposób jak Student repozytorium.But if you were going to use them to facilitate TDD, you'd typically implement them with interfaces the same way you did the Student repository.

Tworzenie ogólnego repozytoriumCreate a Generic Repository

W DAL folderze utwórz GenericRepository.cs i Zastąp istniejący kod następującym kodem:In the DAL folder, create GenericRepository.cs and replace the existing code with the following code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using System.Data.Entity;
using ContosoUniversity.Models;
using System.Linq.Expressions;

namespace ContosoUniversity.DAL
{
    public class GenericRepository<TEntity> where TEntity : class
    {
        internal SchoolContext context;
        internal DbSet<TEntity> dbSet;

        public GenericRepository(SchoolContext context)
        {
            this.context = context;
            this.dbSet = context.Set<TEntity>();
        }

        public virtual IEnumerable<TEntity> Get(
            Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "")
        {
            IQueryable<TEntity> query = dbSet;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            foreach (var includeProperty in includeProperties.Split
                (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            if (orderBy != null)
            {
                return orderBy(query).ToList();
            }
            else
            {
                return query.ToList();
            }
        }

        public virtual TEntity GetByID(object id)
        {
            return dbSet.Find(id);
        }

        public virtual void Insert(TEntity entity)
        {
            dbSet.Add(entity);
        }

        public virtual void Delete(object id)
        {
            TEntity entityToDelete = dbSet.Find(id);
            Delete(entityToDelete);
        }

        public virtual void Delete(TEntity entityToDelete)
        {
            if (context.Entry(entityToDelete).State == EntityState.Detached)
            {
                dbSet.Attach(entityToDelete);
            }
            dbSet.Remove(entityToDelete);
        }

        public virtual void Update(TEntity entityToUpdate)
        {
            dbSet.Attach(entityToUpdate);
            context.Entry(entityToUpdate).State = EntityState.Modified;
        }
    }
}

Zmienne klasy są deklarowane w kontekście bazy danych, a dla zestawu jednostek, który jest skonkretyzowany repozytorium:Class variables are declared for the database context and for the entity set that the repository is instantiated for:

internal SchoolContext context;
internal DbSet dbSet;

Konstruktor akceptuje wystąpienie kontekstu bazy danych i inicjuje zmienną zestaw jednostek:The constructor accepts a database context instance and initializes the entity set variable:

public GenericRepository(SchoolContext context)
{
    this.context = context;
    this.dbSet = context.Set<TEntity>();
}

Get Metoda używa wyrażenia lambda, aby umożliwić kod wywołujący, aby określić warunek filtru i kolumny, aby uporządkować wyniki według, a parametr ciągu umożliwia obiekt wywołujący, podaj rozdzielana przecinkami lista właściwości nawigacji dla wczesne ładowanie:The Get method uses lambda expressions to allow the calling code to specify a filter condition and a column to order the results by, and a string parameter lets the caller provide a comma-delimited list of navigation properties for eager loading:

public virtual IEnumerable<TEntity> Get(
    Expression<Func<TEntity, bool>> filter = null,
    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
    string includeProperties = "")

Kod Expression<Func<TEntity, bool>> filter oznacza, że obiekt wywołujący zapewni Wyrażenie lambda, na podstawie TEntity typu, a to wyrażenie zwróci wartość logiczną.The code Expression<Func<TEntity, bool>> filter means the caller will provide a lambda expression based on the TEntity type, and this expression will return a Boolean value. Na przykład, jeśli repozytorium jest skonkretyzowany Student typu jednostki, kod w metodzie wywołujący może określić student => student.LastName == "Smith " dla filter parametru.For example, if the repository is instantiated for the Student entity type, the code in the calling method might specify student => student.LastName == "Smith" for the filter parameter.

Kod Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy oznacza również, że obiekt wywołujący zapewni wyrażenia lambda.The code Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy also means the caller will provide a lambda expression. Ale w tym przypadku dane wejściowe używając wyrażenia IQueryable dla obiektu TEntity typu.But in this case, the input to the expression is an IQueryable object for the TEntity type. Wyrażenie zwróci uporządkowane wersję tego IQueryable obiektu.The expression will return an ordered version of that IQueryable object. Na przykład, jeśli repozytorium jest skonkretyzowany Student typu jednostki, kod w metodzie wywołujący może określić q => q.OrderBy(s => s.LastName) dla orderBy parametru.For example, if the repository is instantiated for the Student entity type, the code in the calling method might specify q => q.OrderBy(s => s.LastName) for the orderBy parameter.

Kod w Get metoda tworzy IQueryable obiektu, a następnie stosuje wyrażenie filtru, jeśli istnieje:The code in the Get method creates an IQueryable object and then applies the filter expression if there is one:

IQueryable<TEntity> query = dbSet;

if (filter != null)
{
    query = query.Where(filter);
}

Następnie stosuje wyrażeń eager ładowania po analizie rozdzielana przecinkami lista:Next it applies the eager-loading expressions after parsing the comma-delimited list:

foreach (var includeProperty in includeProperties.Split
    (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) 
{ 
    query = query.Include(includeProperty); 
}

Na koniec stosuje orderBy wyrażenia, jeśli istnieje i zwraca wyniki; w przeciwnym razie zwraca wyniki z nieuporządkowaną kwerendy:Finally, it applies the orderBy expression if there is one and returns the results; otherwise it returns the results from the unordered query:

if (orderBy != null)
{
    return orderBy(query).ToList();
}
else
{
    return query.ToList();
}

Gdy wywołujesz Get metody, można było przeprowadzać filtrowanie i sortowanie zawartości IEnumerable kolekcji zwróconej przez metodę zamiast podawać parametrów dla tych funkcji.When you call the Get method, you could do filtering and sorting on the IEnumerable collection returned by the method instead of providing parameters for these functions. Ale sortowanie i filtrowanie następnie będą wykonywane w pamięci na serwerze sieci web.But the sorting and filtering work would then be done in memory on the web server. Za pomocą tych parametrów, należy upewnić się, prace wykonywane przez bazy danych, a nie na serwerze sieci web.By using these parameters, you ensure that the work is done by the database rather than the web server. Alternatywą jest tworzenie klas pochodnych dla typów w określonej jednostce i dodaj specjalne Get metod, takich jak GetStudentsInNameOrder lub GetStudentsByName.An alternative is to create derived classes for specific entity types and add specialized Get methods, such as GetStudentsInNameOrder or GetStudentsByName. Jednak w złożonych aplikacji, może to spowodować dużej liczby takich klas pochodnych i specjalne metody, które może być więcej pracy do obsługi.However, in a complex application, this can result in a large number of such derived classes and specialized methods, which could be more work to maintain.

Kod w GetByID, Insert, i Update metody jest podobny do przedstawionego w repozytorium nieogólnego.The code in the GetByID, Insert, and Update methods is similar to what you saw in the non-generic repository. (Nie są podając parametr wczesne ładowanie w GetByID podpisu, ponieważ nie można tego zrobić wczesne ładowanie z Find metody.)(You aren't providing an eager loading parameter in the GetByID signature, because you can't do eager loading with the Find method.)

Dwa przeciążenia są dostarczane dla Delete metody:Two overloads are provided for the Delete method:

public virtual void Delete(object id)
{
    TEntity entityToDelete = dbSet.Find(id);
    dbSet.Remove(entityToDelete);
}

public virtual void Delete(TEntity entityToDelete)
{
    if (context.Entry(entityToDelete).State == EntityState.Detached)
    {
        dbSet.Attach(entityToDelete);
    }
    dbSet.Remove(entityToDelete);
}

Jedną z tych umożliwia przekazanej tylko identyfikator obiektu do usunięcia, a jedno wykorzystuje wystąpienia jednostki.One of these lets you pass in just the ID of the entity to be deleted, and one takes an entity instance. Jak widać w Obsługa współbieżności samouczek, należy możesz Obsługa współbieżności Delete metody, która przyjmuje wystąpienie jednostki, która obejmuje oryginalnej wartości właściwości śledzenia.As you saw in the Handling Concurrency tutorial, for concurrency handling you need a Delete method that takes an entity instance that includes the original value of a tracking property.

Ten ogólny repozytorium będzie obsługiwać typowych wymagań CRUD.This generic repository will handle typical CRUD requirements. Gdy typ określonego obiektu ma specjalne wymagania, takie jak bardziej złożone filtrowanie lub kolejności, można utworzyć klasy pochodnej, która ma dodatkowe metody dla tego typu.When a particular entity type has special requirements, such as more complex filtering or ordering, you can create a derived class that has additional methods for that type.

Tworzenie jednostki pracy klasyCreating the Unit of Work Class

Klasa pracy jednostki służy jeden cel: aby upewnić się, korzystając z wieloma repozytoriami, współużytkują one kontekstu pojedynczej bazy danych.The unit of work class serves one purpose: to make sure that when you use multiple repositories, they share a single database context. Po zakończeniu jednostka pracy w ten sposób można wywołać SaveChanges metody, w tym wystąpieniu kontekstu i mieć pewność, wszystkie powiązane zmiany zostaną skoordynowany.That way, when a unit of work is complete you can call the SaveChanges method on that instance of the context and be assured that all related changes will be coordinated. Wszystko to jest klasa potrzeb Save metod i właściwości dla każdego repozytorium.All that the class needs is a Save method and a property for each repository. Każda właściwość repozytorium Zwraca wystąpienie repozytorium, które zostały utworzone przy użyciu tego samego wystąpienia bazy danych w kontekście co inne wystąpienia repozytorium.Each repository property returns a repository instance that has been instantiated using the same database context instance as the other repository instances.

W DAL folderze utwórz plik klasy o nazwie UnitOfWork.cs i Zastąp kod szablonu poniższym kodem:In the DAL folder, create a class file named UnitOfWork.cs and replace the template code with the following code:

using System;
using ContosoUniversity.Models;

namespace ContosoUniversity.DAL
{
    public class UnitOfWork : IDisposable
    {
        private SchoolContext context = new SchoolContext();
        private GenericRepository<Department> departmentRepository;
        private GenericRepository<Course> courseRepository;

        public GenericRepository<Department> DepartmentRepository
        {
            get
            {

                if (this.departmentRepository == null)
                {
                    this.departmentRepository = new GenericRepository<Department>(context);
                }
                return departmentRepository;
            }
        }

        public GenericRepository<Course> CourseRepository
        {
            get
            {

                if (this.courseRepository == null)
                {
                    this.courseRepository = new GenericRepository<Course>(context);
                }
                return courseRepository;
            }
        }

        public void Save()
        {
            context.SaveChanges();
        }

        private bool disposed = false;

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    context.Dispose();
                }
            }
            this.disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

Ten kod tworzy zmienne klasy kontekstu bazy danych i każdego repozytorium.The code creates class variables for the database context and each repository. Aby uzyskać context zmiennej, zostanie uruchomiony nowy kontekst:For the context variable, a new context is instantiated:

private SchoolContext context = new SchoolContext();
private GenericRepository<Department> departmentRepository;
private GenericRepository<Course> courseRepository;

Każda właściwość repozytorium sprawdza, czy repozytorium już istnieje.Each repository property checks whether the repository already exists. W przeciwnym razie metoda tworzy repozytorium, przekazując to wystąpienie kontekstu.If not, it instantiates the repository, passing in the context instance. W rezultacie wszystkie repozytoria współużytkują to samo wystąpienie kontekstu.As a result, all repositories share the same context instance.

public GenericRepository<Department> DepartmentRepository
{
    get
    {

        if (this.departmentRepository == null)
        {
            this.departmentRepository = new GenericRepository<Department>(context);
        }
        return departmentRepository;
    }
}

Save Wywołania metody SaveChanges w kontekście bazy danych.The Save method calls SaveChanges on the database context.

Wszystkie klasy, która tworzy w zmiennej klasy kontekstu bazy danych, takich jak UnitOfWork klasy implementuje IDisposable i usuwa kontekst.Like any class that instantiates a database context in a class variable, the UnitOfWork class implements IDisposable and disposes the context.

Zmiana kontroler kurs klasy UnitOfWork i repozytoriówChanging the Course Controller to use the UnitOfWork Class and Repositories

Zastąp kod w CourseController.cs następującym kodem:Replace the code you currently have in CourseController.cs with the following code:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using ContosoUniversity.Models;
using ContosoUniversity.DAL;

namespace ContosoUniversity.Controllers
{
   public class CourseController : Controller
   {
      private UnitOfWork unitOfWork = new UnitOfWork();

      //
      // GET: /Course/

      public ViewResult Index()
      {
         var courses = unitOfWork.CourseRepository.Get(includeProperties: "Department");
         return View(courses.ToList());
      }

      //
      // GET: /Course/Details/5

      public ViewResult Details(int id)
      {
         Course course = unitOfWork.CourseRepository.GetByID(id);
         return View(course);
      }

      //
      // GET: /Course/Create

      public ActionResult Create()
      {
         PopulateDepartmentsDropDownList();
         return View();
      }

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Create(
          [Bind(Include = "CourseID,Title,Credits,DepartmentID")]
         Course course)
      {
         try
         {
            if (ModelState.IsValid)
            {
               unitOfWork.CourseRepository.Insert(course);
               unitOfWork.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.)
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
         }
         PopulateDepartmentsDropDownList(course.DepartmentID);
         return View(course);
      }

      public ActionResult Edit(int id)
      {
         Course course = unitOfWork.CourseRepository.GetByID(id);
         PopulateDepartmentsDropDownList(course.DepartmentID);
         return View(course);
      }

      [HttpPost]
      [ValidateAntiForgeryToken]
      public ActionResult Edit(
           [Bind(Include = "CourseID,Title,Credits,DepartmentID")]
         Course course)
      {
         try
         {
            if (ModelState.IsValid)
            {
               unitOfWork.CourseRepository.Update(course);
               unitOfWork.Save();
               return RedirectToAction("Index");
            }
         }
         catch (DataException /* dex */)
         {
            //Log the error (uncomment dex variable name after DataException and add a line here to write a log.)
            ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
         }
         PopulateDepartmentsDropDownList(course.DepartmentID);
         return View(course);
      }

      private void PopulateDepartmentsDropDownList(object selectedDepartment = null)
      {
         var departmentsQuery = unitOfWork.DepartmentRepository.Get(
             orderBy: q => q.OrderBy(d => d.Name));
         ViewBag.DepartmentID = new SelectList(departmentsQuery, "DepartmentID", "Name", selectedDepartment);
      }

      //
      // GET: /Course/Delete/5

      public ActionResult Delete(int id)
      {
         Course course = unitOfWork.CourseRepository.GetByID(id);
         return View(course);
      }

      //
      // POST: /Course/Delete/5

      [HttpPost, ActionName("Delete")]
      [ValidateAntiForgeryToken]
      public ActionResult DeleteConfirmed(int id)
      {
         Course course = unitOfWork.CourseRepository.GetByID(id);
         unitOfWork.CourseRepository.Delete(id);
         unitOfWork.Save();
         return RedirectToAction("Index");
      }

      protected override void Dispose(bool disposing)
      {
         unitOfWork.Dispose();
         base.Dispose(disposing);
      }
   }
}

Ten kod dodaje zmienną klasy UnitOfWork klasy.This code adds a class variable for the UnitOfWork class. (Jeśli była używana interfejsów w tym miejscu, w takich sytuacjach przydałaby zainicjować zmienną, w tym miejscu; zamiast tego możesz również zaimplementować wzorzec dwa konstruktory tak, jak została ona Student repozytorium.)(If you were using interfaces here, you wouldn't initialize the variable here; instead, you'd implement a pattern of two constructors just as you did for the Student repository.)

private UnitOfWork unitOfWork = new UnitOfWork();

W pozostałej części klasy, wszystkie odwołania do kontekstu bazy danych są zastępowane przez odwołania do odpowiedniego repozytorium przy użyciu UnitOfWork właściwości, aby uzyskać dostępu do repozytorium.In the rest of the class, all references to the database context are replaced by references to the appropriate repository, using UnitOfWork properties to access the repository. Dispose Usuwa metoda UnitOfWork wystąpienia.The Dispose method disposes the UnitOfWork instance.

var courses = unitOfWork.CourseRepository.Get(includeProperties: "Department");
// ...
Course course = unitOfWork.CourseRepository.GetByID(id);
// ...
unitOfWork.CourseRepository.Insert(course);
unitOfWork.Save();
// ...
Course course = unitOfWork.CourseRepository.GetByID(id);
// ...
unitOfWork.CourseRepository.Update(course);
unitOfWork.Save();
// ...
var departmentsQuery = unitOfWork.DepartmentRepository.Get(
    orderBy: q => q.OrderBy(d => d.Name));
// ...
Course course = unitOfWork.CourseRepository.GetByID(id);
// ...
unitOfWork.CourseRepository.Delete(id);
unitOfWork.Save();
// ...
unitOfWork.Dispose();

Uruchamianie witryny, a następnie kliknij przycisk kursów kartę.Run the site and click the Courses tab.

Courses_Index_page

Strona wygląda i działa tak samo jak wcześniej zmiany i innych stron kurs również działać tak samo.The page looks and works the same as it did before your changes, and the other Course pages also work the same.

PodsumowanieSummary

Udało Ci się teraz wdrożyć repozytorium i jednostki pracy.You have now implemented both the repository and unit of work patterns. Wyrażenia lambda używanych jako parametry metody w ogólnego repozytorium.You have used lambda expressions as method parameters in the generic repository. Aby uzyskać więcej informacji o sposobie używania tych wyrażeń z IQueryable obiektu, zobacz IQueryable(T) interfejsu (System.Linq) w bibliotece MSDN.For more information about how to use these expressions with an IQueryable object, see IQueryable(T) Interface (System.Linq) in the MSDN Library. W następnym samouczku dowiesz się, jak obsługiwać niektórych zaawansowanych scenariuszy.In the next tutorial you'll learn how to handle some advanced scenarios.

Linki do innych zasobów platformy Entity Framework można znaleźć w Mapa zawartości dostępu do danych ASP.NET.Links to other Entity Framework resources can be found in the ASP.NET Data Access Content Map.