Pular para o conteúdo
.NETAtualização com PUT e upsert
Módulo 04CRUD Completo e Validação

Atualização com PUT e upsert

intermediario 30 min de leitura·Atualizado · .NET 9
Resumo

PUT substitui integralmente um recurso e é idempotente. Aproveitando o change tracking do EF Core, a atualização se torna trivial. Vemos também o padrão upsert (inserir ao atualizar quando o recurso não existe).

1. Objetivos de Aprendizagem#

  • Implementar PUT retornando 204 No Content.
  • Usar trackChanges: true para persistir alterações sem chamar Update explicitamente.
  • Entender o padrão upsert.

2. Pré-requisitos#

  • Criação com POST (Módulo 23).

3. Conceito#

PUT representa a substituição completa do recurso: o cliente envia o estado desejado inteiro. É idempotente — enviar o mesmo corpo N vezes resulta no mesmo estado. O porquê de buscar com trackChanges: true: ao carregar a entidade rastreada, aplicar o mapeamento e chamar SaveChangesAsync, o EF Core detecta as mudanças e gera o UPDATE automaticamente.

4. Mão na Massa#

4.1. Setup#

DTO de atualização e mapeamento correspondente.

4.2. Implementação Passo a Passo#

  1. DTO:
C#
public record ProdutoForUpdateDto(string Nome, decimal Preco);
  1. Serviço (atualização por tracking):
C#
public async Task UpdateAsync(Guid categoriaId, Guid id, ProdutoForUpdateDto dto)
{
    _ = await _repo.Categoria.GetByIdAsync(categoriaId, trackChanges: false)
        ?? throw new CategoriaNotFoundException(categoriaId);

    var produto = await _repo.Produto.GetByIdAsync(categoriaId, id, trackChanges: true)
        ?? throw new ProdutoNotFoundException(id);

    _mapper.Map(dto, produto);   // aplica sobre a entidade rastreada
    await _repo.SaveAsync();      // EF detecta mudanças e gera UPDATE
}
  1. Controller:
C#
[HttpPut("{id:guid}")]
public async Task<IActionResult> Atualizar(Guid categoriaId, Guid id, [FromBody] ProdutoForUpdateDto dto)
{
    await _service.Produto.UpdateAsync(categoriaId, id, dto);
    return NoContent();
}

4.3. Executando#

Shell
curl -i -X PUT http://localhost:5000/api/categorias/{catId}/produtos/{id} \
  -H "Content-Type: application/json" \
  -d '{"nome":"Mouse Pro","preco":149.9}'
# HTTP/1.1 204 No Content

5. Exemplo Completo#

Upsert: se o recurso não existe, em vez de 404, criamos um novo com o id informado. Útil quando o cliente controla o id. Use com cuidado, pois muda a semântica de PUT.

6. Boas Práticas e Armadilhas#

FaçaEvite
Buscar com trackChanges: true para atualizarBuscar sem tracking e depois estranhar que nada foi salvo
Enviar o recurso completo no PUTUsar PUT para atualização parcial (use PATCH)
Retornar 204 em sucessoRetornar o recurso inteiro sem necessidade
Atenção

atualização parcial via PUT (enviar só alguns campos) zera os campos omitidos, pois PUT substitui tudo. Para atualizações parciais, use PATCH (Módulo 26).

7. Segurança e Produção#

  • Controle concorrência com um concurrency token (rowversion) para evitar lost updates quando dois clientes editam o mesmo recurso.

8. Exercícios#

  • Fácil: implemente PUT de categoria.
  • Médio: adicione um rowversion e trate DbUpdateConcurrencyException retornando 409 Conflict.
  • Desafio: implemente upsert e discuta seus riscos.

9. Resumo#

PUT substitui o recurso e é idempotente. Com change tracking, a atualização é automática ao salvar. Concorrência otimista protege contra sobrescritas perdidas.

10. Próximos Passos#

Módulo 26: atualização parcial com PATCH e JSON Patch.

11. Referências#

25-01 — Atualização com PUT e upsert | Curso ASP.NET Core