Entity Framework - Duplicar entrada '1' para a chave 'PRIMARY' "

Nov 17 2020

Usando MySql DB e Entity Framework, ao tentar inserir um dado que contenha uma lista de dados filho, recebo este erro: InnerException = {"Entrada duplicada '1' para a chave 'PRIMARY'"}


Aqui está uma imagem das minhas mesas: https://i.stack.imgur.com/bAnVy.png


Estas são minhas modelos:
public class Etapa 
{
    public int Id { get; set; }

    public string Descricao { get; set; }

    [Column("ativo", TypeName = "bit")]
    public bool Ativo { get; set; }

    [Column("finalizadora", TypeName = "bit")]
    public bool Finalizadora { get; set; }

    public List<EtapaVinculada> ListaEtapaVinculada { get; set; }
}

[Table("etapa_vinculada")]
public class EtapaVinculada 
{
    public int Id { get; set; }

    [Column("id_etapa_pai")]
    public int EtapaPaiId { get; set; }

    public Etapa EtapaPai { get; set; }

    [Column("id_etapa_filha")]
    public int EtapaFilhaId { get; set; }

    public Etapa EtapaFilha { get; set; }

    public string Descricao { get; set; }
}

Os contextos da Etapa estão aqui:

public class ContextoEtapa : Contexto
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Etapa>().HasMany(x => x.ListaEtapaVinculada).WithOne(y => y.EtapaPai).HasForeignKey(x => x.EtapaPaiId);
    }

    public async Task Adicionar(Etapa registro)
    {
        await AddAsync(registro);
        await SaveChangesAsync();
    }
}

Preenchendo manualmente as tabelas no DB, ao depurar meu contexto posso ver que meu objeto Etapa está preenchido e a propriedade ListEtapaVinculada também está preenchida, corretamente.

O problema ocorre quando o objeto Etapa preenchido com sua lista de EtapaVinculada vai ser inserido no banco de dados através do método Add. Parece-me que fiz algum mapeamento errado, pois dá a impressão que a Entidade tenta inserir 2x o registro da Etapa seguidos, caindo no erro de chave duplicada.

O incremento automático está funcionando. Se eu tentar salvar um objeto como este:

 {
        Etapa etapa = new Etapa();
        etapa.Descricao = "test";
        etapa.Ativo = true;
        etapa.Finalizadora = true;
        etapa.ListaEtapaVinculada = new List<EtapaVinculada>(); // Without itens

        using (var context = new ContextoEtapa())
        {
            await context.Etapa.AddAsync(etapa);
            await context.SaveChangesAsync();
        }
    }

Mas, se eu fizer algo assim:

 {
        Etapa etapaFilha = null;

        using (var context = new ContextEtapa())
        {
            etapaFilha = await context.Etapa.Where(x => x.Id == 666).First();
        }

        Etapa etapa = new Etapa();
        etapa.Descricao = "test";
        etapa.Ativo = true;
        etapa.Finalizadora = true;
        etapa.ListaEtapaVinculada = new List<EtapaVinculada>();

        EtapaVinculada etapaVinculada = new EtapaVinculada();
        etapaVinculada.EtapaPaiId = etapa.Id;
        etapaVinculada.EtapaPai = etapa;
        etapaVinculada.EtapaFilhaId = etapaFilha.Id;
        etapaVinculada.EtapaFilha = etapaFilha;

        etapa.listaEtapaVinculada.Add(etapaVinculada);

        using (var context = new ContextoEtapa())
        {
            await context.Etapa.AddAsync(etapa);
            await context.SaveChangesAsync();
        }
    }

Agora recebo os erros de chave duplicada. Parece-me que EF está tentando inserir o objeto 2x Etapa, quando o correto é inserir Etapa, então inserir todos os itens de ListaEtapaVinculada.

Respostas

henocsalinas Nov 19 2020 at 06:27

Acho que o problema é ao tentar atribuir uma instância do objeto e um id ao mesmo tempo, tente comentar na seguinte linha de código:

 {
    Etapa etapaFilha = null;

    using (var context = new ContextEtapa())
    {
        etapaFilha = await context.Etapa.Where(x => x.Id == 666).First();
    }

    Etapa etapa = new Etapa();
    etapa.Descricao = "test";
    etapa.Ativo = true;
    etapa.Finalizadora = true;
    etapa.ListaEtapaVinculada = new List<EtapaVinculada>();

    EtapaVinculada etapaVinculada = new EtapaVinculada();
    // etapaVinculada.EtapaPaiId = etapa.Id; // this is asigned when asign to collection and savechanges

    // etapaVinculada.EtapaPai = etapa;

    etapaVinculada.EtapaFilhaId = etapaFilha.Id; // 

    // etapaVinculada.EtapaFilha = etapaFilha; this is duplicate

    etapa.listaEtapaVinculada.Add(etapaVinculada);

    using (var context = new ContextoEtapa())
    {
        await context.Etapa.AddAsync(etapa);
        await context.SaveChangesAsync();
    }
}