EntityFramework complaining of duplicate identifier even with property being null

2

I have the following classes:

Product :

public class Produto
{
    [Key]
    public int Id { get; set; }

    [MaxLength(70)]
    [Required(AllowEmptyStrings = false)]
    public string Descricao { get; set; }

    [MaxLength(10)]
    [Required(AllowEmptyStrings = false)]
    public string Tamanho { get; set; }

    [Required]
    public double Preco { get; set; }
}

Entry , which represents an entry in stock:

public class Entrada
{
    [Key]
    public int Id { get; set; }

    [Required]
    [ForeignKey("Produto")]
    public int ProdutoId { get; set; }
    public virtual Produto Produto { get; set; }

    [Required]
    [Column(TypeName = "Date")]
    public DateTime DataCadastro { get; set; }

    [Required]
    public int Quantidade { get; set; }

    [Required]
    public double ValorCompra { get; set; }

    [Required]
    public double ValorVenda { get; set; }
}

In my controller the Action that receives the post to enter an entry record is like this:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(
    [Bind(Include = "Id, ProdutoId, DataCadastro, Quantidade, ValorCompra, ValorVenda")]
    EntradaModelEdit model)
{
    if (ModelState.IsValid)
    {
        await _service.AddAsync(model);
        return RedirectToAction("Index");
    }

    return View(model);
}

Only by Bind in Action you can already see that the property of type Produto will be null and only the ProdutoId property will have value .

The AddAsync method of my service class looks like this:

public async Task AddAsync(EntradaModelEdit model)
{
    using (var transaction = _repository.Context.Database.BeginTransaction())
    {
        try
        {
            await _repository.AddAsync(EntradaResolver.CreateEntradaFromModel(model));

            var produtoService = new ProdutoService(new ProdutoRepository(_repository.Context));
            await produtoService.AtualizarPrecoProdutoAsync(model.ProdutoId, model.ValorVenda);

            transaction.Commit();
        }
        catch (Exception e)
        {
            transaction.Rollback();
            throw new Exception(e.Message);
        }
    }
}

EntradaResolver.CreateEntradaFromModel is my viewer class mapper for bank entities and vice versa:

public static Entrada CreateEntradaFromModel(EntradaModel obj)
{
    if (obj == null)
        return null;

    return new Entrada
    {
        Id = obj.Id,
        ProdutoId = obj.ProdutoId,
        Produto = ProdutoResolver.CreateProdutoFromModel(obj.Produto),
        DataCadastro = obj.DataCadastro,
        Quantidade = obj.Quantidade,
        ValorCompra = obj.ValorCompra,
        ValorVenda = obj.ValorVenda,
    };
}

Product :

public static Produto CreateProdutoFromModel(ProdutoModel obj)
{
    if (obj == null)
        return null;

    return new Produto
    {
        Id = obj.Id,
        Descricao = obj.Descricao,
        Tamanho = obj.Tamanho,
        Preco = obj.Preco
    };
}

And finally, in my repository I have the following in AddAsync :

public async Task AddAsync(TEntity model)
{
    _context.Entry(model).State = EntityState.Added;
    await _context.SaveChangesAsync();
}

After executing such lines as AddAsync of Repository I already get the following error:

  

Attaching an entity of type 'ControlRoupas.Domain.Entity.Product' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

But I'm inserting an Entry, and the Product property is null, because EntityFramework accuses this error?

    
asked by anonymous 08.05.2015 / 21:08

2 answers

1

Your issue is related to how the Entity Framework does tracking objects, which uses the Identiy Map Pattern , that is, only a single instance with the same primary key can be attached to the context.

In your case, the problem is that you attached (even unknowingly) 2 product entities with the same ID. First you used your factory / mapper to create a product through CreateFromModel (). The second instance was probably created in your UpdatePrecoProdutoAsync service (you did not post the code). You must pass the same instance that was previously created by your factory.

Instead of passing the method as a parameter to the repository, save your factory return in a variable:

var entrada = EntradaResolver.CreateEntradaFromModel(model);
await _repository.AddAsync(entrada);

var produtoService = new ProdutoService(new ProdutoRepository(_repository.Context));
await produtoService.AtualizarPrecoProdutoAsync(entrada.Produto, model.ValorVenda);
    
09.05.2015 / 05:13
0

The Entity Framework does not work well when you define a foreign key directly, as in the case of Produto . The correct one would look something like:

var produto = contexto.Produtos.SingleOrDefault(p => p.Id == 1);
entrada.Produto = produto;

When doing:

    ProdutoId = obj.ProdutoId,
    Produto = ProdutoResolver.CreateProdutoFromModel(obj.Produto),

The Entity Framework thinks that you are creating a new object, however much it exists. This is further aggravated in Entity Framework 6, where dependent entities are also persisted in cascade in SaveChanges() .

    
09.05.2015 / 05:30