Implementando o repositório e os padrões de unidade de trabalho em um aplicativo MVC ASP.NET (9 de 10)Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application (9 of 10)

por Tom Dykstraby Tom Dykstra

Baixar projeto concluídoDownload Completed Project

O aplicativo Web de exemplo da Contoso University demonstra como criar aplicativos ASP.NET MVC 4 usando o Entity Framework 5 Code First e o 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. Para obter informações sobre a série de tutoriais, consulte primeiro tutorial na série.For information about the tutorial series, see the first tutorial in the series. Você pode iniciar a série de tutoriais desde o início ou baixar um projeto inicial para este capítulo e começar aqui.You can start the tutorial series from the beginning or download a starter project for this chapter and start here.

Note

Se você encontrar um problema que não possa resolver, Baixe o capítulo concluído e tente reproduzir o problema.If you run into a problem you can't resolve, download the completed chapter and try to reproduce your problem. Em geral, você pode encontrar a solução para o problema comparando seu código com o código concluído.You can generally find the solution to the problem by comparing your code to the completed code. Para alguns erros comuns e como resolvê-los, consulte erros e soluções alternativas.For some common errors and how to solve them, see Errors and Workarounds.

No tutorial anterior, você usou a herança para reduzir o código redundante nas classes de entidade Student e Instructor.In the previous tutorial you used inheritance to reduce redundant code in the Student and Instructor entity classes. Neste tutorial, você verá algumas maneiras de usar o repositório e os padrões de unidade de trabalho para operações CRUD.In this tutorial you'll see some ways to use the repository and unit of work patterns for CRUD operations. Como no tutorial anterior, neste, você alterará a maneira como seu código funciona com páginas que você já criou, em vez de criar novas páginas.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.

O repositório e os padrões de unidade de trabalhoThe Repository and Unit of Work Patterns

O repositório e os padrões de unidade de trabalho destinam-se a criar uma camada de abstração entre a camada de acesso a dados e a camada de lógica de negócios de um aplicativo.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. A implementação desses padrões pode ajudar a isolar o aplicativo de alterações no armazenamento de dados e pode facilitar o teste de unidade automatizado ou TDD (desenvolvimento orientado por testes).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).

Neste tutorial, você implementará uma classe de repositório para cada tipo de entidade.In this tutorial you'll implement a repository class for each entity type. Para o tipo de entidade Student, você criará uma interface de repositório e uma classe de repositório.For the Student entity type you'll create a repository interface and a repository class. Ao criar uma instância do repositório em seu controlador, você usará a interface para que o controlador aceite uma referência a qualquer objeto que implemente a interface do repositório.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. Quando o controlador é executado em um servidor Web, ele recebe um repositório que funciona com o Entity Framework.When the controller runs under a web server, it receives a repository that works with the Entity Framework. Quando o controlador é executado sob uma classe de teste de unidade, ele recebe um repositório que funciona com dados armazenados de forma que você possa manipular facilmente para teste, como uma coleção na memória.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.

Posteriormente no tutorial, você usará vários repositórios e uma classe de unidade de trabalho para os tipos de entidade Course e Department no controlador de Course.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. A classe de unidade de trabalho coordena o trabalho de vários repositórios criando uma única classe de contexto de banco de dados compartilhada por todos eles.The unit of work class coordinates the work of multiple repositories by creating a single database context class shared by all of them. Se você quisesse ser capaz de executar testes de unidade automatizados, você criaria e usará interfaces para essas classes da mesma maneira que fazia para o repositório de Student.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. No entanto, para manter o tutorial simples, você criará e usará essas classes sem interfaces.However, to keep the tutorial simple, you'll create and use these classes without interfaces.

A ilustração a seguir mostra uma maneira de conceituar as relações entre o controlador e as classes de contexto em comparação com não usar o repositório ou o padrão de unidade de trabalho.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

Você não criará testes de unidade nesta série de tutoriais.You won't create unit tests in this tutorial series. Para obter uma introdução ao TDD com um aplicativo MVC que usa o padrão de repositório, confira Walkthrough: usando TDD com 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. Para obter mais informações sobre o padrão de repositório, consulte os seguintes recursos:For more information about the repository pattern, see the following resources:

Note

Há várias maneiras de implementar o repositório e os padrões de unidade de trabalho.There are many ways to implement the repository and unit of work patterns. Você pode usar classes de repositório com ou sem uma classe de unidade de trabalho.You can use repository classes with or without a unit of work class. Você pode implementar um único repositório para todos os tipos de entidade ou um para cada tipo.You can implement a single repository for all entity types, or one for each type. Se você implementar um para cada tipo, poderá usar classes separadas, uma classe base genérica e classes derivadas, ou uma classe base abstrata e classes derivadas.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. Você pode incluir lógica de negócios em seu repositório ou restringi-la à lógica de acesso a dados.You can include business logic in your repository or restrict it to data access logic. Você também pode criar uma camada de abstração na sua classe de contexto de banco de dados usando interfaces IDbSet em vez de tipos DbSet para seus conjuntos de entidades.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. A abordagem para implementar uma camada de abstração mostrada neste tutorial é uma opção a ser considerada, não uma recomendação para todos os cenários e ambientes.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.

Criando a classe de repositório StudentCreating the Student Repository Class

Na pasta Dal , crie um arquivo de classe chamado IStudentRepository.cs e substitua o código existente pelo código a seguir: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();
    }
}

Esse código declara um conjunto típico de métodos CRUD, incluindo dois métodos de leitura — um que retorna todas as entidades de Student e outro que localiza uma única entidade de Student por ID.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.

Na pasta Dal , crie um arquivo de classe chamado StudentRepository.cs File.In the DAL folder, create a class file named StudentRepository.cs file. Substitua o código existente pelo código a seguir, que implementa a interface IStudentRepository: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);
        }
    }
}

O contexto do banco de dados é definido em uma variável de classe e o construtor espera que o objeto de chamada passe em uma instância do contexto: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;
}

Você pode criar uma instância de um novo contexto no repositório, mas, se você tiver usado vários repositórios em um controlador, cada um acabaria com um contexto separado.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. Posteriormente, você usará vários repositórios no controlador de Course e verá como uma classe de unidade de trabalho pode garantir que todos os repositórios usem o mesmo contexto.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.

O repositório implementa IDisposable e descarta o contexto do banco de dados como vimos anteriormente no controlador, e seus métodos CRUD fazem chamadas ao contexto do banco de dados da mesma maneira que vimos anteriormente.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.

Alterar o controlador do aluno para usar o repositórioChange the Student Controller to Use the Repository

No StudentController.cs, substitua o código atualmente na classe pelo código a seguir.In StudentController.cs, replace the code currently in the class with the following code. As alterações são realçadas.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);
      }
   }
}

O controlador agora declara uma variável de classe para um objeto que implementa a interface IStudentRepository em vez da classe de contexto:The controller now declares a class variable for an object that implements the IStudentRepository interface instead of the context class:

private IStudentRepository studentRepository;

O construtor padrão (sem parâmetros) cria uma nova instância de contexto e um Construtor opcional permite que o chamador transmita uma instância de contexto.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;
}

(Se você estivesse usando injeção de dependência, ou di, não precisaria do construtor padrão porque o software di garantiria que o objeto de repositório correto sempre seria fornecido.)(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.)

Nos métodos CRUD, o repositório agora é chamado em vez do contexto: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();

E o método Dispose agora descarta o repositório em vez do contexto:And the Dispose method now disposes the repository instead of the context:

studentRepository.Dispose();

Execute o site e clique na guia alunos .Run the site and click the Students tab.

Students_Index_page

A página parece e funciona da mesma maneira que antes você alterou o código para usar o repositório, e as outras páginas de aluno também funcionam da mesma forma.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. No entanto, há uma diferença importante na maneira como o método Index do controlador faz a filtragem e a ordenação.However, there's an important difference in the way the Index method of the controller does filtering and ordering. A versão original desse método continha o seguinte código: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()));
}

O método Index atualizado contém o seguinte código: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()));
}

Somente o código realçado foi alterado.Only the highlighted code has changed.

Na versão original do código, students é tipada como um objeto IQueryable.In the original version of the code, students is typed as an IQueryable object. A consulta não é enviada ao banco de dados até ser convertida em uma coleção usando um método como ToList, o que não ocorrerá até que a exibição de índice acesse o modelo de aluno.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. O método Where no código original acima se torna uma cláusula WHERE na consulta SQL que é enviada ao banco de dados.The Where method in the original code above becomes a WHERE clause in the SQL query that is sent to the database. Isso, por sua vez, significa que apenas as entidades selecionadas são retornadas pelo banco de dados.That in turn means that only the selected entities are returned by the database. No entanto, como resultado da alteração de context.Students para studentRepository.GetStudents(), a variável students após essa instrução é uma coleção de IEnumerable que inclui todos os alunos do banco de dados.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. O resultado final da aplicação do método de Where é o mesmo, mas agora o trabalho é feito na memória no servidor Web e não no banco de dados.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. Para consultas que retornam grandes volumes de dados, isso pode ser ineficiente.For queries that return large volumes of data, this can be inefficient.

Tip

IQueryable versus IEnumerableIQueryable vs. IEnumerable

Depois de implementar o repositório, conforme mostrado aqui, mesmo se você inserir algo na caixa de pesquisa , a consulta enviada para SQL Server retornará todas as linhas de aluno porque não inclui seus critérios de pesquisa: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'

Essa consulta retorna todos os dados do aluno porque o repositório executou a consulta sem saber mais sobre os critérios de pesquisa.This query returns all of the student data because the repository executed the query without knowing about the search criteria. O processo de classificação, aplicação de critérios de pesquisa e seleção de um subconjunto dos dados para paginação (mostrando apenas três linhas nesse caso) é feito na memória mais tarde quando o método de ToPagedList é chamado na coleção de IEnumerable.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.

Na versão anterior do código (antes de você implementar o repositório), a consulta não será enviada ao banco de dados até que você aplique os critérios de pesquisa quando ToPagedList for chamado no objeto IQueryable.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.

Quando ToPagedList é chamado em um objeto IQueryable, a consulta enviada para SQL Server especifica a cadeia de caracteres de pesquisa e, como resultado, somente as linhas que atendem aos critérios de pesquisa são retornadas e nenhuma filtragem precisa ser feita na memória.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'

(O tutorial a seguir explica como examinar as consultas enviadas para SQL Server.)(The following tutorial explains how to examine queries sent to SQL Server.)

A seção a seguir mostra como implementar métodos de repositório que permitem especificar que esse trabalho deve ser feito pelo banco de dados.The following section shows how to implement repository methods that enable you to specify that this work should be done by the database.

Agora você criou uma camada de abstração entre o controlador e o contexto do banco de dados Entity Framework.You've now created an abstraction layer between the controller and the Entity Framework database context. Se você for executar testes de unidade automatizados com esse aplicativo, poderá criar uma classe de repositório alternativa em um projeto de teste de unidade que implementa 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. Em vez de chamar o contexto para ler e gravar dados, essa classe de repositório fictício poderia manipular as coleções na memória para testar as funções do controlador.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.

Implementar um repositório genérico e uma unidade de classe de trabalhoImplement a Generic Repository and a Unit of Work Class

A criação de uma classe de repositório para cada tipo de entidade pode resultar em muitos códigos redundantes e pode resultar em atualizações parciais.Creating a repository class for each entity type could result in a lot of redundant code, and it could result in partial updates. Por exemplo, suponha que você tenha que atualizar dois tipos de entidade diferentes como parte da mesma transação.For example, suppose you have to update two different entity types as part of the same transaction. Se cada uma delas usar uma instância de contexto de banco de dados separada, uma delas poderá ser bem-sucedida e a outra poderá falhar.If each uses a separate database context instance, one might succeed and the other might fail. Uma maneira de minimizar o código redundante é usar um repositório genérico e uma maneira de garantir que todos os repositórios usem o mesmo contexto de banco de dados (e, portanto, coordenar todas as atualizações) é usar uma classe de unidade de trabalho.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.

Nesta seção do tutorial, você criará uma classe de GenericRepository e uma classe de UnitOfWork e as usará no controlador de Course para acessar os conjuntos de entidades Department e Course.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. Conforme explicado anteriormente, para manter essa parte do tutorial simples, você não está criando interfaces para essas classes.As explained earlier, to keep this part of the tutorial simple, you aren't creating interfaces for these classes. Mas se você pretende usá-los para facilitar o TDD, normalmente os implementaria com interfaces da mesma maneira que fez no repositório de Student.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.

Criar um repositório genéricoCreate a Generic Repository

Na pasta Dal , crie GenericRepository.cs e substitua o código existente pelo código a seguir: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;
        }
    }
}

As variáveis de classe são declaradas para o contexto do banco de dados e para o conjunto de entidades para o qual o repositório é instanciado: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;

O construtor aceita uma instância de contexto de banco de dados e inicializa a variável de conjunto de entidades: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>();
}

O método Get usa expressões lambda para permitir que o código de chamada especifique uma condição de filtro e uma coluna para ordenar os resultados por, e um parâmetro de cadeia de caracteres permite que o chamador forneça uma lista delimitada por vírgulas de propriedades de navegação para carregamento adiantado: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 = "")

O Expression<Func<TEntity, bool>> filter de código significa que o chamador fornecerá uma expressão lambda com base no tipo de TEntity, e essa expressão retornará um valor booliano.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. Por exemplo, se o repositório for instanciado para o tipo de entidade Student, o código no método de chamada poderá especificar student => student.LastName == "Smith" para o parâmetro filter.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.

O código Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy também significa que o chamador fornecerá uma expressão lambda.The code Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy also means the caller will provide a lambda expression. Mas, nesse caso, a entrada para a expressão é um objeto IQueryable para o tipo TEntity.But in this case, the input to the expression is an IQueryable object for the TEntity type. A expressão retornará uma versão ordenada do objeto IQueryable.The expression will return an ordered version of that IQueryable object. Por exemplo, se o repositório for instanciado para o tipo de entidade Student, o código no método de chamada poderá especificar q => q.OrderBy(s => s.LastName) para o parâmetro orderBy.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.

O código no método Get cria um objeto IQueryable e, em seguida, aplica a expressão de filtro se houver um: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);
}

Em seguida, ele aplica as expressões de carregamento adiantado após a análise da lista delimitada por vírgulas: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); 
}

Por fim, ele aplica a expressão orderBy se houver um e retorna os resultados; caso contrário, ele retorna os resultados da consulta não ordenada: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();
}

Ao chamar o método Get, você pode filtrar e classificar na coleção de IEnumerable retornada pelo método, em vez de fornecer parâmetros para essas funções.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. Mas o trabalho de classificação e filtragem seria feito na memória no servidor Web.But the sorting and filtering work would then be done in memory on the web server. Usando esses parâmetros, você garante que o trabalho seja feito pelo banco de dados em vez do servidor Web.By using these parameters, you ensure that the work is done by the database rather than the web server. Uma alternativa é criar classes derivadas para tipos de entidade específicos e adicionar métodos de Get especializados, como GetStudentsInNameOrder ou GetStudentsByName.An alternative is to create derived classes for specific entity types and add specialized Get methods, such as GetStudentsInNameOrder or GetStudentsByName. No entanto, em um aplicativo complexo, isso pode resultar em um grande número de classes derivadas e de métodos especializados, o que pode ser mais trabalho a ser mantido.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.

O código nos métodos GetByID, Inserte Update é semelhante ao que você viu no repositório não genérico.The code in the GetByID, Insert, and Update methods is similar to what you saw in the non-generic repository. (Você não está fornecendo um parâmetro de carregamento adiantado na assinatura GetByID, porque não pode fazer o carregamento adiantado com o método Find.)(You aren't providing an eager loading parameter in the GetByID signature, because you can't do eager loading with the Find method.)

Duas sobrecargas são fornecidas para o método de Delete: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);
}

Um deles permite que você passe apenas a ID da entidade a ser excluída e uma instância de entidade.One of these lets you pass in just the ID of the entity to be deleted, and one takes an entity instance. Como vimos no tutorial sobre a simultaneidade de manipulação , para manipulação de simultaneidade, você precisa de um Delete método que usa uma instância de entidade que inclui o valor original de uma propriedade de rastreamento.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.

Esse repositório genérico tratará os requisitos CRUD típicos.This generic repository will handle typical CRUD requirements. Quando um determinado tipo de entidade tem requisitos especiais, como filtragem ou ordenação mais complexa, você pode criar uma classe derivada que tenha métodos adicionais para esse tipo.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.

Criando a classe de unidade de trabalhoCreating the Unit of Work Class

A classe da unidade de trabalho atende a uma finalidade: para garantir que, quando você usar vários repositórios, eles compartilhem um único contexto de banco de dados.The unit of work class serves one purpose: to make sure that when you use multiple repositories, they share a single database context. Dessa forma, quando uma unidade de trabalho for concluída, você poderá chamar o método SaveChanges nessa instância do contexto e ter certeza de que todas as alterações relacionadas serão coordenadas.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. Tudo o que a classe precisa é um método Save e uma propriedade para cada repositório.All that the class needs is a Save method and a property for each repository. Cada propriedade Repository retorna uma instância de repositório que foi instanciada usando a mesma instância de contexto de banco de dados que as outras instâncias de repositório.Each repository property returns a repository instance that has been instantiated using the same database context instance as the other repository instances.

Na pasta Dal , crie um arquivo de classe chamado UnitOfWork.cs e substitua o código do modelo pelo código a seguir: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);
        }
    }
}

O código cria variáveis de classe para o contexto do banco de dados e para cada repositório.The code creates class variables for the database context and each repository. Para a variável context, um novo contexto é instanciado:For the context variable, a new context is instantiated:

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

Cada propriedade de repositório verifica se o repositório já existe.Each repository property checks whether the repository already exists. Caso contrário, ele instancia o repositório, passando a instância de contexto.If not, it instantiates the repository, passing in the context instance. Como resultado, todos os repositórios compartilham a mesma instância de contexto.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;
    }
}

O método Save chama SaveChanges no contexto do banco de dados.The Save method calls SaveChanges on the database context.

Como qualquer classe que instancia um contexto de banco de dados em uma variável de classe, a classe UnitOfWork implementa IDisposable e descarta o contexto.Like any class that instantiates a database context in a class variable, the UnitOfWork class implements IDisposable and disposes the context.

Alterando o controlador do curso para usar a classe e os repositórios UnitOfWorkChanging the Course Controller to use the UnitOfWork Class and Repositories

Substitua o código que você tem atualmente em CourseController.cs com o seguinte código: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);
      }
   }
}

Esse código adiciona uma variável de classe para a classe UnitOfWork.This code adds a class variable for the UnitOfWork class. (Se você estivesse usando interfaces aqui, não inicializaria a variável aqui; em vez disso, você implementaria um padrão de dois construtores da mesma forma que fazia para o repositório de Student.)(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();

No restante da classe, todas as referências ao contexto do banco de dados são substituídas por referências ao repositório apropriado, usando UnitOfWork Propriedades para acessar o repositório.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. O método Dispose descarta a instância de UnitOfWork.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();

Execute o site e clique na guia cursos .Run the site and click the Courses tab.

Courses_Index_page

A página parece e funciona da mesma maneira que antes das alterações, e as outras páginas do curso também funcionam da mesma forma.The page looks and works the same as it did before your changes, and the other Course pages also work the same.

ResumoSummary

Agora você implementou o repositório e os padrões de unidade de trabalho.You have now implemented both the repository and unit of work patterns. Você usou expressões lambda como parâmetros de método no repositório genérico.You have used lambda expressions as method parameters in the generic repository. Para obter mais informações sobre como usar essas expressões com um objeto IQueryable, consulte interface IQueryable (t) (System. Linq) na biblioteca MSDN.For more information about how to use these expressions with an IQueryable object, see IQueryable(T) Interface (System.Linq) in the MSDN Library. No próximo tutorial, você aprenderá a lidar com alguns cenários avançados.In the next tutorial you'll learn how to handle some advanced scenarios.

Links para outros recursos de Entity Framework podem ser encontrados no mapa de conteúdo de acesso a dados do ASP.net.Links to other Entity Framework resources can be found in the ASP.NET Data Access Content Map.