Neste artigo veremos como definir os principais relacionamentos entre as entidades usando o EF Core em um projeto prático. |
Vamos usar um projeto prático bem simples para mostrar como definir o
relacionamento entre entidades usando o EF Core.
Assim vamos criar um projeto para gerenciar informações sobre filmes,
produtoras, distribuição, orçamento e outros detalhes.
Para isso iremos criar um projeto ASP.NET Core MVC que não vai exigir muito conhecimento do Entity Framework, e neste projetos vamos aprender a definir os relacionamentos um para muitos, um para um e muitos para muitos, a gerar o banco de dados e as tabelasa e também como realizar as operações CRUD.
Assim iremos realizar as seguintes tarefas durante o
desenvolvimento do projeto
1. Criar registros em uma ou várias tabelas de banco de dados ao mesmo tempo.
2. Ler registros únicos, múltiplos e relacionados de
tabelas de banco de dados.
3. Atualizar as entradas de registros em uma ou várias tabelas de banco de dados
ao mesmo tempo.
4. Excluir entradas de registros de uma ou várias tabelas ao mesmo tempo.
Para focar nas funcionalidades do EF Core vamos criar um projeto monolítico como apenas uma camada, a camada de apresentação representada por uma aplicação ASP .NET Core MVC onde no projeto vamos criar pastas para separar responsabilidades na medida do possível. Em uma proxima etapa podemos reestrutura o projeto usando uma arquitetura mais robusta.
Dessa forma nesta primeira abordagem um iniciante poderá
acompanhar e entender o projeto de forma mais clara.
Os recursos usados neste projeto serão os seguintes:
Criando o
projeto no VS 2022
Vamos criar um novo projeto usando o template ASP.NET Core
Web App (Model-View-Controller) com o nome XFilmes
usando as configurações padrão conforme mostrado abaixo:
A estrutura do projeto criado é mostrada a seguir:
Vamos incluir as
referências a dois pacotes do Entity Framework Core:
Microsoft.EntityFramerworkCore.SqlServer
Microsoft.EntityFramerworkCore.Design
Podemos usar o menu Tools-> Nuget Package Manager -> Manage Nuget Packages for Solution e acionar a guia Browse e selecionar o pacote , os projetos e clicar no botão Install:
Criando as entidades do modelo de domínio
Vamos criar na pasta Models do projeto as classes que representam as nossas entidades do domínio. Vamos criar as seguintes classes:
Produtora
Filme
Matriz
FilmeDistribuidora
Distribuidora
Nota: As classes que representam as entidades são definidas no singular e as respectivas tabelas serão definidas com o mesmo nome no plural.
1- Produtora - Gerencia as informações das empresas responsáveis pela produção do filme
using
System.ComponentModel.DataAnnotations; namespace XFilmes.Models; public class Produtora{ public int ProdutoraId { get; set; } public string? Nome { get; set; } public string? Logo { get; set; } [DisplayFormat(DataFormatString = "{0:n2}", ApplyFormatInEditMode = true)][Display(Name = "Receita Anual")][Required(ErrorMessage = "Informe a receita anual")][Range(100000,999999999)] public decimal ReceitaAnual { get; set; } = 0.00M;
[Required(ErrorMessage = "Informe a data de fundação")]public DateTime DataCriacao { get; set; } = DateTime.Now;
//relacionamento um-para-um com Matriz public Matriz? Matriz { get; set; } } |
A classe Produtora define 5 propriedades e a propriedade de navegação de coleção Filmes que será usada para criar um relacionamento um-para-muitos entre as tabelas Produtoras e Filmes no banco de dados após aplicarmos o Migrations.
2- Filme - Representa
as informações de um filme
public class Filme{ public int FilmeId { get; set; } public string? Nome { get; set; } public string? Poster { get; set; } [DisplayFormat(DataFormatString = "{0:n2}", ApplyFormatInEditMode = true)][Display(Name = "Orçamento")]public decimal Orcamento { get; set; } [DisplayFormat(DataFormatString = "{0:n2}", ApplyFormatInEditMode = true)]public decimal Faturamento { get; set; } [Display(Name = "Data de Lançamento")]public DateTime DataLancamento { get; set; } // one-to-Many com Produtora public int ProdutoraId { get; set; }
public
Produtora? Produtora {
get;
set;
} } |
Neste código temos que:
- ProdutoraId será a chave
estrangeira para esta tabela (ou seja, o valor pai será a coluna Id da tabela
Produtora).
- Uma propriedade de navegação de referência –
Produtora
- Uma propriedade de navegação de coleção chamada
FilmesDistribuidoras
A propriedade Produtora é do tipo Produtora e será
usada para criar um relacionamento um para muitos com a classe de entidade
Produtora.
A propriedade FilmesDistribuidoras é do tipo IList e
será usada para criar relacionamentos
muitos-para-muitos com a classe de entidade Distribuidora. Para fazer
isso, ele usa outra entidade chamada FilmeDistribuidora.
3- Matriz - Gerencia os endereços das sedes das produtoras de um filme
public class Matriz{ public int MatrizId { get; set; } public string? Pais { get; set; } public string? Cidade { get; set; } public string? Local { get; set; } public string? Telefone { get; set; } public string? Email { get; set; } public int ProdutoraId { get; set; } public Produtora? Produtora{ get; set; } } |
Nesta entidade temos a propriedade de navegação Produtora que vai criar um relacionamento um-para-um entre esta entidade e a entidade Produtora.
4- Distribuidora - Gerencia as informações sobre a distribuição dos filmes
using System.ComponentModel.DataAnnotations;namespace XFilmes.Models;public class Distribuidora{ public int DistribuidoraId { get; set; }public string? Nome { get; set; } [Required] public string? Local { get; set; }
public string? Telefone { get; set; } // Many-to-Many with Movie public IList<FilmeDistribuidora> FilmesDistribuidoras { get; set; } = new List<FilmeDistribuidora>(); }
|
A propriedade de navegação de coleção FilmesDistribuidora é do tipo IList e vai criar um relacionamento muitos-para-muitos entre as tabelas Distribuidoras e Filmes que serão geradas no banco de dados. Para isso será usada a entidade FilmeDistribuidora.
4- FilmeDistribuidora - É usada como uma tabela de junção para definir o relacionamento muitos-para-muitos
public class FilmeDistribuidora{ public int FilmeId { get; set; } //foreign key public int DistribuidoraId { get; set; } //foreign key public Filme? Filme { get; set; } //Reference navigation public Distribuidora? Distribuidora { get; set; } //Reference navigation } |
Neste código FilmeId é a chave estrangeira associada à entidade Filme, enquanto DistribuidoraId é a chave estrangeira associada à entidade Distribuidora.
As propriedades Filme e Distribuidora são propriedades de navegação de referência para criação do relacionamento muitos-para-muitos.
Da forma como definimos as entidades estamos usando as convenções do EF Core para definir os relacionamentos entre as tabelas que irão ser geradas no banco de dados. Para isso precisamos definir o mapeamento usando o EF Core criando a classe de contexto.
Criando a classe de contexto
Vamos criar no projeto a pasta Context e nesta pasta vamos criar a classe
AppDbContext que vai herdar da classe
DbContext que será usada para definir o
relacionamento entre as entidades e as tabelas que desejamos gerar.
public class AppDbContext :DbContext{ public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<Produtora>? Produtoras { get; set; } public DbSet<Filme>? Filmes { get; set; } public DbSet<Matriz>? Sedes { get; set; } public DbSet<Distribuidora>? Distribuidoras { get; set; } public DbSet<FilmeDistribuidora>? FilmesDistribuidoras { get; set; } } |
O código acima define no construtor AppDbContext
a utilização da classe DbcontextOptions que
carrega as informações de configuração necessárias para configurar o
DbContext, e , isso será feito pela classe base.
Nota:
DbContextOptions também pode ser configurado usando o método OnConfiguring.
Este método obtém o DbContextOptionsBuilder como seu argumento. Em seguida,
é usado para criar o dbContextOptions.
Da forma que esta definida esta classe não reflete os relacionamentos entre
as entidades pois usando as convenções o EF Core não vai conseguir fazer o
trabalho sozinho.
Teremos então que
usar o método OnModelCreating e definir explicitamente as propriedades e os
relacionamentos entre as entidades usando a Fluent API.
using Microsoft.EntityFrameworkCore;using XFilmes.Models;namespace XFilmes.Context;public class AppDbContext :DbContext{ public AppDbContext(DbContextOptions<AppDbContext> options) : base(options){ } public DbSet<Produtora>? Produtoras { get; set; } public DbSet<Filme>? Filmes { get; set; } public DbSet<FilmeDetalhe>? FilmesDetalhes { get; set; } public DbSet<Distribuidora>? Distribuidoras { get; set; } public DbSet<FilmeDistribuidora>? FilmesDistribuidoras { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) {
}); // Filme modelBuilder.Entity<Filme>().HasKey(s => s.FilmeId); modelBuilder.Entity<Filme>() .HasOne(e => e.Produtora) .WithMany(e => e.Filmes) .HasForeignKey(e => e.ProdutoraId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity<Filme>(entity => { "decimal(12,2)"); entity.Property(e => e.Faturamento) .IsRequired() .HasColumnType( "decimal(12,2)");entity.Property(e => e.DataLancamento) .IsRequired() .HasColumnType( "Date");}); // Distribuidora modelBuilder.Entity<Distribuidora>().HasKey(s => s.DistribuidoraId); modelBuilder.Entity<Distribuidora>(entity => {
}); // FilmeDistribuidora modelBuilder.Entity<FilmeDistribuidora>().HasKey(t => new { t.FilmeId, t.DistribuidoraId });modelBuilder.Entity<FilmeDistribuidora>() .HasOne(t => t.Filme) .WithMany(t => t.FilmesDistribuidoras) .HasForeignKey(t => t.FilmeId); modelBuilder.Entity<FilmeDistribuidora>() .HasOne(t => t.Distribuidora) .WithMany(t => t.FilmesDistribuidoras)
.HasForeignKey(t => t.DistribuidoraId); } } |
Vamos entender o código acima:
No método OnModelCreating,
usamos as APIs Fluent do Entity Framework Core para configurar o modelo
do EF para mapeamentos de banco de dados, chave primária, chave estrangeira e
relacionamentos.
Para a entidade do Filme, configuramos a sua chave
primária como:
modelBuilder.Entity<Filme>().HasKey(s => s.FilmeId);
No método OnDelete() definimos a configuração
DeleteBehavior para Cascade.
Isso significa que quando a entidade pai, ou seja, aqui o registro da tabela de
banco de dados 'Produtora' for excluído, todos os registros filho na
tabela Filme também são excluídos.
Outra coisa a notar é a configuração feita para a entidade FilmeDistribuidora onde criamos uma chave primária composta para seus dois campos que são FilmeId e DistribuidoraId como:
// FilmeDistribuidora
modelBuilder.Entity<FilmeDistribuidora>().HasKey(t =>
new { t.FilmeId, t.DistribuidoraId });modelBuilder.Entity<FilmeDistribuidora>()
.HasOne(t => t.Filme)
.WithMany(t => t.FilmesDistribuidoras)
.HasForeignKey(t => t.FilmeId);
modelBuilder.Entity<FilmeDistribuidora>()
.HasOne(t => t.Distribuidora)
.WithMany(t => t.FilmesDistribuidoras)
.HasForeignKey(t => t.DistribuidoraId);
Precisamos concluir esta etapa registro o serviço do contexto na classe Program :
using Microsoft.EntityFrameworkCore;using XFilmes.Context;var builder = WebApplication.CreateBuilder(args);// Add services to the container. builder.Services.AddControllersWithViews();
options.UseSqlServer(connection)); ... } |
E finalmente vamos incluir a string de conexão do SQL Server no arquivo appsettings.json :
{ "ConnectionStrings": { "DefaultConnection": "Data Source=\\SQLEXPRESS;Initial Catalog=FilmesDB;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"}, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning"} }, "AllowedHosts": "*"}
|
Com isso temos todos os elementos configurados no projeto e podemos aplicar o Migrations.
Vamos usar a ferramenta de linha de comando (CLI) do EF Core dotnet ef.
Para instalar esta ferramenta no seu ambiente o comando usado é : dotnet tool install --global dotnet-ef
Com a ferramenta instalada vamos abrir uma janela do Package Manager Console e emigir o comando:
dotnet ef migrations add Inicial
Este comando vai criar a pasta Migrations no projeto e nesta pasta vai criar o arquivo de script contendo os comandos para criar o banco de dados e as tabelas.
Para executar este arquivo e aplicar os comandos vamos emitir o comando: dotnet ef database update
Após o processamento teremos o banco de dados FilmesDB e as tabelas criadas com os relacionamentos definidos como mostra a figura abaixo:
Na próxima parte do artigo vamos continuar....
"Jesus dizia, pois, aos judeus que criam nele: Se vós permanecerdes na minha
palavra, verdadeiramente sereis meus discípulos;"
João 8:31
Referências: