Solucionado (ver solução)
Solucionado
(ver solução)
2
respostas

[Dúvida] Lazy properties não funcionando no POST (Create Cinema)

Olá,

Ao realizar o Create Cinema, a propriedade Endereço pode ser carregada?

Entendo que a classe ReadCinemaDto (No meu caso é ExibeCinemaDto) é quem possui a capacidade de ter a propriedade endereço carregada mas mesmo forçando o Parse no Create do Cinema, a propriedade fica null, mas no retornaPor Id carrega normalmente.

Dúvidas:

  1. Qual a visão de vocês sobre o cenário?
  2. Qual o motivo da L.Property ser utilizada no RetornaPorId mas não No Insere Cinema se a instrução é a mesma (_context.Cinemas.FirstOrDefault(p => p.Id == cinema.Id) ?
  3. Por que os tipos são diferentes (Castle.Proxies.Proxy x AluraFilmes.Models.Cinema)?

RecupePorId x Insere Cinema

Abaixo mostro que a propriedade Endereço está null quando insiro o cinema e possui valores se busco o cinema posteriormente.

RecuperaCinemaPorId traz a propriedade endereço carregada

Insere Cinema retorna a propriedade como Null

Mesma lógica do retornaPorId, estou buscando do contexto mas não carrega

Acima eu realizei o saveChanges do contexto. Minhas classes estão assim:

        [HttpPost]
        public IActionResult InsereCinema([FromBody] CreateCinemaDto cinemaDto)
        {
            var cinema = _mapper.Map<Cinema>(cinemaDto);

            _context.Cinemas.Add(cinema);
            _context.SaveChanges();

            var cinemaFind = _context.Cinemas.FirstOrDefault(p => p.Id == cinema.Id);
            var cinemaRetorno = _mapper.Map<ExibeCinemaDto>(cinemaFind);

            return CreatedAtAction(nameof(RecuperaCinemaPorId), new { Id = cinema.Id }, cinemaRetorno);
        }
        [HttpGet("{id}")]
        public IActionResult RecuperaCinemaPorId(int id)
        {
            var cinema = _context.Cinemas.FirstOrDefault(p => p.Id == id);
            if (cinema == null) return NotFound();

            var cinemaDto = _mapper.Map<ExibeCinemaDto>(cinema);

            return Ok(cinemaDto);
        }
public class ExibeCinemaDto
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public ExibeEnderecoDto Endereco { get; set; }
}
        public CinemaProfile()
        {
            //De Cinema para ExibeCinemaDto porque controlamos o que será exibido
            CreateMap<CreateCinemaDto, Cinema>();
            CreateMap<UpdateCinemaDto, Cinema>();
            CreateMap<Cinema, ExibeCinemaDto>()
                     .ForMember(cinemaDto => cinemaDto.Endereco, 
                                opt => opt.MapFrom(cinema => cinema.Endereco));

        }
public class Cinema
{
    [Key]
    [Required]
    public int Id { get; set; }

    [Required(ErrorMessage = "O nome do cinema é obrigatório")]
    [StringLength(50, ErrorMessage = "Não pode ter tamanho maior que 50", MinimumLength = 5)]
    public string Nome { get; set; }
    public int EnderecoId { get; set; }
    public virtual Endereco Endereco{ get; set; }


}
2 respostas
solução!

Olá, Sílvio!

Entendo que você está com dificuldades para fazer o carregamento da propriedade Endereço ao criar um novo cinema, certo? Vamos lá!

Primeiramente, é importante entender que o Lazy Loading é uma técnica que carrega os dados somente quando eles são necessários. No caso do Entity Framework, quando você acessa uma propriedade de navegação, ele automaticamente carrega os dados para você. Isso acontece porque o Entity Framework cria proxies das suas classes, que são versões delas que contêm este comportamento adicional.

Agora, vamos para suas dúvidas:

  1. Sobre o cenário: o que está acontecendo é que, quando você cria um novo cinema, o Entity Framework não tem como saber que você vai querer acessar a propriedade Endereço logo em seguida, então ele não carrega essa propriedade. Quando você busca o cinema pelo Id, aí sim ele sabe que vai precisar do Endereço e carrega a propriedade para você.

  2. Sobre o uso da L.Property no RetornaPorId e não no InsereCinema: a L.Property é utilizada para acessar uma propriedade de navegação. No caso do RetornaPorId, como mencionado acima, o Entity Framework sabe que vai precisar do Endereço e carrega essa propriedade. No caso do InsereCinema, como o Entity Framework não sabe que vai precisar do Endereço, ele não carrega essa propriedade.

  3. Sobre os tipos diferentes (Castle.Proxies.Proxy x AluraFilmes.Models.Cinema): isso acontece porque, como mencionado acima, o Entity Framework cria proxies das suas classes para adicionar o comportamento do Lazy Loading. Então, quando você está trabalhando com uma instância que foi recuperada do banco de dados, você está na verdade trabalhando com um proxy. Quando você cria uma nova instância, você está trabalhando com a classe real.

Agora, vamos para a solução do seu problema. Acredito que o que está acontecendo é que você está tentando acessar a propriedade Endereço antes de ela ser carregada. Uma possível solução seria forçar o carregamento dessa propriedade logo após salvar as alterações, usando o método Load:

_context.Cinemas.Add(cinema);
_context.SaveChanges();

_context.Entry(cinema).Reference(c => c.Endereco).Load();

var cinemaFind = _context.Cinemas.FirstOrDefault(p => p.Id == cinema.Id);
var cinemaRetorno = _mapper.Map<ExibeCinemaDto>(cinemaFind);

return CreatedAtAction(nameof(RecuperaCinemaPorId), new { Id = cinema.Id }, cinemaRetorno);

Espero ter ajudado e bons estudos!

Muito obrigado Matheus,

Consegui ter o endereço carregado após a utilizalção de: _context.Entry(cinema).Reference(c => c.Endereco).Load();

Muito obrigado por cada esclarecimento.