Recursos filhos e coleções
ResumoCriamos um pai junto com seus filhos numa única requisição e implementamos a criação de uma coleção de recursos, retornando os ids gerados de forma padronizada.
1. Objetivos de Aprendizagem#
- Criar um recurso pai com filhos aninhados numa só chamada.
- Implementar criação de coleção (
POSTde vários itens). - Retornar uma coleção criada com
Locationapropriado.
2. Pré-requisitos#
- Criação básica com
POST(tópico 23.1).
3. Conceito#
Às vezes o cliente quer criar um agregado inteiro (categoria + seus produtos) atomicamente, ou várias entidades de uma vez. O porquê: reduzir round-trips e garantir atomicidade (tudo ou nada, via uma única SaveChangesAsync).
Para coleções, um desafio é o Location: representamos a coleção criada com uma rota que aceita uma lista de ids (ex.: ids=1,2,3), usando um model binder customizado (tópico 23.3).
4. Mão na Massa#
4.1. Setup#
Reuse repositórios e AutoMapper.
4.2. Implementação Passo a Passo#
- Criar pai com filhos:
public async Task<CategoriaDto> CreateComProdutosAsync(CategoriaForCreationDto dto)
{
var categoria = _mapper.Map<Categoria>(dto); // dto inclui a lista de produtos
_repo.Categoria.Create(categoria);
await _repo.SaveAsync(); // salva pai e filhos numa transação
return _mapper.Map<CategoriaDto>(categoria);
}
- Criar coleção de produtos:
public async Task<(IEnumerable<ProdutoDto> produtos, string ids)> CreateColecaoAsync(
IEnumerable<ProdutoForCreationDto> dtos)
{
var entidades = _mapper.Map<IEnumerable<Produto>>(dtos).ToList();
foreach (var e in entidades) _repo.Produto.Create(e);
await _repo.SaveAsync();
var result = _mapper.Map<IEnumerable<ProdutoDto>>(entidades);
var ids = string.Join(',', entidades.Select(e => e.Id));
return (result, ids);
}
- Controller da coleção:
[HttpPost("colecao")]
public async Task<IActionResult> CriarColecao([FromBody] IEnumerable<ProdutoForCreationDto> dtos)
{
var (produtos, ids) = await _service.Produto.CreateColecaoAsync(dtos);
return CreatedAtRoute("ProdutoCollection", new { ids }, produtos);
}
4.3. Executando#
curl -i -X POST http://localhost:5000/api/produtos/colecao \
-H "Content-Type: application/json" \
-d '[{"nome":"Mouse","preco":99.9},{"nome":"Teclado","preco":199.9}]'
5. Exemplo Completo#
O endpoint GET /api/produtos/colecao/{ids} (tópico 23.3) usa um binder customizado para transformar 1,2,3 em uma lista de GUIDs, fechando o ciclo com o Location retornado aqui.
6. Boas Práticas e Armadilhas#
| Faça | Evite |
|---|---|
| Salvar o agregado numa única transação | Salvar filhos separadamente e arriscar estado parcial |
Rejeitar payload nulo de coleção com 400 | Iterar sobre uma coleção nula |
| Retornar os ids criados de forma consultável | Não indicar como buscar os recursos criados |
Atençãocriação em lote sem limite pode ser abusada. Imponha um tamanho máximo de coleção e valide cada item (Módulo 27).
7. Segurança e Produção#
- Operações em lote devem ser transacionais e limitadas em tamanho para evitar exaustão de recursos.
8. Exercícios#
- Fácil: valide que a coleção não é nula nem vazia.
- Médio: limite a criação a no máximo 100 itens por requisição.
- Desafio: torne a criação do agregado resiliente, retornando quais itens falharam na validação.
9. Resumo#
Criamos agregados pai+filhos atomicamente e coleções de recursos, retornando ids consultáveis. A atomicidade vem de uma única SaveChangesAsync.
10. Próximos Passos#
A seguir, o model binding que permite receber ids=1,2,3 como coleção tipada.
11. Referências#
- Microsoft Learn — Model binding no ASP.NET Core
- Microsoft Learn — Salvar dados relacionados no EF Core
- MDN — 201 Created