Tutorial: Implementar a herança com o EF em um aplicativo ASP.NET MVC 5
No tutorial anterior, você lidou com exceções de simultaneidade. Este tutorial mostrará como implementar a herança no modelo de dados.
Na programação orientada a objetos, você pode usar a herança para facilitar a reutilização de código. Neste tutorial, você alterará as classes Instructor
e Student
, de modo que elas derivem de uma classe base Person
que contém propriedades, como LastName
, comuns a instrutores e alunos. Você não adicionará nem alterará as páginas da Web, mas alterará uma parte do código, e essas alterações serão refletidas automaticamente no banco de dados.
Neste tutorial, você:
- Saiba como mapear a herança para o banco de dados
- Criar a classe Person
- Atualizará Instructor e Student
- Adicionar Pessoa ao Modelo
- Criar e atualizar migrações
- Testará a implementação
- Implantar no Azure
Pré-requisitos
Mapeará a herança para o banco de dados
As Instructor
classes e Student
no School
modelo de dados têm várias propriedades idênticas:
Suponha que você deseje eliminar o código redundante para as propriedades compartilhadas pelas entidades Instructor
e Student
. Ou você deseja escrever um serviço que pode formatar nomes sem se preocupar se o nome foi obtido de um instrutor ou um aluno. Você pode criar uma Person
classe base que contenha apenas essas propriedades compartilhadas e, em seguida, fazer com que as Instructor
entidades e Student
herdem dessa classe base, conforme mostrado na ilustração a seguir:
Há várias maneiras pelas quais essa estrutura de herança pode ser representada no banco de dados. Você pode ter uma tabela Person
que inclui informações sobre alunos e instrutores em uma única tabela. Algumas das colunas só podem ser aplicadas a instrutores (HireDate
), algumas somente aos alunos (EnrollmentDate
), algumas a ambos (LastName
, FirstName
). Normalmente, você teria uma coluna discriminatória para indicar qual tipo cada linha representa. Por exemplo, a coluna discriminatória pode ter "Instrutor" para instrutores e "Aluno" para alunos.
Esse padrão de geração de uma estrutura de herança de entidade de uma única tabela de banco de dados é chamado de herança de tabela por hierarquia (TPH).
Uma alternativa é fazer com que o banco de dados se pareça mais com a estrutura de herança. Por exemplo, você pode ter apenas os campos de nome na tabela Person
e ter tabelas Instructor
e Student
separadas com os campos de data.
Esse padrão de criação de uma tabela de banco de dados para cada classe de entidade é chamado de herança TPT ( tabela por tipo ).
Outra opção é mapear todos os tipos não abstratos para tabelas individuais. Todas as propriedades de uma classe, incluindo propriedades herdadas, são mapeadas para colunas da tabela correspondente. Esse padrão é chamado de herança TPC (Tabela por Classe Concreta). Se você tiver implementado a herança TPC para as classes Person
, Student
e Instructor
, conforme mostrado anteriormente, as tabelas Student
e Instructor
não parecerão diferentes após a implementação da herança do que antes.
Os padrões de herança TPC e TPH geralmente oferecem melhor desempenho no Entity Framework do que padrões de herança TPT, pois os padrões TPT podem resultar em consultas de junção complexas.
Este tutorial demonstra como implementar a herança TPH. TPH é o padrão de herança no Entity Framework, portanto, tudo o que você precisa fazer é criar uma Person
classe, alterar as Instructor
classes e Student
para derivar de Person
, adicionar a nova classe ao DbContext
e criar uma migração. (Para obter informações sobre como implementar os outros padrões de herança, consulte Mapeando a herança de tabela por tipo (TPT) e Mapeando a herança da classe TPC (Table-Per-Concrete Class) na documentação do MSDN Entity Framework.)
Criar a classe Person
Na pasta Modelos , crie Person.cs e substitua o código do modelo pelo seguinte código:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public abstract class Person
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
}
}
Atualizará Instructor e Student
Agora atualize Instructor.cs e Student.cs para herdar valores do Person.sc.
Em Instructor.cs, derive a Instructor
classe da Person
classe e remova os campos chave e nome. O código será semelhante ao seguinte exemplo:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Instructor : Person
{
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
public virtual ICollection<Course> Courses { get; set; }
public virtual OfficeAssignment OfficeAssignment { get; set; }
}
}
Faça alterações semelhantes em Student.cs. A Student
classe será semelhante ao seguinte exemplo:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student : Person
{
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Adicionar Pessoa ao Modelo
Em SchoolContext.cs, adicione uma DbSet
propriedade para o Person
tipo de entidade:
public DbSet<Person> People { get; set; }
Isso é tudo o que o Entity Framework precisa para configurar a herança de tabela por hierarquia. Como você verá, quando o banco de dados for atualizado, ele terá uma Person
tabela no lugar das Student
tabelas e Instructor
.
Criar e atualizar migrações
No PMC (Console do Gerenciador de Pacotes), insira o seguinte comando:
Add-Migration Inheritance
Execute o Update-Database
comando no PMC. O comando falhará neste ponto porque temos dados existentes que as migrações não sabem como lidar. Você recebe uma mensagem de erro como a seguinte:
Não foi possível remover o objeto 'dbo. Instrutor' porque ele é referenciado por uma restrição FOREIGN KEY.
Abra Migrações< timestamp>_Inheritance.cs e substitua o Up
método pelo seguinte código:
public override void Up()
{
// Drop foreign keys and indexes that point to tables we're going to drop.
DropForeignKey("dbo.Enrollment", "StudentID", "dbo.Student");
DropIndex("dbo.Enrollment", new[] { "StudentID" });
RenameTable(name: "dbo.Instructor", newName: "Person");
AddColumn("dbo.Person", "EnrollmentDate", c => c.DateTime());
AddColumn("dbo.Person", "Discriminator", c => c.String(nullable: false, maxLength: 128, defaultValue: "Instructor"));
AlterColumn("dbo.Person", "HireDate", c => c.DateTime());
AddColumn("dbo.Person", "OldId", c => c.Int(nullable: true));
// Copy existing Student data into new Person table.
Sql("INSERT INTO dbo.Person (LastName, FirstName, HireDate, EnrollmentDate, Discriminator, OldId) SELECT LastName, FirstName, null AS HireDate, EnrollmentDate, 'Student' AS Discriminator, ID AS OldId FROM dbo.Student");
// Fix up existing relationships to match new PK's.
Sql("UPDATE dbo.Enrollment SET StudentId = (SELECT ID FROM dbo.Person WHERE OldId = Enrollment.StudentId AND Discriminator = 'Student')");
// Remove temporary key
DropColumn("dbo.Person", "OldId");
DropTable("dbo.Student");
// Re-create foreign keys and indexes pointing to new table.
AddForeignKey("dbo.Enrollment", "StudentID", "dbo.Person", "ID", cascadeDelete: true);
CreateIndex("dbo.Enrollment", "StudentID");
}
Este código é responsável pelas seguintes tarefas de atualização de banco de dados:
Remove as restrições de chave estrangeira e índices que apontam para a tabela Aluno.
Renomeia a tabela Instrutor como Pessoa e faz as alterações necessárias para que ela armazene dados de Aluno:
- Adiciona EnrollmentDate que permite valo nulo para os alunos.
- Adiciona a coluna Discriminatória para indicar se uma linha refere-se a um aluno ou um instrutor.
- Faz com que HireDate permita valor nulo, pois linhas de alunos não terão datas de contratação.
- Adiciona um campo temporário que será usado para atualizar chaves estrangeiras que apontam para alunos. Quando você copiar os alunos para a tabela Pessoa, eles receberão novos valores de chave primária.
Copia os dados da tabela Aluno para a tabela Pessoa. Isso faz com que os alunos recebam novos valores de chave primária.
Corrige valores de chave estrangeira que apontam para alunos.
Recria restrições de chave estrangeira e índices, agora apontando-os para a tabela Person.
(Se você tiver usado o GUID, em vez de inteiro como o tipo de chave primária, os valores de chave primária dos alunos não precisarão ser alterados e várias dessas etapas poderão ser omitidas.)
Execute o comando update-database
novamente.
(Em um sistema de produção, você faria alterações correspondentes ao método Down caso tivesse que usá-lo para voltar para a versão anterior do banco de dados. Para este tutorial, você não usará o método Down.)
Observação
É possível obter outros erros ao migrar dados e fazer alterações de esquema. Se você receber erros de migração não puder resolve, poderá continuar com o tutorial alterando o cadeia de conexão no arquivo Web.config ou excluindo o banco de dados. A abordagem mais simples é renomear o banco de dados no arquivo Web.config . Por exemplo, altere o nome do banco de dados para ContosoUniversity2, conforme mostrado no exemplo a seguir:
<add name="SchoolContext"
connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=ContosoUniversity2;Integrated Security=SSPI;"
providerName="System.Data.SqlClient" />
Com um novo banco de dados, não há dados a serem migrados e é muito mais provável que o update-database
comando seja concluído sem erros. Para obter instruções sobre como excluir o banco de dados, consulte Como remover um banco de dados do Visual Studio 2012. Se você adotar essa abordagem para continuar com o tutorial, ignore a etapa de implantação no final deste tutorial ou implante em um novo site e banco de dados. Se você implantar uma atualização no mesmo site em que já está implantando, o EF receberá o mesmo erro quando executar as migrações automaticamente. Se você quiser solucionar um erro de migrações, o melhor recurso será um dos fóruns ou StackOverflow.com do Entity Framework.
Testará a implementação
Execute o site e experimente várias páginas. Tudo funciona da mesma maneira que antes.
Em Explorer de Servidor, expanda Conexões de Dados\SchoolContext e, em seguida, Tabelas, e você verá que as tabelas Aluno e Instrutor foram substituídas por uma tabela Person. Expanda a tabela Pessoa e você verá que ela tem todas as colunas que costumavam estar nas tabelas Aluno e Instrutor .
Clique com o botão direito do mouse na tabela Person e, em seguida, clique em Mostrar Dados da Tabela para ver a coluna discriminatória.
O diagrama a seguir ilustra a estrutura do novo banco de dados Escolar:
Implantar no Azure
Esta seção exige que você tenha concluído a seção opcional Implantando o aplicativo no Azure na Parte 3, Classificação, Filtragem e Paginação desta série de tutoriais. Se você tiver erros de migrações resolvidos excluindo o banco de dados em seu projeto local, ignore esta etapa; ou crie um novo site e banco de dados e implante no novo ambiente.
No Visual Studio, clique com o botão direito do mouse no projeto no Gerenciador de Soluções e selecione Publicar no menu de contexto.
Clique em Publicar.
O aplicativo Web é aberto no navegador padrão.
Teste o aplicativo para verificar se ele está funcionando.
Na primeira vez que você executa uma página que acessa o banco de dados, o Entity Framework executa todos os métodos de migrações necessários
Up
para atualizar o banco de dados com o modelo de dados atual.
Obter o código
Recursos adicionais
Links para outros recursos do Entity Framework podem ser encontrados no ASP.NET Acesso a Dados – Recursos Recomendados.
Para obter mais informações sobre essa e outras estruturas de herança, consulte Padrão de Herança TPT e Padrão de Herança TPH no MSDN. No próximo tutorial, você verá como lidar com uma variedade de cenários relativamente avançados do Entity Framework.
Próximas etapas
Neste tutorial, você:
- Aprendeu a mapear a herança para o banco de dados
- Criou a classe Person
- Atualizou Instructor e Student
- Pessoa adicionada ao modelo
- Migrações criadas e atualizadas
- Testou a implementação
- Implantado no Azure
Avance para o próximo artigo para saber mais sobre os tópicos que são úteis para estar ciente quando você vai além dos conceitos básicos do desenvolvimento ASP.NET aplicativos Web que usam o Entity Framework Code First.