Updating many to many in Entity Framework core

北慕城南 提交于 2021-02-10 19:21:14

问题


Here are the relevant classes and their configuration:

public class Product 
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long ProductId { get; set; }

    [Required]
    public string ProductName { get; set; }

    public ICollection<ProductSpecification> ProductSpecifications { get; set; }
} 

public class ProductAttributeValue 
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public long ProductAttributeValueId { get; set; }

    [Required]
    public string ProductAttributeValueName { get; set; }

    public ICollection<ProductSpecification> ProductSpecifications { get; set; }
}

public class ProductSpecification
{
    public long ProductId { get; set; }
    public long ProductAttributeValueId { get; set; }

    public string Note { get; set; }

    public Product Product { get; set; }
    public ProductAttributeValue ProductAttributeValue { get; set; }
}

// Configuration in the dbConext
modelBuilder.Entity<ProductSpecification>().HasKey(ps => new { ps.ProductId, ps.ProductAttributeValueId });
modelBuilder.Entity<ProductSpecification>().HasOne(p => p.Product).WithMany(ps => ps.ProductSpecifications).HasForeignKey(ps => ps.ProductId);
modelBuilder.Entity<ProductSpecification>().HasOne(pav => pav.ProductAttributeValue).WithMany(ps => ps.ProductSpecifications).HasForeignKey(ps => ps.ProductAttributeValueId);

In the controller :

public async Task<IActionResult> UpdateProduct([FromRoute] long id, [FromBody] Product product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    if (id != product.ProductId)
    {
        return BadRequest();
    }

    Product productToBeUpdated = await _unitOfWork.Repository<Product>().GetEntityList(p => p.ProductId == id).Include(p => p.ProductSpecifications).SingleOrDefaultAsync();

    if (productToBeUpdated == null)
    {
        return NotFound();
    }

    foreach (ProductSpecification productSpecification in productToBeUpdated.ProductSpecifications.ToList())
    {
        productToBeUpdated.ProductSpecifications.Remove(productSpecification);
    }

    productToBeUpdated.ProductSpecifications = product.ProductSpecifications;

    productToBeUpdated.ModifiedOn = DateTime.UtcNow;
    await _unitOfWork.SaveChangesAsync();

    return Ok(true);
 }

I have also tried:

foreach (ProductSpecification productSpecification in productToBeUpdated.ProductSpecifications.ToList())
{
    _unitOfWork.Repository<ProductSpecifications>().DeleteEntity(productSpecification);
}

Both of them throwing the following exception:

The instance of entity type 'ProductSpecification' cannot be tracked because another instance with the same key value for {'ProductId', 'ProductAttributeValueId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

Could not find where the problem actually lies! Any help will be highly appreciated!


回答1:


Well problem is finally identified! In Entity Framework 6.x, during many to to many navigation properties update, we can clear/delete the existing child list and then add the new child list to the parent and finally update the parent and parent is updated with the new child list.

But in Entity Framework Core, we cannot do the same.The error message suggest that we cannot have same child with the same key value more than once. We have to maintain the uniqueness of entities in the Entity Framework transaction.

That's why we have to first identified which existing children are being deleted and which children are being newly added during update operation. This will make Entity framework Transaction state unique.

So the Update method will be as follows:

public async Task<IActionResult> UpdateProduct([FromRoute] long id, [FromBody] Product product)
{
    if (!ModelState.IsValid)
    {
       return BadRequest(ModelState);
    }

    if (id != product.ProductId)
    {
       return BadRequest();
    }

    Product productToBeUpdated = await _unitOfWork.Repository<Product>().GetEntityList(p => p.ProductId == id).Include(p => p.ProductSpecifications).SingleOrDefaultAsync();
    if (productToBeUpdated == null)
    {
        return NotFound();
    }

    _mapper.Map(product, productToBeUpdated); // If you use AutoMapper than ignore the many-to-many navigation property in the mapping

   List<ProductSpecification> productSpecificationsToBeDeleted = productToBeUpdated.ProductSpecifications.Where(c1 => product.ProductSpecifications.All(c2 => c2.ProductAttributeValueId != c1.ProductAttributeValueId)).ToList();
   foreach (ProductSpecification productSpecification in productSpecificationsToBeDeleted)
   {
                productToBeUpdated.ProductSpecifications.Remove(productSpecification);
   }

   List<ProductSpecification> productSpecificationsToBeAdded = product.ProductSpecifications.Where(c1 => productToBeUpdated.ProductSpecifications.All(c2 => c2.ProductAttributeValueId != c1.ProductAttributeValueId)).ToList();
   foreach (ProductSpecification productSpecification in productSpecificationsToBeAdded)
   {
                productToBeUpdated.ProductSpecifications.Add(productSpecification);
   }

   productToBeUpdated.ModifiedOn = DateTime.UtcNow;
   await _unitOfWork.SaveChangesAsync();
   return Ok(true);
}


来源:https://stackoverflow.com/questions/48962213/updating-many-to-many-in-entity-framework-core

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!