Compartilhar via


Tutorial: Criar um modelo de dados mais complexo para um aplicativo MVC ASP.NET

Nos tutoriais anteriores, você trabalhou com um modelo de dados simples composto por três entidades. Neste tutorial, você adiciona mais entidades e relações e personaliza o modelo de dados especificando regras de formatação, validação e mapeamento de banco de dados. Este artigo mostra duas maneiras de personalizar o modelo de dados: adicionando atributos a classes de entidade e adicionando código à classe de contexto do banco de dados.

Quando terminar, as classes de entidade formarão o modelo de dados concluído mostrado na seguinte ilustração:

School_class_diagram

Neste tutorial, você:

  • Personalizar o modelo de dados
  • Atualizar entidade Student
  • Criar a entidade Instructor
  • Criar a entidade OfficeAssignment
  • Modificar a entidade Course
  • Criar a entidade Department
  • Modificar a entidade Enrollment
  • Adicionar código ao contexto do banco de dados
  • Propagar o banco de dados com dados de teste
  • Adicionar uma migração
  • Atualizar o banco de dados

Pré-requisitos

Personalizar o modelo de dados

Nesta seção, você verá como personalizar o modelo de dados usando atributos que especificam formatação, validação e regras de mapeamento de banco de dados. Em seguida, em várias das seções a seguir, você criará o modelo de dados completo School adicionando atributos às classes que você já criou e criando novas classes para os tipos de entidade restantes no modelo.

O atributo DataType

Para datas de registro de alunos, todas as páginas da Web atualmente exibem a hora junto com a data, embora tudo o que você deseje exibir nesse campo seja a data. Usando atributos de anotação de dados, você pode fazer uma alteração de código que corrigirá o formato de exibição em cada exibição que mostra os dados. Para ver um exemplo de como fazer isso, você adicionará um atributo à propriedade EnrollmentDate na classe Student.

Em Models\Student.cs, adicione uma using instrução para o System.ComponentModel.DataAnnotations namespace e adicione DataType atributos e DisplayFormat à EnrollmentDate propriedade, conforme mostrado no exemplo a seguir:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }
        
        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

O atributo DataType é usado para especificar um tipo de dados mais específico do que o tipo intrínseco do banco de dados. Nesse caso, apenas desejamos acompanhar a data, não a data e a hora. A Enumeração de Tipo de Dados fornece muitos tipos de dados, como Data, Hora, Número de Telefone, Moeda, Endereço de Email e muito mais. O atributo DataType também pode permitir que o aplicativo forneça automaticamente recursos específicos a um tipo. Por exemplo, um mailto: link pode ser criado para DataType.EmailAddress e um seletor de data pode ser fornecido para DataType.Date em navegadores que dão suporte a HTML5. Os atributos DataType emitem atributos HTML 5 data- (pronuncia-se data dash) que os navegadores HTML 5 podem entender. Os atributos DataType não fornecem nenhuma validação.

DataType.Date não especifica o formato da data exibida. Por padrão, o campo de dados é exibido de acordo com os formatos padrão com base no CultureInfo do servidor.

O atributo DisplayFormat é usado para especificar explicitamente o formato de data:

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

A ApplyFormatInEditMode configuração especifica que a formatação especificada também deve ser aplicada quando o valor é exibido em uma caixa de texto para edição. (Talvez você não queira isso para alguns campos — por exemplo, para valores de moeda, talvez não queira o símbolo de moeda na caixa de texto para edição.)

Você pode usar o atributo DisplayFormat sozinho, mas geralmente é uma boa ideia usar o atributo DataType também. O DataType atributo transmite a semântica dos dados em vez de como renderizá-los em uma tela e fornece os seguintes benefícios que você não obtém comDisplayFormat:

  • O navegador pode habilitar os recursos do HTML5 (por exemplo, mostrar um controle de calendário, o símbolo de moeda apropriado à localidade, links de email, uma validação de entrada do lado do cliente, etc.).
  • Por padrão, o navegador renderizará os dados usando o formato correto com base em sua localidade.
  • O atributo DataType pode permitir que o MVC escolha o modelo de campo correto para renderizar os dados (o DisplayFormat usa o modelo de cadeia de caracteres). Para obter mais informações, consulte os modelos ASP.NET MVC 2 de Brad Wilson. (Embora escrito para MVC 2, este artigo ainda se aplica à versão atual do ASP.NET MVC.)

Se você usar o DataType atributo com um campo de data, também precisará especificar o DisplayFormat atributo para garantir que o campo seja renderizado corretamente nos navegadores Chrome. Para obter mais informações, consulte este tópico do StackOverflow.

Para obter mais informações sobre como lidar com outros formatos de data no MVC, acesse Introdução ao MVC 5: Examinando os métodos de edição e o Modo de Exibição de Edição e pesquise na página por "internacionalização".

Execute a página Índice de Alunos novamente e observe que os horários não são mais exibidos para as datas de inscrição. O mesmo será verdadeiro para qualquer vista que use o Student modelo.

Students_index_page_with_formatted_date

O StringLengthAttribute

Você também pode especificar regras de validação de dados e mensagens de erro de validação usando atributos. O atributo StringLength define o comprimento máximo no banco de dados e fornece validação do lado do cliente e do lado do servidor para ASP.NET MVC. Você também pode especificar o tamanho mínimo da cadeia de caracteres nesse atributo, mas o valor mínimo não tem nenhum impacto sobre o esquema de banco de dados.

Suponha que você deseje garantir que os usuários não insiram mais de 50 caracteres em um nome. Para adicionar essa limitação, adicione atributos StringLength às LastName propriedades and FirstMidName , conforme mostrado no exemplo a seguir:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [StringLength(50)]
        public string LastName { get; set; }
        [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        public DateTime EnrollmentDate { get; set; }
        
        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

O atributo StringLength não impedirá que um usuário insira espaço em branco para um nome. Você pode usar o atributo RegularExpression para aplicar restrições à entrada. Por exemplo, o código a seguir requer que o primeiro caractere seja maiúsculo e os caracteres restantes sejam alfabéticos:

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]

O atributo MaxLength fornece funcionalidade semelhante ao atributo StringLength , mas não fornece validação do lado do cliente.

Execute o aplicativo e clique na guia Alunos. Você recebe o seguinte erro:

O modelo que dá suporte ao contexto 'SchoolContext' foi alterado desde que o banco de dados foi criado. Considere usar as Migrações do Code First para atualizar o banco de dados (https://go.microsoft.com/fwlink/?LinkId=238269).

O modelo de banco de dados foi alterado de uma forma que requer uma alteração no esquema de banco de dados, e o Entity Framework detectou isso. Você usará migrações para atualizar o esquema sem perder os dados adicionados ao banco de dados usando a interface do usuário. Se você alterou os Seed dados criados pelo método, eles serão alterados de volta ao seu estado original devido ao método AddOrUpdate que você está usando no Seed método. (AddOrUpdate é equivalente a uma operação "upsert" da terminologia do banco de dados.)

No PMC (Console do Gerenciador de Pacotes), Insira os seguintes comandos:

add-migration MaxLengthOnNames
update-database

O add-migration comando cria um arquivo chamado <timeStamp>_MaxLengthOnNames.cs. Esse arquivo contém o código no método Up que atualizará o banco de dados para que ele corresponda ao modelo de dados atual. O comando update-database executou esse código.

O carimbo de data/hora anexado ao nome do arquivo de migrações é usado pelo Entity Framework para ordenar as migrações. Você pode criar várias migrações antes de executar o update-database comando e, em seguida, todas as migrações são aplicadas na ordem em que foram criadas.

Execute a página Criar e insira um dos nomes com mais de 50 caracteres. Quando você clica em Criar, a validação do lado do cliente mostra uma mensagem de erro: O campo LastName deve ser uma cadeia de caracteres com um comprimento máximo de 50.

O atributo de coluna

Você também pode usar atributos para controlar como as classes e propriedades são mapeadas para o banco de dados. Suponha que você tenha usado o nome FirstMidName para o campo de nome porque o campo também pode conter um sobrenome. Mas você deseja que a coluna do banco de dados seja nomeada FirstName, pois os usuários que escreverão consultas ad hoc no banco de dados estão acostumados com esse nome. Para fazer esse mapeamento, use o atributo Column.

O atributo Column especifica que quando o banco de dados for criado, a coluna da tabela Student que é mapeada para a propriedade FirstMidName será nomeada FirstName. Em outras palavras, quando o código se referir a Student.FirstMidName, os dados serão obtidos ou atualizados na coluna FirstName da tabela Student. Se você não especificar nomes de coluna, eles receberão o mesmo nome que o nome da propriedade.

No arquivo Student.cs, adicione uma using instrução para System.ComponentModel.DataAnnotations.Schema e adicione o atributo de nome da coluna à FirstMidName propriedade, conforme mostrado no código realçado a seguir:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        [StringLength(50)]       
        public string LastName { get; set; }
        [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
        [Column("FirstName")]
        public string FirstMidName { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

        public DateTime EnrollmentDate { get; set; }
        
        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

A adição do atributo Column altera o modelo que dá suporte ao SchoolContext, portanto, ele não corresponderá ao banco de dados. Insira os seguintes comandos no PMC para criar outra migração:

add-migration ColumnFirstName
update-database

No Gerenciador de Servidores, abra o designer de tabela Aluno clicando duas vezes na tabela Aluno .

A imagem a seguir mostra o nome da coluna original como era antes de você aplicar as duas primeiras migrações. Além do nome da coluna mudar de FirstMidName para FirstName, as duas colunas de nome foram alteradas de MAX comprimento para 50 caracteres.

Duas capturas de tela que mostram as diferenças no Nome e no Tipo de Dados das duas tabelas Student.

Você também pode fazer alterações no mapeamento do banco de dados usando a API Fluent, como verá mais adiante neste tutorial.

Observação

Se você tentar compilar antes de concluir a criação de todas as classes de entidade nas seções a seguir, poderá receber erros do compilador.

Atualizar entidade Student

Em Models\Student.cs, substitua o código adicionado anteriormente pelo código a seguir. As alterações são realçadas.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Student
    {
        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; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Enrollment Date")]
        public DateTime EnrollmentDate { get; set; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get
            {
                return LastName + ", " + FirstMidName;
            }
        }

        public virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

O atributo obrigatório

O atributo Obrigatório torna as propriedades do nome campos obrigatórios. O Required attribute não é necessário para tipos de valor como DateTime, int, double e float. Os tipos de valor não podem receber um valor nulo, portanto, eles são tratados inerentemente como campos obrigatórios.

O atributo Required precisa ser usado com MinimumLength para que MinimumLength seja imposto.

[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }

MinimumLength e Required permitem que o espaço em branco atenda à validação. Use o RegularExpression atributo para controle total sobre a cadeia de caracteres.

O atributo de exibição

O atributo Display especifica que a legenda para as caixas de texto deve ser "Nome", "Sobrenome", "Nome Completo" e "Data de Registro", em vez do nome de a propriedade em cada instância (que não tem nenhum espaço entre as palavras).

A propriedade calculada FullName

FullName é uma propriedade calculada que retorna um valor criado pela concatenação de duas outras propriedades. Portanto, ele tem apenas um get acessador e nenhuma FullName coluna será gerada no banco de dados.

Criar a entidade Instructor

Crie Models\Instructor.cs, substituindo o código do modelo pelo seguinte código:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Instructor
    {
        public int ID { get; set; }

        [Required]
        [Display(Name = "Last Name")]
        [StringLength(50)]
        public string LastName { get; set; }

        [Required]
        [Column("FirstName")]
        [Display(Name = "First Name")]
        [StringLength(50)]
        public string FirstMidName { get; set; }

        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Hire Date")]
        public DateTime HireDate { get; set; }

        [Display(Name = "Full Name")]
        public string FullName
        {
            get { return LastName + ", " + FirstMidName; }
        }

        public virtual ICollection<Course> Courses { get; set; }
        public virtual OfficeAssignment OfficeAssignment { get; set; }
    }
}

Observe que várias propriedades são iguais nas entidades Student e Instructor. No tutorial Implementando a herança mais adiante nesta série, você refatorará esse código para eliminar a redundância.

Você pode colocar vários atributos em uma linha, portanto, também pode escrever a classe de instrutor da seguinte maneira:

public class Instructor
{
   public int ID { get; set; }

   [Display(Name = "Last Name"),StringLength(50, MinimumLength=1)]
   public string LastName { get; set; }

   [Column("FirstName"),Display(Name = "First Name"),StringLength(50, MinimumLength=1)]
   public string FirstMidName { get; set; }

   [DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
   public DateTime HireDate { get; set; }

   [Display(Name = "Full Name")]
   public string FullName
   {
      get { return LastName + ", " + FirstMidName; }
   }

   public virtual ICollection<Course> Courses { get; set; }
   public virtual OfficeAssignment OfficeAssignment { get; set; }
}

As propriedades de navegação Courses e OfficeAssignment

As propriedades Courses e OfficeAssignment são propriedades de navegação. Como foi explicado anteriormente, eles normalmente são definidos como virtuais para que possam aproveitar um recurso do Entity Framework chamado carregamento lento. Além disso, se uma propriedade de navegação puder conter várias entidades, seu tipo deverá implementar a Interface T ICollection<.> Por exemplo , IList<T> se qualifica, mas não IEnumerable<T> porque IEnumerable<T> não implementa Add.

Um instrutor pode ministrar qualquer número de cursos, portanto Courses , é definido como uma coleção de Course entidades.

public virtual ICollection<Course> Courses { get; set; }

Nossas regras de negócios afirmam que um instrutor só pode ter no máximo um escritório, portanto OfficeAssignment , é definido como uma única OfficeAssignment entidade (que pode ser null se nenhum escritório for atribuído).

public virtual OfficeAssignment OfficeAssignment { get; set; }

Criar a entidade OfficeAssignment

Crie Modelos\OfficeAssignment.cs com o seguinte código:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class OfficeAssignment
    {
        [Key]
        [ForeignKey("Instructor")]
        public int InstructorID { get; set; }
        [StringLength(50)]
        [Display(Name = "Office Location")]
        public string Location { get; set; }

        public virtual Instructor Instructor { get; set; }
    }
}

Crie o projeto, que salva suas alterações e verifica se você não cometeu nenhum erro de copiar e colar que o compilador possa detectar.

O atributo de chave

Há uma relação “um para zero ou um” entre as entidades Instructor e OfficeAssignment. Uma atribuição de escritório existe apenas em relação ao instrutor ao qual ela é atribuída e, portanto, sua chave primária também é a chave estrangeira da entidade Instructor. Mas o Entity Framework não pode reconhecer InstructorID automaticamente como a chave primária dessa entidade porque seu nome não segue a ID convenção de nomenclatura ou classnameID. Portanto, o atributo Key é usado para identificá-la como a chave:

[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }

Você também pode usar o Key atributo se a entidade tiver sua própria chave primária, mas quiser nomear a propriedade com algo diferente de classnameID ou ID. Por padrão, o EF trata a chave como não gerada pelo banco de dados porque a coluna é para uma relação de identificação.

O atributo ForeignKey

Quando há uma relação um-para-zero-ou-um ou uma relação um-para-um entre duas entidades (como entre OfficeAssignment e Instructor), o EF não consegue descobrir qual extremidade da relação é a principal e qual extremidade é dependente. As relações um-para-um têm uma propriedade de navegação de referência em cada classe para a outra classe. O atributo ForeignKey pode ser aplicado à classe dependente para estabelecer a relação. Se você omitir o atributo ForeignKey, receberá o seguinte erro ao tentar criar a migração:

Não é possível determinar a extremidade principal de uma associação entre os tipos 'ContosoUniversity.Models.OfficeAssignment' e 'ContosoUniversity.Models.Instructor'. A extremidade principal dessa associação deve ser configurada explicitamente usando a API fluente de relacionamento ou anotações de dados.

Mais adiante no tutorial, você verá como configurar essa relação com a API fluente.

A propriedade de navegação do instrutor

A Instructor entidade tem uma propriedade de navegação anulável OfficeAssignment (porque um instrutor pode não ter uma atribuição de escritório) e a OfficeAssignment entidade tem uma propriedade de navegação não anulável Instructor (porque uma atribuição de escritório não pode existir sem um instrutor – InstructorID não é anulável). Quando uma Instructor entidade tiver uma entidade relacionada OfficeAssignment , cada entidade terá uma referência à outra em sua propriedade de navegação.

Você pode colocar um [Required] atributo na propriedade de navegação Instructor para especificar que deve haver um instrutor relacionado, mas não precisa fazer isso porque a chave estrangeira InstructorID (que também é a chave para essa tabela) não permite valor nulo.

Modificar a entidade Course

Em Models\Course.cs, substitua o código adicionado anteriormente pelo seguinte código:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
   public class Course
   {
      [DatabaseGenerated(DatabaseGeneratedOption.None)]
      [Display(Name = "Number")]
      public int CourseID { get; set; }

      [StringLength(50, MinimumLength = 3)]
      public string Title { get; set; }

      [Range(0, 5)]
      public int Credits { get; set; }

      public int DepartmentID { get; set; }

      public virtual Department Department { get; set; }
      public virtual ICollection<Enrollment> Enrollments { get; set; }
      public virtual ICollection<Instructor> Instructors { get; set; }
   }
}

A entidade course tem uma propriedade DepartmentID de chave estrangeira que aponta para a entidade relacionada Department e tem uma Department propriedade de navegação. O Entity Framework não exige que você adicione uma propriedade de chave estrangeira ao modelo de dados quando você tem uma propriedade de navegação para uma entidade relacionada. O EF cria automaticamente chaves estrangeiras no banco de dados sempre que necessário. No entanto, ter a chave estrangeira no modelo de dados pode tornar as atualizações mais simples e mais eficientes. Por exemplo, quando você busca uma entidade de curso para editar, a Department entidade é nula se você não carregá-la, portanto, ao atualizar a entidade de curso, você teria que primeiro buscar a Department entidade. Quando a propriedade de chave estrangeira DepartmentID estiver incluída no modelo de dados, você não precisará buscar a entidade Department antes da atualização.

O atributo DatabaseGenerated

O atributo DatabaseGenerated com o parâmetro None na CourseID propriedade especifica que os valores de chave primária são fornecidos pelo usuário em vez de gerados pelo banco de dados.

[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

Por padrão, o Entity Framework pressupõe que os valores de chave primária sejam gerados pelo banco de dados. É isso que você quer na maioria dos cenários. No entanto, para entidades Course, você usará um número de curso especificado pelo usuário como uma série de 1.000 de um departamento, uma série de 2.000 para outro departamento e assim por diante.

Propriedades de chave estrangeira e navegação

As propriedades de navegação e de chave estrangeira na entidade Course refletem as seguintes relações:

  • Um curso é atribuído a um departamento e, portanto, há uma propriedade de chave estrangeira DepartmentID e de navegação Department pelas razões mencionadas acima.

    public int DepartmentID { get; set; }
    public virtual Department Department { get; set; }
    
  • Um curso pode ter qualquer quantidade de estudantes inscritos; portanto, a propriedade de navegação Enrollments é uma coleção:

    public virtual ICollection<Enrollment> Enrollments { get; set; }
    
  • Um curso pode ser ministrado por vários instrutores; portanto, a propriedade de navegação Instructors é uma coleção:

    public virtual ICollection<Instructor> Instructors { get; set; }
    

Criar a entidade Department

Crie Modelos\Department.cs com o seguinte código:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
   public class Department
   {
      public int DepartmentID { get; set; }

      [StringLength(50, MinimumLength=3)]
      public string Name { get; set; }

      [DataType(DataType.Currency)]
      [Column(TypeName = "money")]
      public decimal Budget { get; set; }

      [DataType(DataType.Date)]
      [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
      [Display(Name = "Start Date")]
      public DateTime StartDate { get; set; }

      public int? InstructorID { get; set; }

      public virtual Instructor Administrator { get; set; }
      public virtual ICollection<Course> Courses { get; set; }
   }
}

O atributo de coluna

Anteriormente, você usava o atributo Column para alterar o mapeamento do nome da coluna. No código da Department entidade, o Column atributo está sendo usado para alterar o mapeamento do tipo de dados SQL para que a coluna seja definida usando o tipo money do SQL Server no banco de dados:

[Column(TypeName="money")]
public decimal Budget { get; set; }

O mapeamento de coluna geralmente não é necessário, pois o Entity Framework geralmente escolhe o tipo de dados SQL Server apropriado com base no tipo CLR definido para a propriedade. O tipo decimal CLR é mapeado para um tipo decimal SQL Server. Mas, nesse caso, você sabe que a coluna conterá valores monetários e o tipo de dados money é mais apropriado para isso. Para obter mais informações sobre tipos de dados CLR e como eles correspondem aos tipos de dados do SQL Server, consulte SqlClient para Entity FrameworkTypes.

Propriedades de chave estrangeira e navegação

As propriedades de navegação e de chave estrangeira refletem as seguintes relações:

  • Um departamento pode ou não ter um administrador, e um administrador é sempre um instrutor. Portanto, a InstructorID propriedade é incluída como a chave estrangeira para a Instructor entidade e um ponto de interrogação é adicionado após a designação de int tipo para marcar a propriedade como anulável. A propriedade de navegação é nomeada Administrator , mas contém uma Instructor entidade:

    public int? InstructorID { get; set; }
    public virtual Instructor Administrator { get; set; }
    
  • Um departamento pode ter muitos cursos, portanto, há uma Courses propriedade de navegação:

    public virtual ICollection<Course> Courses { get; set; }
    

    Observação

    Por convenção, o Entity Framework habilita a exclusão em cascata para chaves estrangeiras que não permitem valor nulo e em relações muitos para muitos. Isso pode resultar em regras de exclusão em cascata circular, que causará uma exceção quando você tentar adicionar uma migração. Por exemplo, se você não definiu a Department.InstructorID propriedade como anulável, receberá a seguinte mensagem de exceção: "A relação referencial resultará em uma referência cíclica que não é permitida". Se suas regras de negócios exigissem InstructorID que a propriedade não permitisse valor nulo, você teria que usar a seguinte instrução de API fluente para desabilitar a exclusão em cascata na relação:

modelBuilder.Entity().HasRequired(d => d.Administrator).WithMany().WillCascadeOnDelete(false);

Modificar a entidade Enrollment

Em Modelos\Enrollment.cs, substitua o código adicionado anteriormente pelo seguinte código

using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        [DisplayFormat(NullDisplayText = "No grade")]
        public Grade? Grade { get; set; }

        public virtual Course Course { get; set; }
        public virtual Student Student { get; set; }
    }
}

Propriedades de chave estrangeira e navegação

As propriedades de navegação e de chave estrangeira refletem as seguintes relações:

  • Um registro destina-se a um único curso e, portanto, há uma propriedade de chave estrangeira CourseID e uma propriedade de navegação Course:

    public int CourseID { get; set; }
    public virtual Course Course { get; set; }
    
  • Um registro destina-se a um único aluno e, portanto, há uma propriedade de chave estrangeira StudentID e uma propriedade de navegação Student:

    public int StudentID { get; set; }
    public virtual Student Student { get; set; }
    

Relações muitos para muitos

Há uma relação muitos para muitos entre as entidades Student e Course, e entidade Enrollment funciona como uma tabela de junção muitos para muitos com conteúdo no banco de dados. Isso significa que a tabela contém dados adicionais além de Enrollment chaves estrangeiras para as tabelas unidas (neste caso, uma chave primária e uma Grade propriedade).

A ilustração a seguir mostra a aparência dessas relações em um diagrama de entidades. (Este diagrama foi gerado usando o Entity Framework Power Tools; a criação do diagrama não faz parte do tutorial, está apenas sendo usada aqui como uma ilustração.)

Aluno Course_many a many_relationship

Cada linha de relação tem um 1 em uma extremidade e um asterisco (*) na outra, indicando uma relação um para muitos.

Se a tabela Enrollment não incluir informações de nota, ela apenas precisará conter as duas chaves estrangeiras CourseID e StudentID. Nesse caso, ele corresponderia a uma tabela de junção muitos para muitos sem carga (ou uma tabela de junção pura) no banco de dados, e você não precisaria criar uma classe de modelo para ela. As Instructor entidades e Course têm esse tipo de relacionamento muitos-para-muitos e, como você pode ver, não há classe de entidade entre elas:

Instrutor Course_many a many_relationship

No entanto, uma tabela de junção é necessária no banco de dados, conforme mostrado no diagrama de banco de dados a seguir:

Instrutor Course_many a many_relationship_tables

O Entity Framework cria automaticamente a CourseInstructor tabela e você a lê e atualiza indiretamente lendo e atualizando as Instructor.Courses propriedades de navegação and Course.Instructors .

Diagrama de relacionamento da entidade

A ilustração a seguir mostra o diagrama criado pelo Entity Framework Power Tools para o modelo Escola concluído.

School_data_model_diagram

Além das linhas de relacionamento muitos para muitos (* para *) e as linhas de relacionamento um-para-muitos (1 para *), você pode ver aqui a linha de relacionamento um-para-zero-ou-um (1 a 0..1) entre as Instructor entidades e OfficeAssignment e a linha de relacionamento zero-ou-um-para-muitos (0..1 a *) entre as entidades Instrutor e Departamento.

Adicionar código ao contexto do banco de dados

Em seguida, você adicionará as novas entidades à SchoolContext classe e personalizará parte do mapeamento usando chamadas de API fluentes. A API é "fluente" porque geralmente é usada pela cadeia de caracteres de uma série de chamadas de método em uma única instrução, como no exemplo a seguir:

modelBuilder.Entity<Course>()
     .HasMany(c => c.Instructors).WithMany(i => i.Courses)
     .Map(t => t.MapLeftKey("CourseID")
         .MapRightKey("InstructorID")
         .ToTable("CourseInstructor"));

Neste tutorial, você usará a API fluente apenas para mapeamento de banco de dados que não pode ser feito com atributos. No entanto, você também pode usar a API fluente para especificar a maioria das regras de formatação, validação e mapeamento que pode ser feita por meio de atributos. Alguns atributos como MinimumLength não podem ser aplicados com a API fluente. Como mencionado anteriormente, MinimumLength não altera o esquema, apenas aplica uma regra de validação do lado do cliente e do servidor

Alguns desenvolvedores preferem usar a API fluente exclusivamente para que possam manter suas classes de entidade "limpas". Combine atributos e a API fluente se desejar. Além disso, há algumas personalizações que podem ser feitas apenas com a API fluente, mas em geral, a prática recomendada é escolher uma dessas duas abordagens e usar isso com o máximo de consistência possível.

Para adicionar as novas entidades ao modelo de dados e executar o mapeamento de banco de dados que você não fez usando atributos, substitua o código em DAL\SchoolContext.cs pelo seguinte código:

using ContosoUniversity.Models;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace ContosoUniversity.DAL
{
   public class SchoolContext : DbContext
   {
      public DbSet<Course> Courses { get; set; }
      public DbSet<Department> Departments { get; set; }
      public DbSet<Enrollment> Enrollments { get; set; }
      public DbSet<Instructor> Instructors { get; set; }
      public DbSet<Student> Students { get; set; }
      public DbSet<OfficeAssignment> OfficeAssignments { get; set; }

      protected override void OnModelCreating(DbModelBuilder modelBuilder)
      {
         modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

         modelBuilder.Entity<Course>()
             .HasMany(c => c.Instructors).WithMany(i => i.Courses)
             .Map(t => t.MapLeftKey("CourseID")
                 .MapRightKey("InstructorID")
                 .ToTable("CourseInstructor"));
      }
   }
}

A nova instrução no método OnModelCreating configura a tabela de junção muitos para muitos:

  • Para a relação muitos-para-muitos entre as Instructor entidades e Course , o código especifica os nomes de tabela e coluna para a tabela de junção. O Code First pode configurar a relação muitos-para-muitos para você sem esse código, mas se você não chamá-la, obterá nomes padrão, como InstructorInstructorID para a InstructorID coluna.

    modelBuilder.Entity<Course>()
        .HasMany(c => c.Instructors).WithMany(i => i.Courses)
        .Map(t => t.MapLeftKey("CourseID")
            .MapRightKey("InstructorID")
            .ToTable("CourseInstructor"));
    

O código a seguir fornece um exemplo de como você poderia ter usado a API fluente em vez de atributos para especificar a relação entre as Instructor entidades e OfficeAssignment :

modelBuilder.Entity<Instructor>()
    .HasOptional(p => p.OfficeAssignment).WithRequired(p => p.Instructor);

Para obter informações sobre o que as instruções de "API fluente" estão fazendo nos bastidores, consulte a postagem no blog da API fluente.

Propagar o banco de dados com dados de teste

Substitua o código no arquivo Migrations\Configuration.cs pelo código a seguir para fornecer dados de semente para as novas entidades que você criou.

namespace ContosoUniversity.Migrations
{
    using ContosoUniversity.Models;
    using ContosoUniversity.DAL;
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;
    
    internal sealed class Configuration : DbMigrationsConfiguration<SchoolContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(SchoolContext context)
        {
            var students = new List<Student>
            {
                new Student { FirstMidName = "Carson",   LastName = "Alexander", 
                    EnrollmentDate = DateTime.Parse("2010-09-01") },
                new Student { FirstMidName = "Meredith", LastName = "Alonso",    
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Arturo",   LastName = "Anand",     
                    EnrollmentDate = DateTime.Parse("2013-09-01") },
                new Student { FirstMidName = "Gytis",    LastName = "Barzdukas", 
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Yan",      LastName = "Li",        
                    EnrollmentDate = DateTime.Parse("2012-09-01") },
                new Student { FirstMidName = "Peggy",    LastName = "Justice",   
                    EnrollmentDate = DateTime.Parse("2011-09-01") },
                new Student { FirstMidName = "Laura",    LastName = "Norman",    
                    EnrollmentDate = DateTime.Parse("2013-09-01") },
                new Student { FirstMidName = "Nino",     LastName = "Olivetto",  
                    EnrollmentDate = DateTime.Parse("2005-09-01") }
            };

            students.ForEach(s => context.Students.AddOrUpdate(p => p.LastName, s));
            context.SaveChanges();

            var instructors = new List<Instructor>
            {
                new Instructor { FirstMidName = "Kim",     LastName = "Abercrombie", 
                    HireDate = DateTime.Parse("1995-03-11") },
                new Instructor { FirstMidName = "Fadi",    LastName = "Fakhouri",    
                    HireDate = DateTime.Parse("2002-07-06") },
                new Instructor { FirstMidName = "Roger",   LastName = "Harui",       
                    HireDate = DateTime.Parse("1998-07-01") },
                new Instructor { FirstMidName = "Candace", LastName = "Kapoor",      
                    HireDate = DateTime.Parse("2001-01-15") },
                new Instructor { FirstMidName = "Roger",   LastName = "Zheng",      
                    HireDate = DateTime.Parse("2004-02-12") }
            };
            instructors.ForEach(s => context.Instructors.AddOrUpdate(p => p.LastName, s));
            context.SaveChanges();

            var departments = new List<Department>
            {
                new Department { Name = "English",     Budget = 350000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Abercrombie").ID },
                new Department { Name = "Mathematics", Budget = 100000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Fakhouri").ID },
                new Department { Name = "Engineering", Budget = 350000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Harui").ID },
                new Department { Name = "Economics",   Budget = 100000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Kapoor").ID }
            };
            departments.ForEach(s => context.Departments.AddOrUpdate(p => p.Name, s));
            context.SaveChanges();

            var courses = new List<Course>
            {
                new Course {CourseID = 1050, Title = "Chemistry",      Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 1045, Title = "Calculus",       Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 3141, Title = "Trigonometry",   Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 2021, Title = "Composition",    Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 2042, Title = "Literature",     Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
            };
            courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
            context.SaveChanges();

            var officeAssignments = new List<OfficeAssignment>
            {
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID, 
                    Location = "Smith 17" },
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Harui").ID, 
                    Location = "Gowan 27" },
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID, 
                    Location = "Thompson 304" },
            };
            officeAssignments.ForEach(s => context.OfficeAssignments.AddOrUpdate(p => p.InstructorID, s));
            context.SaveChanges();

            AddOrUpdateInstructor(context, "Chemistry", "Kapoor");
            AddOrUpdateInstructor(context, "Chemistry", "Harui");
            AddOrUpdateInstructor(context, "Microeconomics", "Zheng");
            AddOrUpdateInstructor(context, "Macroeconomics", "Zheng");

            AddOrUpdateInstructor(context, "Calculus", "Fakhouri");
            AddOrUpdateInstructor(context, "Trigonometry", "Harui");
            AddOrUpdateInstructor(context, "Composition", "Abercrombie");
            AddOrUpdateInstructor(context, "Literature", "Abercrombie");

            context.SaveChanges();

            var enrollments = new List<Enrollment>
            {
                new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").ID, 
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID, 
                    Grade = Grade.A 
                },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID, 
                    Grade = Grade.C 
                 },                            
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").ID,
                    CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID, 
                    Grade = Grade.B
                 },
                 new Enrollment { 
                     StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment { 
                     StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alonso").ID,
                    CourseID = courses.Single(c => c.Title == "Composition" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Anand").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Anand").ID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
                    Grade = Grade.B         
                 },
                new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
                    CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
                    Grade = Grade.B         
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Li").ID,
                    CourseID = courses.Single(c => c.Title == "Composition").CourseID,
                    Grade = Grade.B         
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Justice").ID,
                    CourseID = courses.Single(c => c.Title == "Literature").CourseID,
                    Grade = Grade.B         
                 }
            };

            foreach (Enrollment e in enrollments)
            {
                var enrollmentInDataBase = context.Enrollments.Where(
                    s =>
                         s.Student.ID == e.StudentID &&
                         s.Course.CourseID == e.CourseID).SingleOrDefault();
                if (enrollmentInDataBase == null)
                {
                    context.Enrollments.Add(e);
                }
            }
            context.SaveChanges();
        }

        void AddOrUpdateInstructor(SchoolContext context, string courseTitle, string instructorName)
        {
            var crs = context.Courses.SingleOrDefault(c => c.Title == courseTitle);
            var inst = crs.Instructors.SingleOrDefault(i => i.LastName == instructorName);
            if (inst == null)
                crs.Instructors.Add(context.Instructors.Single(i => i.LastName == instructorName));
        }
    }
}

Como você viu no primeiro tutorial, a maior parte desse código simplesmente atualiza ou cria novos objetos de entidade e carrega dados de exemplo em propriedades conforme necessário para teste. No entanto, observe como a Course entidade, que tem uma relação muitos-para-muitos com a Instructor entidade, é tratada:

var courses = new List<Course>
{
    new Course {CourseID = 1050, Title = "Chemistry",      Credits = 3,
      DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
      Instructors = new List<Instructor>() 
    },
    ...
};
courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
context.SaveChanges();

Ao criar um Course objeto, você inicializa a Instructors propriedade de navegação como uma coleção vazia usando o código Instructors = new List<Instructor>(). Isso possibilita adicionar Instructor entidades relacionadas a isso Course usando o Instructors.Add método. Se você não criasse uma lista vazia, não seria possível adicionar essas relações, pois a Instructors propriedade seria nula e não teria um Add método. Você também pode adicionar a inicialização da lista ao construtor.

Adicionar uma migração

No PMC, digite o add-migration comando (não execute o update-database comando ainda):

add-Migration ComplexDataModel

Se você tiver tentado executar o comando update-database neste ponto (não faça isso ainda), receberá o seguinte erro:

A instrução ALTER TABLE entrou em conflito com a restrição FOREIGN KEY "FK_dbo.Course_dbo.Department_DepartmentID". O conflito ocorreu no banco de dados "ContosoUniversity", tabela "dbo.Departamento", coluna 'DepartmentID'.

Às vezes, quando você executa migrações com dados existentes, precisa inserir dados de stub no banco de dados para atender às restrições de chave estrangeira, e é isso que você precisa fazer agora. O código gerado no método ComplexDataModel Up adiciona uma chave estrangeira não anulável DepartmentID à Course tabela. Como já há linhas na Course tabela quando o código é executado, a operação falhará porque o AddColumn SQL Server não sabe qual valor colocar na coluna que não pode ser nula. Portanto, tem que alterar o código para dar à nova coluna um valor padrão e criar um departamento de stub chamado "Temp" para atuar como o departamento padrão. Como resultado, as linhas existentes Course serão todas relacionadas ao departamento "Temp" após a execução do Up método. Você pode relacioná-los com os departamentos corretos no Seed método.

Edite o arquivo _ComplexDataModel.cs carimbo <de data/hora>, comente a linha de código que adiciona a coluna DepartmentID à tabela Course e adicione o seguinte código realçado (a linha comentada também é destacada):

CreateTable(
        "dbo.CourseInstructor",
        c => new
            {
                CourseID = c.Int(nullable: false),
                InstructorID = c.Int(nullable: false),
            })
        .PrimaryKey(t => new { t.CourseID, t.InstructorID })
        .ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: true)
        .ForeignKey("dbo.Instructor", t => t.InstructorID, cascadeDelete: true)
        .Index(t => t.CourseID)
        .Index(t => t.InstructorID);

    // Create  a department for course to point to.
    Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
    //  default value for FK points to department created above.
    AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false, defaultValue: 1)); 
    //AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false));

    AlterColumn("dbo.Course", "Title", c => c.String(maxLength: 50));

Quando o Seed método for executado, ele inserirá linhas na Department tabela e relacionará as linhas existentes Course a essas novas Department linhas. Se você não tiver adicionado nenhum curso na interface do usuário, não precisará mais do departamento "Temporário" ou do valor padrão na Course.DepartmentID coluna. Para permitir a possibilidade de que alguém possa ter adicionado cursos usando o aplicativo, você também deseja atualizar o código do Seed método para garantir que todas as Course linhas (não apenas as inseridas por execuções anteriores do Seed método) tenham valores válidos DepartmentID antes de remover o valor padrão da coluna e excluir o departamento "Temp".

Atualizar o banco de dados

Depois de terminar de editar o arquivo _ComplexDataModel.cs carimbo <de data/hora>, insira o update-database comando no PMC para executar a migração.

update-database

Observação

É possível obter outros erros ao migrar dados e fazer alterações de esquema. Se você receber erros de migração que não consegue resolver, altere o nome do banco de dados na cadeia de conexão ou exclua o banco de dados. A abordagem mais simples é renomear o banco de dados no arquivo Web.config . O exemplo a seguir mostra o nome alterado para_Test:

<add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=CU_Test;Integrated Security=SSPI;" 
      providerName="System.Data.SqlClient" />

Com um novo banco de dados, não há dados para migrar 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 descartar um banco de dados do Visual Studio 2012.

Se isso falhar, outra coisa que você pode tentar é reinicializar o banco de dados digitando o seguinte comando no PMC:

update-database -TargetMigration:0

Abra o banco de dados no Gerenciador de Servidores como você fez anteriormente e expanda o nó Tabelas para ver se todas as tabelas foram criadas. (Se você ainda tem Gerenciador de Servidores aberto a partir do horário anterior, clique no botão Atualizar .)

Captura de tela que mostra a janela do Gerenciador de Servidores. A pasta Tabelas em Contexto da Escola está aberta.

Você não criou uma classe de modelo para a CourseInstructor tabela. Conforme explicado anteriormente, esta é uma tabela de junção para a relação muitos-para-muitos entre as Instructor entidades e Course .

Clique com o botão direito do mouse na CourseInstructor tabela e selecione Mostrar Dados da Tabela para verificar se ela contém dados como resultado das entidades adicionadas à Course.Instructors propriedade de Instructor navegação.

Table_data_in_CourseInstructor_table

Obter o código

Download do projeto concluído

Recursos adicionais

Links para outros recursos do Entity Framework podem ser encontrados no ASP.NET Acesso a Dados – Recursos Recomendados.

Próximas etapas

Neste tutorial, você:

  • Personalizou o modelo de dados
  • Entidade Student atualizada
  • Criou a entidade Instructor
  • Criou a entidade OfficeAssignment
  • Modificou a entidade Course
  • Criou a entidade Departamento
  • Modificou a entidade Enrollment
  • Adicionado código ao contexto do banco de dados
  • Propagou o banco de dados com os dados de teste
  • Adicionou uma migração
  • Atualizou o banco de dados

Avance para o próximo artigo para saber como ler e exibir dados relacionados que o Entity Framework carrega nas propriedades de navegação.