Entityframework complaining of Identifier duplicity even with the property being null

Asked

Viewed 1,466 times

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; }
}

Entree, representing an entry into 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 insert 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 it is already possible to see that the property of type Produto will stay null and only property ProdutoId will have value.

The method AddAsync of my service class is 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 vision model 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,
    };
}

That of 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();
}

print

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

Attaching an Entity of type 'Controleroupas.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 yet 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 am entering an Input, and the Product property is null, because Entityframework accuses this error?

  • I think it’s because of ProdutoId = 1 and Produto = null. You’ll have to read the Product whose id is 1 assign it to Entrada.Produto. You also need to ensure that _context.Entry(Produto).State == EntityState.Unchanged

  • @ramaral, Because it happens, being that it doesn’t always occur, I usually make insertions in this way.

  • So you will need to identify what is different in each of the situations.

  • @ramaral, Boy, hard sometimes to understand what this business wants. I understood that not.

2 answers

1

Your problem is related to how the Entity Framework tracks 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 have attached (even if you didn’t realize) 2 product entities with the same Id. First you used your Factory/mapper to create a product through Createentradafrommodel(). The second instance was probably created in your Updatingproduct service (you didn’t post the code). You must pass the same instance that was created earlier by your Factory.

Instead of passing the method as a parameter to the repository, save the return of your Factory 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);
  • The explanation is okay, but the code doesn’t solve the problem. There is still the aggravating fact that the questioner used a design pattern that makes EF work incorrectly for this case. In the case of addition, it is best to load the entire product record and assign by object.

0

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

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

In doing:

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

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

Browser other questions tagged

You are not signed in. Login or sign up in order to post.