ASP.NET Core - CRUD com Master-Detail - I


  Hoje vamos iniciar uma série de artigos onde veremos como realizar as operações CRUD em um contexto mestre-detalhes em uma aplicação ASP.NET Core.

As páginas master-details ou mestre-detalhes são bastante comuns em muitos aplicações web, e existem várias abordagens para a criação de páginas master-detail :  usando o lado do servidor, o lado do cliente e a abordagem híbrida.

Existem também muitos controles e plug-ins de terceiros que podem ser usados para realizar essa tarefa. Seria interessante para iniciantes aprender e entender como as páginas master-detail funcionam e como elas podem ser desenvolvidas na ASP.NET Core.

Com este objetivo em mente este artigo vai mostrar uma abordagem de como as páginas mestre-detalhes podem ser desenvolvidas usando código puro do lado do servidor sem depender de componentes ou bibliotecas de terceiros.

Antes de entrarmos nos detalhes técnicos da criação de páginas master-detail, vou apresentar um esboço da aplicação que iremos criar.

Vamos criar uma aplicação para gerenciar informações sobre equipes ou times e seus membros usando a ASP.NET Core MVC e o EF Core acessando o banco de dados SQL Server, onde teremos duas entidades :

1 - Time - É a entidade principal que contém informações da equipe ou time como TimeId, Nome, Descricao. Um time pode ter muitos membros e Time é a entidade Mestre ou principal.

2 - Membro - É a entidade que contém informações dos membros ou componentes da equipe : MembroId, TimeId, Nome e Email. Membro é a entidade detalhes.

Após o mapeamento ORM e aplicação do Migrations teremos duas tabelas no banco de dados SQL Server: Times e Membros usando um relacionamento um-para-muitos.

Observe a seguinte imagem que exibe a página principal da aplicação web :

Nesta página exibimos as informações do Time e podemos incluir, e gerenciar as informações do time e dos membros do time. Abaixo temos a página para incluir um novo time:

A imagem a seguir exibir a página usada para gerenciar um time:

Nesta página podemos editar e deletar informações de um time.

A seguir temos a página para gerenciar os membros de um time selecionado:

E abaixo temos a página para gerenciar informações de um membro específico:

Com isso poderemos gerenciar as informações de times e seus membros usando a abordagem mestre-detalhe.

Criando a aplicação MVC

Vamos iniciar criando uma aplicação ASP.NET Core MVC chamada TimesMasterDetail usando o VS 2022.

A seguir vamos incluir no projeto os seguintes pacotes Nuget:

  1. Microsoft.EntityFrameworkCore.SqlServer
  2. Microsoft.EntityFrameworkCore.Tools
  3. Microsoft.EntityFrameworkCore.Design

Crie a seguir uma pasta Entities no projeto e nesta pasta crie as classes  Time e Membro que representam o nosso modelo de domínio.

1- Time

using System.ComponentModel.DataAnnotations.Schema;
using
System.ComponentModel.DataAnnotations;
namespace
TimesMasterDetail.Entities;

public class Time
{
   [Key]
   [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  
public int TimeId { get; set; }
   [MaxLength(100)]
  
public string Nome { get; set; } = string.Empty;
   [MaxLength(200)]
  
public string Descricao { get; set; } = string.Empty;

   [ForeignKey(
"TimeId")]
  
public List<Membro>? Membros { get; set; }
}

Definimos nesta entidade a propriedade de navegação Membros para indicar o relacionamento um-para-muitos. Usamos o atributo [ForeignKey] para configurar a chave estrangeira no relacionamento das entidades Time e Membro.

2- Membro

using System.ComponentModel.DataAnnotations.Schema;
using
System.ComponentModel.DataAnnotations;
namespace
TimesMasterDetail.Entities;

public
class Membro
{
  [Key]
  [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
 
public int MembroId { get; set; }
 
public int TimeId { get; set; }
  [MaxLength(100)]
 
public string Nome { get; set; } = string.Empty;
  [MaxLength(200)]
 
public string Email { get; set; } = string.Empty;
}

Nestas entidades estamos usando os atributos Data Annotations para indicar ao EF Core o tamanho das colunas a serem geradas nas tabelas. Os atributos [Key] e [DatabaseGenerated(DatabaseGeneratedOption.Identity)] são usados para definir a chave primária e indicar que o campo na tabela será do tipo Identity. (Você pode omitir esta informação se seguir as convenções padrão do EF Core. Aqui elas estão sendo redundantes)

Ainda na pasta Entities vamos criar duas enum :

1- EntradaDados - Usada para indicar qual entidade estamos editando

public enum EntradaDados
{
   Times,
   Membros
}

2- ModoExibicao - Usada para indicar o modo de exibição da interface do usuário na entrada de dados.

public enum ModoExibicao
{
   Read,
   Insert,
   Update
}

Vamos criar a pasta Context no projeto e nesta pasta criar a classe AppDbContext que representa o arquivo de contexto que herda de DbContext e onde definimos o mapeamento ORM entre as entidades e as tabelas no SQL Server:

1- AppDbContext

using Microsoft.EntityFrameworkCore;
using
TimesMasterDetail.Entities;
namespace
TimesMasterDetail.Context;

public class AppDbContext : DbContext
{
 
public DbSet<Time> Times { get; set; }
 
public DbSet<Membro> Membros { get; set; }

 
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
  { }

 
protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    modelBuilder.Entity<Time>().HasData(
   
new Time {TimeId = 1,Nome ="Time 1", Descricao = "Descrição de Time 1"},
   
new Time {TimeId = 2,Nome = "Time 2",Descricao = "Descrição de Time 2"},
   
new Time {TimeId = 3,Nome = "Time 3",Descricao = "Descrição de Time 3"});

    modelBuilder.Entity<Membro>().HasData(
   
new Membro { MembroId = 1, TimeId = 1, Nome = "Membro 1", Email = "membro1@email.com"},
   
new Membro { MembroId = 2, TimeId = 2, Nome ="Membro 2", Email="membro2@email.com" },
   
new Membro { MembroId = 3, TimeId = 3, Nome = "Membro 3", Email = "membro3@email.com" });
  }
}

Neste código mapeamos as entidades Time e Membro para as tabelas Times e Membros e usando o método HasData estamos incluindo dados iniciais nas tabelas Times e Membros.

Vamos definir no arquivo appsettings.json da aplicação a string de conexão :

{
"ConnectionStrings": {
 
"DefaultConnection": "Data Source=.\\sqlexpress;Initial Catalog=TimesDB;Integrated Security=True;TrustServerCertificate=True;"
},
"Logging": {
  
"LogLevel": {
  
  "Default": "Information",
    
"Microsoft.AspNetCore": "Warning"
    }
   },
"AllowedHosts": "*"
}

Como estamos usando o .NET 7.0 precisamos incluir a propriedade TrustServerCertificate=True para acessar o SQL Server no ambiente de desenvolvimento.

Agora vamos registrar o serviço do context no contêiner DI na classe Program:

...
builder.Services.AddDbContext<AppDbContext>(o =>
  o.UseSqlServer(builder.Configuration.GetConnectionString(
"DefaultConnection")));
...

Podemos agora aplicar o Migrations para gerar o banco de dados e as tabelas conforme a configuração definida. Abra uma janela no Package Manager Console e digite os comandos:

1- Add-Migration Inicial : para criar o arquivo de script contendo os comandos da migração

2- update-database : para aplicar os comandos e criar o banco e as tabelas

Ao final do processo abra o SQL Server Management Studio e verifique as tabelas criadas:

1-Times

2- Membros

Com isso temos as tabelas criadas no SQL Server e na próxima parte do artigo continuamos a implementação.

"Então disse Jesus aos doze: Quereis vós também retirar-vos?
Respondeu-lhe, pois, Simão Pedro: Senhor, para quem iremos nós? Tu tens as palavras da vida eterna."
João 6:67-68

Referências:


José Carlos Macoratti