EF Core - Definindo os relacionamentos - II
 Neste artigo vamos continuar mostrando como definir os principais relacionamentos entre as entidades usando o EF Core em um projeto prático.


Continuando o artigo anterior vamos iniciar a criação do controlador e das views para gerenciar informações das Produtoras.

 


Para isso vamos criar na pasta Controllers o do projeto o controlador ProdutorasController. Para simplificar vamos injetar no construtor do controlador o contexto AppDbContext e a interface IWebHostEnvironment localizada no namespace Microsoft.AspNetCore.Hosting que fornece informações sobre hospedagem na web. Este objeto nos ajudará a carregar o logotipo da produtora dentro da pasta wwwroot.

 

Essa abordagem vai dificultar a troca da ferramenta ORM pois estamos acoplando o EF Core no controlador mas simplifica o projeto que por ser um estudo de caso para mostrar como definir os relacionamentos usando o EF Core se justifica.


A seguir vamos criar o método Action Create que faz a criação real dos registros das empresas de produção.

 

Para criar o controlador acione o menu Project -> Add New Item;


A seguir selecione ASP.NET Core e o template MVC Controller -Empty informando o nome ProdutorasController:


 

A seguir inclua o código abaixo :

 

using Microsoft.AspNetCore.Mvc;
using XFilmes.Context;
using XFilmes.Models;

namespace XFilmes.Controllers;
 

public class ProdutorasController : Controller
{
    private readonly AppDbContext context;
    private IWebHostEnvironment hostingEnvironment;

    public ProdutorasController(AppDbContext mc, IWebHostEnvironment environment)
    {
        context = mc;
        hostingEnvironment = environment;
    }

 

    public ActionResult<List<Produtora>> Index()
    {
        var pcList = context.Produtoras.ToList();
        return View(pcList);
    }

   

    public IActionResult Create()
    {
        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Create(Produtora produtora, IFormFile Logo)
    {
        if (Logo is null)
            ModelState.AddModelError(nameof(pc.Logo), "Selecione a imagem do Logo");
 

        if (ModelState.IsValid)
        {
            string path = Logo.FileName;
            using (var stream = new FileStream(Path.Combine(hostingEnvironment.WebRootPath, path),
            FileMode.Create))

            {
                await Logo.CopyToAsync(stream);
            }

            var produtoraNova = new Produtora()
            {
                Nome = produtora.Nome,
                Logo = path,
                ReceitaAnual = produtora.ReceitaAnual,
                DataCriacao = produtora.DataCriacao
            };

            context.Add(produtoraNova);
            await context.SaveChangesAsync();

            return RedirectToAction("Index");
        }
        else
        {
            return View();
        }
    }

    public IActionResult Update(int id)
    {
        var pc = context.Produtoras.Where(a => a.Id == id).FirstOrDefault();
        return View(pc);
    }
 

    [HttpPost]
    public async Task<IActionResult> Update(Produtora pc, IFormFile mLogo)

    {
        if (ModelState.IsValid)
        {
            string path = pc.Logo;
            if (mLogo != null)
            {
                path = mLogo.FileName;
                using (var stream =
                                            new FileStream(Path.Combine(hostingEnvironment.WebRootPath, path),
                                            FileMode.Create))
                {
                    await mLogo.CopyToAsync(stream);
                }
            }

            var produtoraAtualizada = new Produtora()
            {
                ProdutoraId = pc.ProdutoraId,
                Nome = pc.Nome,
                Logo = path,
                ReceitaAnual = pc.ReceitaAnual,
                DataCriacao = pc.DataCriacao
            };

            context.Update(produtoraAtualizada);
            await context.SaveChangesAsync();

            return RedirectToAction("Index");
        }
        else
            return View(pc);
    }

    [HttpPost]
    public async Task<IActionResult> Delete(int id)
    {
        var pc = context.Produtoras.Where(a => a.ProdutoraId == id).FirstOrDefault();

        context.Remove(pc);
       

        await context.SaveChangesAsync();

        return RedirectToAction("Index");
    }

}

 

 

Neste controlador injetamos a instância do contexto e de IWebHostEnvironment no construtor e criamos os seguintes métodos Action :

1.
Index - Exibe a lista de produtoras

2. Create - Permite criar nova uma produtora
3. Update - Permite atualizar uma produtora
4. Delete - Permite deletar uma produtora
 

As respectivas views foram criadas na pasta /Views/Produtoras :

 

Abaixo temos o código da view  Index.cshtml :
 

@model List<Produtora>

<h1 class="bg-info text-white">Produtoras</h1>

<a asp-action="Create" class="btn btn-secondary">Criar Nova Produtora</a>

<table class="table table-sm table-striped">
    <tr>
        <th class="sort">Id</th>
        <th class="sort">Nome</th>
        <th>Logo</th>
        <th class="sort">Receita Anual</th>
        <th class="sort">Data Fundação</th>
        <th></th>
        <th></th>
    </tr>
    @foreach (Produtora produtora in Model)
    {
        <tr>
            <td>@produtora .Id</td>
            <td>@produtora .Nome</td>
            <td><img src="/images/produtoras/@Url.Content(produtora .Logo)"
                                  width="200" height="100" /></td>
            <td>@produtora .ReceitaAnual.ToString("c")</td>
            <td>@produtora .DataCriacao.ToString("d")</td>
            <td>
                <a class="btn btn-sm btn-primary" asp-action="Update"
                                    asp-route-id="@produtora .Id">
                    Atualizar
                </a>
            </td>
            <td>
                <form asp-action="Delete" asp-route-id="@produtora .Id" method="post">
                    <button type="submit" class="btn btn-sm btn-danger">
                        Deletar
                    </button>
                </form>
            </td>
        </tr>
    }

</table>

 


 

A view Index exibe a relação de produtoras e permite realizar as operações CRUD os métodos Action Update e Delete.
 

Para exibir as imagens usamos uma tag HTML img :  <img src="/images/produtoras/@Url.Content(pc.Logo)"

 

Temos assim criada na pasta wwwroot a pasta produtoras contendo as imagens das produtoras.
 

Implementando o gerenciamento de Filmes


Os recursos para gerenciar um filme incluem as operações CRUD como criação, leitura, atualização e exclusão de registros de filmes. Vamos criar 4 métodos Action para realizar cada uma dessas operações.

Observe que haverá um relacionamento Um-para-Muitos entre as entidades Produtora e Filme, isso significa que 1 produtora pode ter muitos filmes associados a ela. E assim teremos que exibir a relação das produtoras no cadastro de um filme.

Veremos como tratar esses relacionamentos implementando o controlador, os métodos Actions e as respectivas views.

 

Vamos criar o controlador FilmesController na pasta Controllers com o código abaixo:

 

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using XFilmes.Context;
using XFilmes.Models;

namespace XFilmes.Controllers;

public class FilmesController : Controller
{
    private readonly AppDbContext context;
    private IWebHostEnvironment hostingEnvironment;

    public FilmesController(AppDbContext mc, IWebHostEnvironment environment)
    {
        context = mc;
        hostingEnvironment = environment;
    }

    public IActionResult Index()
    {
        var filmes = context.Filmes.Include(p=> p.Produtora).ToList();
        return View(filmes);
    }

    public IActionResult Create()
    {
        GetProdutoras();
        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Create(Filme filme, IFormFile Poster)
    {
        GetProdutoras();

        if (Poster == null)
            ModelState.AddModelError("Filme.Poster", "Selecione o poster");

        if (ModelState.IsValid)
        {
            string path = Poster.FileName;
            using (var stream = new FileStream(Path.Combine(hostingEnvironment.WebRootPath, path)
                                      , FileMode.Create))
            {
                await Poster.CopyToAsync(stream);
            }

            var filmeNovo = new Filme()
            {
                Nome = filme.Nome,
                Poster = path,
                Orcamento = filme.Orcamento,
                Faturamento = filme.Faturamento,
                DataLancamento = filme.DataLancamento,
                ProdutoraId = filme.ProdutoraId
            };

            context.Add(filmeNovo);
            await context.SaveChangesAsync();

            return RedirectToAction("Index");
        }
        else
            return View();
    }

    public IActionResult Update(int id)
    {
        var filme = context.Filmes.Where(a => a.FilmeId == id).FirstOrDefault();
        GetProdutoras();

        return View(filme);
    }

    [HttpPost]
    public async Task<IActionResult> Update(Filme filme, IFormFile mPoster)
    {
        GetProdutoras();

        if (ModelState.IsValid)
        {
            string path = filme.Poster;
            if (mPoster != null)
            {
                path = mPoster.FileName;
                using (var stream = new FileStream(Path.Combine(hostingEnvironment.WebRootPath, path),
                                           FileMode.Create))
                {
                    await mPoster.CopyToAsync(stream);
                }
            }

            var filmeAtualizado = new Filme()
            {
                FilmeId = filme.FilmeId,
                Nome = filme.Nome,
                Poster = path,
                Orcamento = filme.Orcamento,
                Faturamento = filme.Faturamento,
                DataLancamento = filme.DataLancamento
            };

            context.Update(filmeAtualizado);
            await context.SaveChangesAsync();

            return RedirectToAction("Index");
        }
        else
            return View(filme);
    }

    [HttpPost]
    public async Task<IActionResult> Delete(int id)
    {
        var filme = context.Filmes.Where(a => a.FilmeId == id).FirstOrDefault();
        context.Remove(filme);
        await context.SaveChangesAsync();
        return RedirectToAction("Index");
    }

    void GetProdutoras()
    {
        List<SelectListItem> produtoras = new List<SelectListItem>();
        produtoras = context.Produtoras.Select(x => new SelectListItem
                     {
                          Text = x.Nome, Value = x.ProdutoraId.ToString()
                     }).ToList();

        ViewBag.Produtoras = produtoras;
    }

}


Neste código destacamos que:

 

Injetamos o contexto e a interface IWebHostEnvironment no construtor do controlador.

 

Para carregar todos os filmes e suas produtoras associadas usamos o método Include:


var filmes = context.Filmes.Include(p=> p.Produtora).ToList();
 

E para exibir a relação das produtoras já cadastradas criamos o método GetProdutoras:

    void GetProdutoras()
    {
        List<SelectListItem> produtoras = new List<SelectListItem>();
        produtoras = context.Produtoras.Select(x => new SelectListItem 
                     { 
                          Text = x.Nome, Value = x.ProdutoraId.ToString() 
                     }).ToList();
        ViewBag.Produtoras = produtoras;
    }

Este método preenche a ViewBag.Produtoras que iremos usar para exibir a relação de produtoras nas Views Create e Update.

 

No método Delete cabe destacar que no arquivo de contexto declaramos o comportamento de exclusão como em cascata :

.OnDelete(DeleteBehavior.Cascade);

Isso significa que se o registro do filme for excluído, os registros relacionados da tabela Distribuidora serão excluídos automaticamente. Confira este artigo para obter mais informações a esse respeito.

 

As respectivas views foram criadas na pasta /Views/Filmes.

 

Abaixo temos o código da view  Index.cshtml :
 

@model List<Filme>

<h1 class="bg-info text-white">Filmes</h1>

<a asp-action="Create" class="btn btn-secondary">Criar Filme</a>

<table class="table table-sm table-striped">
    <tr>
        <th>Id</th>
        <th>Nome</th>
        <th>Produtora</th>
        <th>Poster</th>
        <th>Orçamento</th>
        <th>Faturamento</th>
        <th>Data Lançamento</th>
        <th>Distribuidora</th>
        <th></th>
        <th></th>

    </tr>
    @foreach (Filme filme in Model)
    {
        <tr>
            <td>@filme.FilmeId</td>
            <td>@filme.Nome</td>
            <td>@filme.Produtora.Nome</td>
            <td><img src="@Url.Content(filme.Poster)" width="150" height="200"/></td>
            <td>@filme.Orcamento.ToString("c")</td>
            <td>@filme.Faturamento.ToString("c")</td>
            <td>@filme.DataLancamento.ToString("d")</td>

            <td>
                <a class="btn btn-sm btn-success" asp-controller="FilmeDistribuidora"
                              asp-action="Update" asp-route-id="@filme.FilmeId">
                    Distribuidora
                </a>

            </td>
            <td>
                <a class="btn btn-sm btn-primary" asp-action="Update" asp-route-id="@filme.FilmeId">
                    Atualizar
                </a>
            </td>
            <td>
                <form asp-action="Delete" asp-route-id="@filme.FilmeId" method="post">
                    <button type="submit" class="btn btn-sm btn-danger">
                        Deletar
                    </button>
                </form>
            </td>
        </tr>
  }
</table>



A view Index exibe a relação de filmes e permite realizar as operações CRUD usando os métodos Action Update e Delete.
 

Para exibir as imagens usamos uma tag HTML img :  <img src="/images/filmes/@Url.Content(filme.Poster)"

 

Temos assim criada na pasta wwwroot a pasta filmes contendo as imagens dos filmes.
 

Na próxima parte do artigo vamos continuar implementando a funcionalidade para a atualizar a distribuidora....
 

"Que diremos, pois, a estas coisas? Se Deus é por nós, quem será contra nós? "
Romanos 8:31

 

Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6
Porque um menino nos nasceu, um filho se nos deu, e o principado está sobre os seus ombros, e se chamará o seu nome: Maravilhoso, Conselheiro, Deus Forte, Pai da Eternidade, Príncipe da Paz.

Isaías 9:6

Referências:


José Carlos Macoratti