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

Como Faço um update numa relação many to many

Conforme sugestão do professor, segue minhas classes e o objeto JSON antigo e atual para melhor exemplificar o relacionamento. No objeto JSON abaixo o pacote possui id: 6 e possui dois serviços, um com id:12 e outro com id:15, esse pacote é enviado para o cliente, que altera os serviços que contém neste pacote. Passando este pacote a possuir dois serviços, um serviço com id:12 e outro com id:20 . Ou seja, o serviço id:12 continuou no pacote, o serviço id:15 foi removido e o serviço id:20 foi adicionado. Problema: Tenho que fazer todo o trabalho manualmente, verificando qual pacote foi adicionado, removido, mantido. Existe alguma forma de fazer para não ter que analisar item a item(foreach consultando o banco) ou excluindo tudo e adicionando tudo novamente?(Como fiz esta no fim do código, exclui tudo e adicionei novamente. Não acho legal isso mas foi a única maneira que encontrei) Se possível gostaria que alguém escreve o método update dentro desse contexto. Percebi que na versão core não existe o método AddOrUpdate ou a biblioteca graphdiff. Estou correto?

public class Pacote 
 {
        public int Id { get; set; }
        public string Descricao { get; set; }
        public float Valor { get; set; }
        public virtual IList<PacoteServico> ServicosLink { get; set; }
}

 public class Servico
 {
        public int Id { get; set; }
        public string Descricao { get; set; }
        public float Valor { get; set; }
        public virtual IList<PacoteServico> PacotesLink { get; set; }
}

 public class PacoteServico
 {
        public virtual Servico Servico { get; set; }
        public int ServicoId { get; set; }
        public virtual Pacote Pacote { get; set; }
        public int PacoteId { get; set; }
}

Meu método Rest

 public IActionResult Update(long id, [FromBody] Pacote pacote)
{

//Não inseri meu código para não criar mais dúvida. 
//Como deve ser o  código nesse campo para atualizar o relacionamento do pacote com meu serviço? Comparar objetos antigo com o novo objeto (abaixo esta o objeto JSON)
}

Meu objeto Json Antigo retornado para o cliente através do GET

{
    "id": 6,
    "descricao": "Meu pacote 1",
    "valor": 22,
    "servicosLink": [
        {
            "servico": {
                "id": 12,
                "descricao": "Meu servico A",
                "valor": 60,
                "pacotesLink": []
            },
            "servicoId": 12,
            "pacoteId": 6,
            "quantServicos": 5
        },
        {
            "servico": {
                "id": 15,
                "descricao": "Meu servico B",
                "valor": 1,
                "pacotesLink": []
            },
            "servicoId": 15,
            "pacoteId": 6,
            "quantServicos": 5
        }
    ]
}

Meu Novo objeto atualizado pelo cliente

{
    "id": 6,
    "descricao": "Meu pacote 1",
    "valor": 25,
    "servicosLink": [
        {
            "servico": {
                "id": 12,
                "descricao": "Meu servico A",
                "valor": 60,
                "pacotesLink": []
            },
            "servicoId": 12,
            "pacoteId": 6,
            "quantServicos": 5
        },
        {
            "servico": {
                "id": 20,
                "descricao": "Meu servico C",
                "valor": 1,
                "pacotesLink": []
            },
            "servicoId": 20,
            "pacoteId": 6,
            "quantServicos": 3
        }
    ]
}

Esta é a forma que fiz meu update, deixei só o essencial Notem que fiz uma transaction, pois removo os relacionamentos e depois adiciono. Este é um tipo de operação que não pode funcionar somente o remove ou add, caso contrario comprometeria os meus dados se houvesse um erro em um dos métodos.

    [HttpPut("{id}")]
        public IActionResult Update(long id, [FromBody] Pacote newPacote)
        {
            Pacote oldPacote;
                oldPacote = context.Pacotes
                   .Include(p => p.ServicosLink)
                       .ThenInclude(ps => ps.Servico)
                   .Single(p => p.Id == newPacote.Id);

            using (var dbContextTransaction = context.Database.BeginTransaction())
            {
                try
                {
                    foreach (PacoteServico ps in oldPacote.ServicosLink)
                    {
                        context.Set<PacoteServico>().Remove(ps);
                    }
                    Util.ExibeChangeTracks(context.ChangeTracker.Entries());
                    context.SaveChanges();

                    foreach (PacoteServico ps in newPacote.ServicosLink)
                    {
                        oldPacote.ServicosLink.Add(new PacoteServico()
                        {
                            Pacote = oldPacote,
                            Servico = context.Servicos.Single(s => s.Id == ps.Servico.Id),
                            QuantServicos = ps.QuantServicos,
                        });
                    }
                    Util.ExibeChangeTracks(context.ChangeTracker.Entries());
                    context.SaveChanges();

                    dbContextTransaction.Commit();
                    return new NoContentResult();
                }
                catch (Exception e)
                {
                    dbContextTransaction.Rollback();
                    return BadRequest(e.Message);
                }
            }
        }
5 respostas

Jackson, fiquei com algumas dúvidas em sua pergunta. O que você quer dizer com "dificuldade de atualizar"? Está dando erro? Ou não está acontecendo nada? Você pode colocar o código deste exemplo para eu verificar?

Em uma relação many to many trabalhamos em cima do objeto principal. Acho que no seu caso seria o pacote. Daí fazemos as operações de Add, Update e Remove nos objetos filhos, que no seu caso seriam objetos de join.

O que eu faria era verificar como estão os objetos no ChangeTracker para descobrir que estados eles estão sendo registrados. Desta forma eu poderia entender como o objeto principal está se comportando em relação aos seus filhos.

Aguardo sua resposta.

Daniel editei a minha pergunta, coloquei o código e reformulei a questão. Aguardo retorno.

Opa, Jackson, desculpe a demora. Vamos lá.

Primeiro, o método SaveChanges() envolve todos comandos SQL que irá executar no banco em uma transação. Só há necessidade de explicitá-la caso o desenvolvedor deseje maior controle sobre a transação (por exemplo, mudando o nível de isolamento ou mesmo usar uma transação já existente). Saiba mais sobre esse assunto aqui.

Acho que não é preciso recuperar o serviço no SELECT que popula oldPacote.

var oldPacote = context.Pacotes
    .Include(p => p.ServicosLink)
    .Single(p => p.Id == pacote.Id);

Por fim, listo abaixo o código que uso para atualizar um objeto principal e suas relações many to many. A idéia é selecionar os objetos da classe de join que precisarão ser excluídos e incluídos usando a operação LINQ Except().

//cenário desconectado
using (var context = new DatabaseContext())
{
   var oldPacote = context.Pacotes.Include(p => p.ServicosLink).Single(p => p.Id == pacote.Id);

    var pacotesDeServicoAExcluir = oldPacote.ServicosLink.Except(pacote.ServicosLink);
    var pacotesDeServicoAIncluir = pacote.ServicosLink.Except(oldPacote.ServicosLink);

    foreach (var ps in pacotesDeServicoAExcluir)
    {
        context.Entry(ps).State = EntityState.Deleted;
    }

    foreach (var ps in pacotesDeServicoAIncluir)
    {
        oldPacote.ServicosLink.Add(new PacoteServico
        {
            Pacote = pacote,
            Servico = ps.Servico
        });
    }

    context.Pacotes.Update(oldPacote);
    context.SaveChanges();
}

Espero que tenha ajudado. Abraços!

Obrigado pela resposta. Atualizei o meu código com o seu código e tive um problema, o mesmo problema que me fez utilizar dois métodos SaveChanges() dentro de uma transaction. Este problema ele ocorre quando removo um serviço e depois tento adicionar novamente sem ter salvo antes, este erro acontece por estar usando a mesma id num metodo remove e antes de salvar uso novamente no método add.

Me parece que o método except esta retornando o que existe no array A em B, a principio except nao esta funcionando corretamente.

Segue abaixo erro e objeto.

Erro ao adicionar PacoteServico:System.InvalidOperationException: The instance of entity type 'PacoteServico' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.

Json Atual

{
    "id": 9,
    "descricao": "Meu pacote",
    "valor": 22,
    "servicosLink": [
        {
            "servico": {
                "id": 1,
                "descricao": "Meu Servico ",
                "valor": 100,
                "pacotesLink": []
            },
            "servicoId": 1,
            "pacoteId": 9,
            "quantServicos": 0
        },
        {
            "servico": {
                "id": 2,
                "descricao": "Meu servico",
                "valor": 10,
                "pacotesLink": []
            },
            "servicoId": 2,
            "pacoteId": 9,
            "quantServicos": 0
        }
    ]
}

Meu novo JSON

{
    "id": 9,
    "descricao": "Meu pacote",
    "valor": 22,
    "servicosLink": [
        {
            "servico": {
                "id": 1,
                "descricao": "Meu Servico ",
                "valor": 100,
                "pacotesLink": []
            },
            "servicoId": 1,
            "pacoteId": 9,
            "quantServicos": 0
        },
                {
            "servico": {
                "id": 3,
                "descricao": "a",
                "valor": 10,
                "pacotesLink": []
            },
            "servicoId": 3,
            "pacoteId": 9,
            "quantServicos": 0
        }
    ]
}
solução!

Jackson, esqueci de avisar que para o Except() funcionar, é preciso sobrescrever os métodos Equals() e GetHashCode() na classe PacoteServico. Afinal, ele precisa saber o que é igual para calcular a diferença entre as listas.

Seguem minhas implementações destes métodos:

public override bool Equals(object obj)
{
    if (obj == null) return false;
    if (!(obj is PacoteServico)) return false;
    var ps = obj as PacoteServico;
    return (this.PacoteId == ps.PacoteId) && (this.ServicoId == ps.ServicoId);
}

public override int GetHashCode()
{
    return this.PacoteId.GetHashCode() + this.ServicoId.GetHashCode() + 765;
}