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>() |
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:
Super DVD Vídeo Aulas - Vídeo Aula sobre VB .NET, ASP .NET e C#
ASP .NET Core - Criando uma aplicação ASP .NET ... - Macoratti.net
ASP .NET Core - Iniciando com ASP .NET Core MVC e ... - Macoratti.net
ASP .NET Core - Criando uma aplicação ASP .NET ... - Macoratti.net