Update Many To Many Entity Framework

1

I'm having trouble updating with many to many relationship.

I want the update method to update all fields of the itemEntity as well as the providers.

For example, I just put the description field and vendors according to the screen image.

In my code below if I remove the line that gives the error (shown below) it writes only the providers.

I tried to set the itemEntity as modified but I get the error.

Can anyone please help me?

MyScreen

Error

Attachinganentityoftype'Domain.Entities.ItemEntity'failedbecauseanotherentityofthesametypealreadyhasthesameprimarykeyvalue.Thiscanhappenwhenusingthe'Attach'methodorsettingthestateofanentityto'Unchanged'or'Modified'ifanyentitiesinthegraphhaveconflictingkeyvalues.Thismaybebecausesomeentitiesarenewandhavenotreceiveddatabase-generatedkeyvalues.Inthiscaseusethe'Add'methodorthe'Added'entitystatetotrackthegraphandthensetthestateofnon-newentitiesto'Unchanged'or'Modified'asappropriate.

Table

Relationship

Code

    public override ItemEntity Atualizar(ItemEntity entity)
    {
        var novos = new List<FornecedorEntity>(entity.Fornecedores);

        entity.Fornecedores = _dbContext.TbItem
           .Where(p => p.Id == entity.Id).FirstOrDefault().Fornecedores;

        _dbContext.Entry(entity).State = EntityState.Modified;
        //Sem a linha acima ele funciona mais não atualiza as demais propriedades do ItemEntity, 
        //atualiza somente os fornecedores.

        var deletetadoFornecedores = entity.Fornecedores
            .Except(novos).ToList();

        deletetadoFornecedores.ForEach(c => entity.Fornecedores.Remove(c));

        var AdicionadoFornecedores = novos
            .Except(entity.Fornecedores).ToList();

        foreach (FornecedorEntity c in AdicionadoFornecedores)
        {
            if (_dbContext.Entry(c).State == EntityState.Detached)
            {
                _dbContext.TbFornecedor.Attach(c);
            }

            entity.Fornecedores.Add(c);
        }

        _dbContext.SaveChanges();      

        return entity;
    }

Edited

This code does exactly what I need, but I have to be mapping the properties.

    public override ItemEntity Atualizar(ItemEntity entity)
    {
        var itemExistente = _dbContext.TbItem
                .Find(entity.Id);

        itemExistente.Descricao = entity.Descricao;
        itemExistente.Descricao1 = entity.Descricao1;
        itemExistente.Descricao2 = entity.Descricao2;
        itemExistente.Descricao3 = entity.Descricao3;
        itemExistente.Descricao4 = entity.Descricao4;
        //Esse mapeamento acima não tem como fazer de forma automatica?
        //Toda vez que eu criar uma nova propriedade vou ter que lembrar de vir aqui e alterar
        //isso gera maior dificuldade na manutenão e aumenta a chance de esquecer e gerar um bug.


        var fornecedoresDeletados = itemExistente.Fornecedores
            .Except(entity.Fornecedores).ToList();

        var fornecedoresAdicionados = entity.Fornecedores
            .Except(itemExistente.Fornecedores).ToList();

        fornecedoresDeletados.ForEach(c => itemExistente.Fornecedores.Remove(c));

        foreach (FornecedorEntity c in fornecedoresAdicionados)
        {
            if (_dbContext.Entry(c).State == EntityState.Detached)
                _dbContext.TbFornecedor.Attach(c);

            itemExistente.Fornecedores.Add(c);
        }

        _dbContext.SaveChanges();

        return entity;
    }
    
asked by anonymous 26.05.2017 / 16:19

1 answer

2

You are putting Item twice in context, and your Fornecedor is also:

    entity.Fornecedores = _dbContext.TbItem
       .Where(p => p.Id == entity.Id).FirstOrDefault().Fornecedores;

What I would do in its place are two ViewModels :

public class ItemViewModel
{
    public int ItemId { get; set; }
    public String Nome { get; set; }

    public ICollection<FornecedorViewModel> Fornecedores { get; set; }
}

public class FornecedorViewModel
{
    public int FornecedorId { get; set; }
    public String Nome { get; set; }
    public bool Selecionado { get; set; }
}

Here I would:

public override ItemEntity Atualizar(ItemViewModel entity)
{
    var item = _dbContext.TbItem
                         .Include(i => i.Fornecedores)
                         .FirstOrDefault(i => i.Id == entity.Id);

    // Excluídos
    var idsFornecedoresExcluidos = item.Fornecedores
                                       .Where(f => !f.Selecionado)
                                       .Select(f => f.Id)
                                       .ToList();

    foreach (var fornecedor in item.Fornecedores.Where(f => idsFornecedoresExcluidos.Contains(f.Id)))
    {
        item.Fornecedores.Remove(fornecedor);
    }

    __dbContext.SaveChanges();

    // Incluídos
    var idsFornecedoresIncluidos = item.Fornecedores
                                       .Where(f => f.Selecionado)
                                       .Select(f => f.Id)
                                       .ToList();

    foreach (var novoFornecedor in __dbContext.Fornecedores.Where(f => idsFornecedoresIncluidos.Contains(f.Id)))
    {
        item.Fornecedores.Add(novoFornecedor);
    }

    __dbContext.SaveChanges();

    // Alterando item.
    item.Nome = entity.Nome;
    _dbContext.Entry(item).State = EntityState.Modified;
    __dbContext.SaveChanges();

    return item;
}

Since there are several operations, a transactional scope here would be interesting:

public override ItemEntity Atualizar(ItemViewModel entity)
{
    using (var scope = new TransactionScope()) // Adicione a referência para System.Transactions para ter isso funcionando
    {
        var item = _dbContext.TbItem
                             .Include(i => i.Fornecedores)
                             .FirstOrDefault(i => i.Id == entity.Id);

        // Excluídos
        var idsFornecedoresExcluidos = item.Fornecedores
                                           .Where(f => !f.Selecionado)
                                           .Select(f => f.Id)
                                           .ToList();

        foreach (var fornecedor in item.Fornecedores.Where(f => idsFornecedoresExcluidos.Contains(f.Id)))
        {
            item.Fornecedores.Remove(fornecedor);
        }

        __dbContext.SaveChanges();

        // Incluídos
        var idsFornecedoresIncluidos = item.Fornecedores
                                           .Where(f => f.Selecionado)
                                           .Select(f => f.Id)
                                           .ToList();

        foreach (var novoFornecedor in __dbContext.Fornecedores.Where(f => idsFornecedoresIncluidos.Contains(f.Id)))
        {
            item.Fornecedores.Add(novoFornecedor);
        }

        __dbContext.SaveChanges();

        // Alterando item.
        item.Nome = entity.Nome;
        _dbContext.Entry(item).State = EntityState.Modified;
        __dbContext.SaveChanges();

        scope.Complete(); // Não esqueça do 'scope.Complete()' para completar a transação.
        return item;
    }
}

If you need help with loading the ViewModel , only manifest by comment.

    
26.05.2017 / 17:00