在Entity Framework核心中更新多对多

问题描述 投票:1回答:1

以下是相关的类及其配置:

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

在控制器中:

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

我也尝试过:

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

他们都抛出以下异常:

无法跟踪实体类型“ProductSpecification”的实例,因为已经跟踪了具有{'ProductId','ProductAttributeValueId'}的相同键值的另一个实例。附加现有实体时,请确保仅附加具有给定键值的一个实体实例。考虑使用'DbContextOptionsBuilder.EnableSensitiveDataLogging'来查看冲突的键值。

找不到问题究竟在哪里!任何帮助将非常感谢!

c# asp.net-core entity-framework-core
1个回答
1
投票

好问题终于确定了!在Entity Framework 6.x中,在多对多导航属性更新期间,我们可以清除/删除现有子列表,然后将新子列表添加到父级,最后更新父级,并使用新子级列表更新父级。

但是在Entity Framework Core中,我们不能这样做。错误消息表明我们不能多次拥有具有相同键值的同一个子。我们必须维护Entity Framework事务中实体的唯一性。

这就是为什么我们必须首先确定哪些现有的孩子被删除以及在更新操作期间新添加了哪些孩子。这将使Entity框架Transaction状态唯一。

所以Update方法如下:

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);
}
© www.soinside.com 2019 - 2024. All rights reserved.