ASP .NET Core - Iniciando com ASP .NET Core MVC e EF Core no - XI

 Neste artigo eu vamos continuar a criação de uma aplicação ASP .NET Core MVC usando o Entity Framework Core no Visual Studio.

Estamos criando uma aplicação Web usando ASP.NET Core MVC com Entity Framework Core no Visual Studio.

No artigo anterior incluimos novas entidades e relacionamentos em nosso modelo e customizamos o modelo de dados especificando as regras de formatação, validação e mapeamento.

Nesta aula vamos continuar a incluir novas entidades em nosso modelo.

Criando a entidade Instrutor

Vamos criar uma nova entidade chamada Instrutor na pasta Models do projeto com o seguinte código :

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace UniversidadeMacoratti.Models
{
    public class Instrutor
    {
        public int ID { get; set; }
        [Required]
        [Display(Name = "Sobrenome")]
        [StringLength(50)]
        public string Sobrenome { get; set; }
        [Required]
        [Column("Nome")]
        [Display(Name = "Nome")]
        [StringLength(50)]
        public string Nome { get; set; }
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Data de Contratação")]
        public DateTime DataContratacao { get; set; }
        [Display(Name = "Nome Completo")]
        public string NomeCompleto
        {
            get { return Sobrenome + ", " + Nome; }
        }
        public ICollection<CursoAtribuido> CursosAtribuidos { get; set; }
        public SalaAtribuida SalaAtribuida { get; set; }
    }
}

As propriedades CursosAtribuidos e SalaAtribuida são propriedades de navegação.

Um instrutor pode ensinar em diversos cursos, desse modo a propriedade CursosAtribuidos é definida como um conjunto de entidades CursoAtribuido

Se uma propriedade de navegação pode conter várias entidades, seu tipo deve ser uma lista na qual as entradas podem ser adicionadas, excluídas e atualizadas. Você pode especificar ICollection<T> ou um tipo como List<T> ou HashSet<T>.  Se você especificar ICollection<T>, o EF Core vair criar uma coleção HashSet<T> por padrão.

Por outro lado, um instrutor pode ter apenas uma SalaAtribuida, assim a propriedade SalaAtribuida é definida como uma única entidade SalaAtribuida (que pode ser nula se nenhuma sala for atribuída).

Nota: Observe que várias propriedades são as mesmas nas entidades Estudante e Instrutor. Em outro artigo, onde iremos tratar da herança, vamos refatorar este código usando a herança para eliminar esta redundância.

Lembrando que você também pode colocar vários atributos em uma única linha, assim você pode escrever os atributos de DataContratacao da seguinte maneira:

[DataType(DataType.Date), Display(Name = "Data de Contratação"), DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

Como ainda não criamos as classes CursoAtribuido e SalaAtribuida iremos obter um alerta informando que os tipos não podem ser encontrados. Tudo bem, vamos criar essas classes a seguir.

Criando a entidade SalaAtribuida

Vamos criar a entidade SalaAtribuida criando o o arquivo SalaAtribuida.cs na pasta Models e definindo a classe SalaAtribuida. Clique com o botão direito sobre a pasta Models e selecione Add -> Class, informando o nome SalaAtribuida.cs e a seguir defina o seguinte código nesta classe:

using System.ComponentModel.DataAnnotations;
namespace UniversidadeMacoratti.Models
{
    public class SalaAtribuida
    {
        [Key]
        public int InstrutorID { get; set; }
        [StringLength(50)]
        [Display(Name = "Localização da sala")]
        public string Localizacao { get; set; }
        public Instrutor Instrutor { get; set; }
    }
}


O atributo Key

Existe uma relação de um para zero ou um entre o instrutor e as entidades SalaAtribuida. Uma atribuição de sala só existe em relação ao instrutor que está atribuído e, portanto, sua chave primária também é a chave estrangeira para a entidade Instrutor.

Mas o Entity Framework não reconhece automaticamente a propriedade InstrutorID como a chave primária desta entidade porque seu nome não segue a convenção de nomeação: ID ou classnameID. Portanto, o atributo Key é usado para identificar essa propriedade como a chave :

[Key]
public int InstrutorID {get; set; }

Você também pode usar o atributo Key se a entidade tiver sua própria chave primária, mas você quer dar um nome para a propriedade diferente de classnameID ou ID.

Por padrão, a EF trata a chave como não gerada pelo banco de dados porque a coluna é para um relacionamento de identificação.


A propriedade de navegação do Instrutor

A entidade Instrutor possui uma propriedade de navegação Nullable SalaAtribuida(porque um instrutor pode não ter uma sala atribuida) e a entidade SalaAtribuida possui uma propriedade de navegação Instrutor que é não anulável (porque uma sala não pode existir sem um instrutor - InstrutorID é não anulável ou non -nullable).

Dessa forma, quando uma entidade Instrutor possui uma entidade SalaAtribuida relacionada, cada entidade terá uma referência á outra entidade na sua propriedade de navegação.

Você pode colocar um atributo [Required] na propriedade de navegação Instrutor para especificar que deve haver um instrutor relacionado, mas você não precisa fazer isso porque a chave estrangeira InstrutorID (que também é a chave para esta tabela) é não anulável.

Modificando a entidade Curso

Vamos modificar a entidade Curso abrindo o arquivo Curso.cs na pasta Models e alterando o seu código conforme abaixo:

Obs: O código em azul foi incluído na entidade. Como ainda não criamos a entidade Departamento o VS 2017 vai indicar um erro mas não se preocupe deixe assim por enquanto que mais adiante vamos criar a entidade Departamento.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace UniversidadeMacoratti.Models
{
    public class Curso
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        [Display(Name = "Número")]
        public int CursoID { get; set; }
        [StringLength(50, MinimumLength = 3)]
        public string Titulo { get; set; }
        [Range(0, 5)]
        public int Creditos { get; set; }
        public int DepartamentoID { get; set; }
        public ICollection<Matricula> Matriculas { get; set; }
        public Departamento Departamento { get; set; }
        public ICollection<CursoAtribuido> CursosAtribuidos { get; set; }
     }
}

A entidade Curso possui uma propriedade chave estrangeira DepartamentoID que aponta para a entidade Departamento relacionada e possui uma propriedade de navegação do Departamento.

O Entity Framework não exige que você adicione uma propriedade de chave estrangeira ao seu modelo de dados quando você possui uma propriedade de navegação para uma entidade relacionada.

O EF cria automaticamente chaves estrangeiras na base de dados sempre que elas são necessárias e cria propriedades de sombra para elas. Mas ter a chave estrangeira no modelo de dados pode tornar as atualizações mais simples e eficientes. Por exemplo, quando você busca uma entidade Curso para editar, a entidade Departamento é nula se você carregar a entidade, então, quando você atualizar a entidade Curso, você deve primeiro buscar a entidade Departamento. Quando a propriedade de chave estrangeira DepartamentoID está incluída no modelo de dados, você não precisa buscar a entidade Departamento antes de atualizar.

O atributo DatabaseGenerated

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

[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Número")]
public int CursoID { get; set; }


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

O atributo DatabaseGenerated também pode ser usado para gerar valores padrão, como no caso de colunas de banco de dados usadas para registrar a data em que uma linha foi criada ou atualizada.

A Propriedade estrangeira e as propriedades de navegação

As propriedades das chaves estrangeiras e as propriedades de navegação na entidade do Curso refletem os seguintes relacionamentos :

Um curso é atribuído a um departamento, então existe uma chave estrangeira DepartamentoID e uma propriedade de navegação Departamento pelos motivos mencionados acima.

public int DepartamentoID { get; set; }
public Departamento Departamento { get; set; }


Um curso pode ter qualquer número de alunos matriculados nele, de modo que a propriedade de navegação Matriculas é uma coleção:

public ICollection<Matricula> Matriculas { get; set; }

Um curso pode ser ministrado por vários instrutores, de modo que a propriedade de navegação CursosAtribuidos é uma coleção (o tipo CursoAtribuido que iremos explicar mais adiante):

public ICollection<CursoAtribuido> CursosAtribuidos { get; set; }

Criando a entidade Departamento

Clique com o botão direito sobre a pasta Models e selecione Add -> Class, informando o nome Departamento e a seguir defina o seguinte código nesta classe:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace UniversidadeMacoratti.Models
{
    public class Curso
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        [Display(Name = "Número")]
        public int CursoID { get; set; }
        [StringLength(50, MinimumLength = 3)]
        public string Titulo { get; set; }
        [Range(0, 5)]
        public int Creditos { get; set; }
        public int DepartamentoID { get; set; }
        public ICollection<Matricula> Matriculas { get; set; }
        public Departamento Departamento { get; set; }
        public ICollection<CursoAtribuido> CursosAtribuidos { get; set; }
     }
}

O atributo Column

Anteriormente, você usou o atributo Column para alterar o mapeamento do nome da coluna. No código para a entidade Departamento, o atributo Column está sendo usado para alterar o mapeamento do tipo de dados SQL, de modo que a coluna será definida usando o tipo money SQL Server.

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


O mapeamento de colunas geralmente não é necessário, porque o Entity Framework escolhe o tipo de dados apropriado do SQL Server com base no tipo CLR que você define para a propriedade. O tipo CLR decimal mapeia para um tipo decimal do SQL Server. Mas, neste caso, você sabe que a coluna estará mantendo os montantes em moeda corrente e o tipo de dados money é mais apropriado para isso.

Propriedade estrangeira e propriedades de navegação

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

Um departamento pode ou não ter um administrador, e um administrador é sempre um instrutor. Portanto, a propriedade InstrutorID está incluída como a chave estrangeira para a entidade Instrutor e um ponto de interrogação(?) é adicionado após a designação do tipo int para marcar a propriedade como anulável. A propriedade de navegação é denominada Administrador, mas possui uma entidade Instrutor:

public int? InstrutorID { get; set; }
public Instrutor Administrador { get; set; }


Um departamento pode ter muitos cursos, então há uma propriedade de navegação Cursos:

public ICollection<Curso> Cursos { get; set; }

Por convenção, o Entity Framework permite a exclusão em cascata para chaves estrangeiras não anuláveis e para relacionamentos muitos para muitos. Isso pode resultar em regras de exclusão em cascata circular, o que causará uma exceção quando você tentar adicionar uma migração. Por exemplo, se você não definiu a propriedade Departamento.InstrutorID como nullable, o EF configuraria uma regra de exclusão em cascata para excluir o instrutor quando você excluir o departamento, o que não é o que você quer que aconteça. Se suas regras de negócios exigirem que a propriedade InstrutorID não seja anulável, você precisaria usar a seguinte instrução de API fluente para desativar a exclusão em cascata no relacionamento:

modelBuilder.Entity<Departamento>()
                     .HasOne(d => d.Administrador)
                     .WithMany()
                     .OnDelete(DeleteBehavior.Restrict)

 

Modificando a entidade Matricula

Vamos modificar a entidade Matricula abrindo o arquivo Matricula.cs na pasta Models e alterando o seu código conforme abaixo onde incluimos a linha de código em azul:

using System.ComponentModel.DataAnnotations;
namespace UniversidadeMacoratti.Models
{
    public enum Nota
    {
        A, B, C, D, F
    }
    public class Matricula
    {
        public int MatriculaID { get; set; }
        public int CursoID { get; set; }
        public int EstudanteID { get; set; }
        [DisplayFormat(NullDisplayText = "Sem Nota")]
        public Nota? Nota { get; set; }
        public Curso Curso { get; set; }
        public Estudante Estudante { get; set; }
    }
}

Propriedade estrangeira e propriedades de navegação

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

Um registro matricula é para um único curso, então existe uma propriedade de chave estrangeira CursoID e uma propriedade de navegação Curso:

public int CursoID { get; set; }
public Curso Curso { get; set; }

Um registro de matricula é para um único aluno, então existe uma propriedade de chave estrangeira EstudanteID e uma propriedade de navegação Estudante:

public int EstudanteID { get; set; }
public Estudante Estudante { get; set; }

Relacionamentos muitos para muitos

Há uma relacionamento muitos para vários entre as entidades Estudante e Curso e a entidade Matricula funciona como uma tabela de junção de muitos para muitos com carga útil no banco de dados. "Carga útil" ou "With Payload" significa que a tabela Matricula contém dados adicionais além de chaves estrangeiras para as tabelas (neste caso, uma chave primária(EstudanteID) e uma propriedade Nota).

A seguinte ilustração mostra como são esses relacionamentos em um diagrama de entidades : 

Cada linha de relacionamento tem um 1 em uma extremidade e um asterisco (*) na outra, indicando um relacionamento  um-para-muitos.

Se a tabela Matricula não incluísse informações de Nota, ela só precisaria conter as duas chaves estrangeiras ID do Curso e ID do Estudante. Nesse caso, seria uma tabela de junção muitos-para-muitos sem carga útil (ou uma tabela de junção pura) no banco de dados.

As entidades Instrutor e Curso possuem esse tipo de relacionamento muitos para muitos, e seu próximo passo é criar uma classe de entidade para funcionar como uma tabela de junção sem carga útil.

Criando a entidade CursoAtribuido

Clique com o botão direito sobre a pasta Models e selecione Add -> Class, informando o nome CursoAtribuido e a seguir defina o seguinte código nesta classe:

    public class CursoAtribuido
    {
        public int InstrutorID { get; set; }
        public int CursoID { get; set; }
        public Instrutor Instructor { get; set; }
        public Curso Curso { get; set; }
    }

Junção de nomes de entidades

Uma tabela de junção é requerida no banco de dados para o relacionamento muitos-para-muitos Instrutor-Cursos que deve ser representada por um conjunto de entidades.

É comum nomear uma entidade de associação EntityName1EntityName2, que neste caso seria CoursoInstrutor. No entanto, recomendamos que você escolha um nome que descreva o relacionamento; algo como CursoAtribuicao seria melhor que CursoInstrutor.

Chave composta

Uma vez que as chaves estrangeiras não são anuláveis e, juntas, identificam de forma exclusiva cada linha da tabela, não há necessidade de uma chave primária separada. As propriedades InstrutorID e CursoID devem funcionar como uma chave primária composta.

A única maneira de identificar chaves primárias compostas para o EF é usando a API fluente (não pode ser feita usando atributos). Você verá como configurar a chave primária composta mais adiante em outro artigo.

A chave composta garante que enquanto você pode ter várias linhas para um curso e várias linhas para um instrutor, você não pode ter várias linhas para o mesmo instrutor e curso. A entidade de junção de Matricula define sua própria chave primária, então as duplicatas deste tipo são possíveis. Para evitar tais duplicatas, você pode adicionar um índice exclusivo nos campos da chave estrangeira ou configurar a Matricula com uma chave composta principal semelhante ao CursoAtribuicao.

Atualizando o contexto do banco de dados

Vamos alterar o código da classe EscolaContexto que representa o nosso contexto do banco de dados incluindo nesta classe o código abaixo em azul:

using Microsoft.EntityFrameworkCore;
using UniversidadeMacoratti.Models;
namespace UniversidadeMacoratti.Data
{
    public class EscolaContexto : DbContext
    {
        public EscolaContexto(DbContextOptions<EscolaContexto> options) : base(options)
        {             
        }
        public DbSet<Curso> Cursos { get; set; }
        public DbSet<Matricula> Matriculas { get; set; }
        public DbSet<Estudante> Estudantes { get; set; }
        public DbSet<Departamento> Departmentos { get; set; }
        public DbSet<Instrutor> Instrutores { get; set; }
        public DbSet<SalaAtribuida> SalasAtribuidas { get; set; }
        public DbSet<CursoAtribuido> CursosAtribuidos { get; set; }
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Curso>().ToTable("Curso");
            modelBuilder.Entity<Matricula>().ToTable("Matricula");
            modelBuilder.Entity<Estudante>().ToTable("Estudante");
            modelBuilder.Entity<Departamento>().ToTable("Departamento");
            modelBuilder.Entity<Instrutor>().ToTable("Instrutor");
            modelBuilder.Entity<SalaAtribuida>().ToTable("SalaAtribuida");
            modelBuilder.Entity<CursoAtribuido>().ToTable("CursoAtribuido");
            modelBuilder.Entity<CursoAtribuido>()
                .HasKey(c => new { c.CursoID, c.InstrutorID });
        }
    }
}

Este código inclui novas entidades e configura a chave primária composta da entidade CursoAtribuido como sendo  CursoID+InstrutorID.

Na próxima aula vamos ver como usar a Fluent API como uma alternativa para definir atributos e definir o código para alimentar o nosso banco de dados e iniciar a aplicação do Migrations.

(disse Jesus) "Não crês tu que eu estou no Pai, e que o Pai está em mim? As palavras que eu vos digo não as digo de mim mesmo, mas o Pai, que está em mim, é quem faz as obras."
João 14:9

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?


Referências:


José Carlos Macoratti