7
respostas

[Dúvida] Não estou conseguindo persistir a Nota no meu Banco

Aparentemente fazendo o debugging ele a lógica está correta, mas apesar dele voltar um 201 e retornar o dado no método Post, quando eu executo o método Get ele volta nulo, porque a nota não foi persistida.

Post:

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Get:

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

7 respostas

Avaliação:

using ScreenSound.Modelos;

namespace ScreenSound.Shared.Models.Models;

public class AvaliacaoArtista
{
    public virtual Artista? Artista { get; set; }
    public int ArtistaId { get; set; }
    public int PessoaId { get; set; }
    public int Nota { get; set; }

}

Artista:

public string Nome { get; set; }
public string FotoPerfil { get; set; }
public string Bio { get; set; }
public int Id { get; set; }
public virtual ICollection<Musica> Musicas { get; set; } = new List<Musica>();
public virtual ICollection<AvaliacaoArtista> Avaliacoes { get; set; } = new List<AvaliacaoArtista>();
...

public void AdicionarNota(int pessoaId, int nota)
{
    nota = Math.Clamp(nota, 1, 5); // Garante que a nota esteja entre 1 e 5
    Avaliacoes.Add(new AvaliacaoArtista() { ArtistaId = this.Id, PessoaId = pessoaId, Nota = nota });
}

Request:

namespace ScreenSound.API.Requests;

public record AvaliacaoArtistaRequest(int ArtistaId, int Nota);

Response:

using System.ComponentModel.DataAnnotations;

namespace ScreenSound.API.Responses;

public record AvaliacaoArtistaResponse([Required] int ArtistaId, [Required] int Nota);

Program:

builder.Services.AddTransient<DAL<PessoaComAcesso>>();

Endpoint:

// Rota que permite avaliar um artista
groupBuilder.MapPost("avaliacao", (HttpContext context, [FromBody] AvaliacaoArtistaRequest request, [FromServices] DAL<Artista> dalArtista, [FromServices] DAL<PessoaComAcesso> dalPessoaComAcesso) =>
{
    var artista = dalArtista.FindBy(a => a.Id == request.ArtistaId);

    if (artista is null) return Results.NotFound();

    // O ?? é um operador de coalescência nula, que retorna o valor da esquerda se não for nulo, senão o valor da direita
    var email = context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value
        ?? throw new InvalidOperationException("Usuário não autenticado");

    // Se o email não for encontrado, lança uma exceção de InvalidOperationException com a mensagem "Pessoa não encontrada"
    var pessoa = dalPessoaComAcesso.FindBy(p => p.Email == email)
        ?? throw new InvalidOperationException("Pessoa não encontrada");

    // Verifica se a pessoa já avaliou o artista, passando o id do artista e o id da pessoa
    var avaliacao = artista.Avaliacoes.FirstOrDefault(a => a.ArtistaId == artista.Id && a.PessoaId == pessoa.Id);

    // Se a avaliação não existir, adiciona a nota
    if (avaliacao is null)
    {
        // Adiciona a nota
        artista.AdicionarNota(pessoa.Id, request.Nota);
    }
    else
    {
        // Se a avaliação existir, atualiza a nota
        dalArtista.Atualizar(artista);
    }

    return Results.Created();
});

// Rota que busca a nota do artista
groupBuilder.MapGet("{id}/avaliacao", (int id, HttpContext context, [FromServices] DAL<Artista> dalArtista, [FromServices] DAL<PessoaComAcesso> dalPessoa) =>
{
    // Valida se o artista existe
    var artista = dalArtista.FindBy(a => a.Id == id);
    if (artista is null) return Results.NotFound();

    // Verifica se a pessoa está autenticada
    var email = context.User.Claims.FirstOrDefault(c => c.Type.Equals(ClaimTypes.Email))?.Value
        ?? throw new InvalidOperationException("Não foi encontrado o email da pessoa logada");

    // Verifica se a pessoa existe
    var pessoa = dalPessoa.FindBy(p => p.Email!.Equals(email))
        ?? throw new InvalidOperationException("Não foi encontrado o email da pessoa logada");

    // Busca a avaliação do artista feita pela pessoa, passando o id do artista e o id da pessoa
    var avaliacao = artista.Avaliacoes.FirstOrDefault(a => a.ArtistaId == id && a.PessoaId == pessoa.Id);

    // Se a avaliação não existir, retorna 0
    if (avaliacao is null)
    {
        // Retorna a nota 0
        return Results.Ok(new AvaliacaoArtistaResponse(id, 0));
    }
    else
    {
        // Se a avaliação existir, retorna a nota
        return Results.Ok(new AvaliacaoArtistaResponse(id, avaliacao.Nota));
    }

});

Consegui fazer ele persistir o dado no banco, tinha colocado o método atualizar dento do if, mas na verdade ele é fora, pois é ele quem vai persistir meu dado no banco.

Trecho do código do endpoint:

groupBuilder.MapPost...

     // Se a avaliação não existir, adiciona a nota
    if (avaliacao is null)
    {
        // Adiciona a nota
        artista.AdicionarNota(pessoa.Id, request.Nota);
    }
    else
    {
        // Se a avaliação existir, atualiza a nota
        avaliacao.Nota = request.Nota;                
    }

    // Se a avaliação existir, atualiza a nota
    dalArtista.Atualizar(artista);

    return Results.Created();
});

Agora eu estou com um erro que ele não consegue ir lá no meu DB e voltar o dado pra mim. E por conta disso ele está dando erro, porque ele acha que não tem nada e tenta adicionar novamente um dado que já existe.

Verificando o código, tando no Post quando no Get, o trecho que está com problema é onde ele faz a verificação da avaliação, porque está voltando null.

// Verifica se a pessoa já avaliou o artista, passando o id do artista e o id da pessoa
var avaliacao = artista.Avaliacoes.FirstOrDefault(a => a.ArtistaId == artista.Id && a.PessoaId == pessoa.Id);

Essa abordagem seria meio que uma solução para continuar o curso, mas não é bem o foco. Porque o objetivo é popular a coleção de Avaliações em Artistas.

Solução temporária:

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

 var avaliacao = artista.Avaliacoes.FirstOrDefault(a => a.ArtistaId == id && a.PessoaId == pessoa.Id);

 var dalAvaliacao = dalAvaliacaoArtista.FindBy(av => av.ArtistaId == id && av.PessoaId == pessoa.Id);

 avaliacao = dalAvaliacao;

De fato a "solução" que eu encontrei ajuda a gravar no banco de dados. Mas essa gambiarra não me dá muitos benefícios, porque eu preciso que a lista em Artista seja populada e eu não estou conseguindo um retorno satisfatório com essa gambiarra, para que a classificação me retorne a média das notas.

Segue o meu github, para ajudar a visualizar o código.

https://github.com/IgorTudisco/Entity-Framework-Core/tree/main/ScreenSound.API

Oi, Igor! Tudo bem?

O problema pode estar na forma como você está carregando os dados do banco. O EF Core, por padrão, não carrega relacionamentos automaticamente. Você precisa garantir que a lista de avaliações está sendo carregada corretamente quando você busca o artista.

Ajuste a consulta para incluir as avaliações usando Include:

var artista = dalArtista
    .FindBy(a => a.Id == id)
    .Include(a => a.Avaliacoes);

Se você estiver usando um repositório genérico, talvez seja necessário modificar o método FindBy para permitir Includings. Agora, se o problema persistir, verifique se o banco realmente tem os dados persistidos. Rode um SELECT diretamente no banco para conferir.

Espero ter ajudado e bons estudos!

Caso este post tenha lhe ajudado, por favor, marcar como solucionado ✓.

Eu não sei se entendi direito, ma ficaria assim?


using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;

namespace ScreenSound.Data;

public class DAL<T> where T : class
{

    private readonly ScreenSoundContext _context;
    private readonly DbSet<T> _dbSet;

    public DAL(ScreenSoundContext context)
    {
        _context = context;
        _dbSet = _context.Set<T>();
    }

    public IEnumerable<T> QueryIncludes(params Expression<Func<T, object?>>[] includes)
    {
        IQueryable<T> query = _dbSet;

        // Aplica os includes dinâmicos
        foreach (var include in includes)
        {
            query = query.Include(include);
        }

        return query.ToList();
    }

    public IEnumerable<T> Listar()
    {
        return _dbSet.ToList();
    }

    public IEnumerable<T> Listar(params Expression<Func<T, object?>>[] includes)
    {
        return QueryIncludes(includes);
    }

    public void Adicionar(T objeto)
    {
        _dbSet.Add(objeto);
        _context.SaveChanges();
    }

    public void Atualizar(T objeto)
    {
        _dbSet.Update(objeto);
        _context.SaveChanges();
    }

    public void Excluir(T objeto)
    {
        _context.Set<T>().Remove(objeto);
        _context.SaveChanges();
    }

    public T? FindBy(Func<T, bool> condition)
    {
        var obj = _dbSet.FirstOrDefault(condition);
        return obj;
    }

    public IEnumerable<T?> FindBy(Func<T, bool> condicao, params Expression<Func<T, object?>>[] includes)
    {
        var list = QueryIncludes(includes);
        return list.Where(condicao).ToList();
    }
}

Oi, Igor!

Sobre sua última dúvida, o motivo pelo qual a lista Avaliacoes está vindo vazia é porque o método FindBy que você usou não aplica os includes na versão sem os parâmetros de includes.

Ou seja, quando você chama:


var artista = dalArtista.FindBy(a => a.Id == id);

você não está incluindo a lista de avaliações, o que faz com que ela venha vazia mesmo que os dados estejam no banco.

Para resolver isso, use a versão sobrecarregada do FindBy com includes, dessa forma:


var artista = dalArtista
    .FindBy(a => a.Id == id, a => a.Avaliacoes)
    .FirstOrDefault();

Essa chamada vai garantir que a coleção Avaliacoes seja populada junto com o artista.

Importante: veja que seu método FindBy com includes retorna uma lista (IEnumerable<T>), então é necessário usar .FirstOrDefault() após a chamada para obter um único resultado.

Dica extra: se em algum momento você quiser incluir também o Artista dentro de cada avaliação, adicione um include aninhado assim:


a => a.Avaliacoes, a => a.Avaliacoes.Select(av => av.Artista)

Mas para o seu caso atual, somente a => a.Avaliacoes já resolve o problema.

Fico à disposição.