ASP.NET Core - Repository e Unit Of Work - II


Hoje vou apresentar novamente o padrão Repository e Unit Of Work usando a abordagem feita no Domain Driven Design.

Continuando a primeira parte do artigo vamos criar os controladores LivrosController e CatalogosController na pasta Controllers do projeto Livraria.API.

Controlador : LivrosController

Vamos criar o controlador LivrosController na pasta Controllers:

'1- LivrosController

using Livraria.Domain.Entities;
using Livraria.Domain.Interfaces;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Livraria.API.Controllers
{
    [Route("api/v1/[controller]")]
    [ApiController]

    public class LivrosController : ControllerBase
    {
        private readonly IUnitOfWork _unitOfWork;
        public LivrosController(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
        }

        [HttpGet]
        public async Task<IEnumerable<Livro>> Get()
        {
            return await _unitOfWork.LivrosRepo.GetAll();
         }

        [HttpGet("genero")]
        public async Task<IEnumerable<Livro>> GetByGenero([FromQuery] string genero)
        {
            return await _unitOfWork.LivrosRepo.GetLivrosPorGenero(genero);
        }

        [HttpGet("{id}")]
        public async Task<Livro> Get(int id)
        {
            return await _unitOfWork.LivrosRepo.Get(id);
        }

        [HttpPost]
        public async Task<IActionResult> Post([FromBody] Livro livro)
        {
            if (!ModelState.IsValid)
                return BadRequest();

            await _unitOfWork.LivrosRepo.Add(livro);
            _unitOfWork.Commit();

            return new CreatedAtRouteResult("Get", new { id = livro.Id }, livro);
        }

        // PUT api/<Books>/5
        [HttpPut("{id}")]
        public async Task<IActionResult> Put(int id, [FromBody] Livro livro)
        {
            var result = await _unitOfWork.LivrosRepo.Get(id);

            if (result == null)
                BadRequest();

           if (result.Id != livro.Id)
                return BadRequest();

            _unitOfWork.LivrosRepo.Update(livro);
            _unitOfWork.Commit();

           return Ok();
        }

        [HttpDelete("{id}")]
        public async Task<IActionResult> Delete(int id)
        {
            var livro = await _unitOfWork.LivrosRepo.Get(id);

            if (livro == null)
                return BadRequest();

             _unitOfWork.LivrosRepo.Delete(livro);
            _unitOfWork.Commit();

            return Ok();
        }
    }
}

A seguir vamos criar o controlador CatalogosController na mesma pasta:

2- CatalogosController

using Livraria.Domain.Entities;
using Livraria.Domain.Interfaces;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Livraria.API.Controllers
{
    [Route("api/v1/[controller]")]
    [ApiController]

    public class CatalogosController : ControllerBase
    {
        private readonly IUnitOfWork _unitOfWork;
        public CatalogosController(IUnitOfWork unitOfWork)
        {
            _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
        }

        [HttpGet]
        public async Task<IEnumerable<Catalogo>> GetCatalogos()
        {
            var resultado = await _unitOfWork.CatalogosRepo.GetAll();
            return resultado;
        }

        [HttpGet("{id}")]
        public async Task<ActionResult<Catalogo>> GetCatalogo(int id)
        {
            var catalogo = await _unitOfWork.CatalogosRepo.Get(id);

            if (catalogo == null)
            {
                return NotFound();
            }

            return catalogo;
        }

        [HttpPut("{id}")]
        public async Task<IActionResult> PutCatalogo(int id, Catalogo catalogo)
        {
            if (id != catalogo.CatalogoId)
            {
                return BadRequest();
            }

            var result = await _unitOfWork.LivrosRepo.Get(id);

            if (result == null)
                BadRequest();

            if (result.Id != catalogo.CatalogoId)
                return BadRequest();

            _unitOfWork.CatalogosRepo.Update(catalogo);
            _unitOfWork.Commit();

            return Ok();
        }

        [HttpPost]
        public async Task<ActionResult<Catalogo>> PostCatalogo(Catalogo catalogo)
        {
            await _unitOfWork.CatalogosRepo.Add(catalogo);
            _unitOfWork.Commit();

            return CreatedAtAction("GetCatalogo", new { id = catalogo.CatalogoId }, catalogo);
        }

        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteCatalogo(int id)
        {
            var catalogo = await _unitOfWork.CatalogosRepo.Get(id);
            if (catalogo == null)
            {
                return NotFound();
            }

            _unitOfWork.CatalogosRepo.Delete(catalogo);
            _unitOfWork.Commit();

            return OK();
        }
    }
}

Em ambos os controladores injetamos a instância da Unit Of Work e trabalhamos com o respectivo repositório acessando a implementação genérica.

Na figura abaixo temos uma representação simplificada da atuação do padrão Unit Of Work:

Vemos que o Unit Of Work atua como uma camada entre o controlador e o repositório genérico, e vai funcionar como um armazenamento centralizado para receber a instância do DbContext. Com isso garantimos que para unidade de transação que se estende por vários repositórios seja concluída para todas as entidades ou que falhe totalmente, já que todas elas vão compartilhar a mesma instância do contexto representando pelo DbContext.

Assim quando incluirmos dados para as entidades Catalogo e Livro, em uma única transação, ambas vão usar a mesma instância do DbContext. Sem isso cada repositório iria gerar e manter sua própria instância de DbContext, e, isso pode levar a problemas no futuro uma vez que cada DbContext terá sua própria lista na memória de alterações dos registros, das entidades, que estão sendo adicionadas/ atualizadas/ modificadas, em uma única transação. Nesse caso, se o SaveChanges de um dos repositórios falhar e o outro for bem-sucedido, isso resultará em inconsistência do banco de dados. Para evitar isso usamos o padrão Unit of Work.

Executando o projeto teremos o seguinte resultado:

Pegue o projeto aqui:  Livraria.zip

"Guia-me na tua verdade, e ensina-me, pois tu és o Deus da minha salvação; por ti estou esperando todo o dia."
Salmos 25:5

Referências:


José Carlos Macoratti