代码之家  ›  专栏  ›  技术社区  ›  quiqs

为什么我得到DbUpdateException:OptimisticConcurrencyException?

  •  0
  • quiqs  · 技术社区  · 7 年前

    我有一个分类班:

    public class Category
    {
        public int CategoryId { get; set; }
        public string CategoryName { get; set; }
    }
    

    public class Subcategory
    {
        public int SubcategoryId { get; set; }
        public Category Category { get; set; }
        public string SubcategoryName { get; set; }
    }
    

    public class Flavor
    {
        public int FlavorId { get; set; }
        public Subcategory Subcategory { get; set; }
        public string FlavorName { get; set; }
    }
    

    然后我还有Filling和Frosting类,就像Flavor类一样,也有Category和Subcategory导航属性。

    我有一个产品类,它有一个Flavor导航属性。

    public class OrderItem
    {
        public int OrderItemId { get; set; }
        public string OrderNo { get; set; }
        public Product Product { get; set; }
        public Frosting Frosting { get; set; }
        public Filling Filling { get; set; }
        public int Quantity { get; set; }
    }
    

    我在尝试保存OrderItem对象时遇到问题。我不停地 DbUpdateException: An error occurred while saving entities that do not expose foreign key properties for their relationships. OptimisticConcurrencyException: Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. 我已经检查了我的代码好几次,我找不到任何修改或删除从数据库加载的任何实体的内容。我已经能够保存OrderItem,但是它在DB中创建了Product、Flavor、Subcategory和Category项的重复条目。我将OrderItem的EntityState更改为Modified,但这会引发上述异常。我想可能是因为我的产品、磨砂和填充对象都引用了相同的子类别和类别对象,所以我尝试分离磨砂和填充、保存、附加、将OrderItem实体状态更改为Modified并再次保存,但这也引发了上述异常。

    db.OrderItems.Add(orderItem);
    

    在上述所有原因行之后添加以下任何语句 db.SaveChanges();

    db.Entry(item).State = EntityState.Modified;
    db.Entry(item.Product.Flavor.Subcategory.Category).State = EntityState.Modified;
    db.Entry(item.Product.Flavor.Subcategory).State = EntityState.Modified;
    db.Entry(item.Product.Flavor).State = EntityState.Modified;
    db.Entry(item.Product).State = EntityState.Modified;
    

    有人能给我一些见解吗?我的课程设计糟糕吗?

    1 回复  |  直到 7 年前
        1
  •  1
  •   Steve Py    7 年前

    首先要检查的是实体关系是如何映射的。通常导航属性应该标记为virtual,以确保EF可以代理它们。另一个优化是,如果实体引用子类别,那么由于子类别引用一个类别,这些实体不需要同时引用这两个类别。只有在子类别是可选的情况下才需要这两个。两者兼有并不一定会引起问题,但它会导致霜的类别与霜的子类别的类别不匹配的情况(看到了足够多的这样的bug,这取决于代码是frosting.CategoryId还是frosting.SubCategory.CategoryId)你的风格定义似乎只使用了SubCategory,这是好的,只是需要小心。

    错误细节似乎表明EF知道实体,但没有被告知它们之间的关系。您需要确保您有映射细节来告诉EF霜和子类别是如何相关的。EF可以自动推断出其中的一些,但我的偏好总是明确的(我讨厌惊喜!)

    public class FrostingConfiguration : EntityTypeConfiguration<Frosting>
    {
      public FlavorConfiguration()
      {
        ToTable("Flavors");
        HasKey(x => x.FlavorId)
          .Property(x => x.FlavorId)
          .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
    
        HasRequired(x => x.SubCategory)
          .WithMany()
          .Map(x => x.MapKey("SubCategoryId");
      }
    }
    

    考虑到您的Flavor实体似乎没有子类别id的属性,告诉EF它是有帮助的。EF也许能够推断出这一点,但是有了id和它所寻找的自动命名约定,我就不用费心去记什么是自动工作的了。

    如果这是EF核心,你可以替换 .Map()

    .ForeignKey("SubCategoryId");
    

    将为FK设置阴影属性。

    如果子类别是可选的,则替换 HasRequired 具有 HasOptional . 这个 WithMany() 只是表示当一个口味引用一个子类别时,子类别并不维护一个口味列表。

    下一个注意点是在加载实体的DBContext范围之外传递实体。虽然EF确实支持将实体从一个上下文中分离出来并重新连接到另一个上下文,但我认为这种做法几乎是错误的 总是 麻烦远比它值得的多。将实体映射到POCO ViewModels/dto,然后在执行更新时按需再次加载它们更简单,并且在尝试重新附加它们时更不容易出错。数据状态可能在最初加载它们的时间和重新附加它们的时间之间发生了变化,因此故障保护代码无论如何都需要处理这种情况。它还省去了在实体集中处理修改状态的麻烦。虽然第二次加载实体似乎很有效,但是通过采用视图模型,您可以更有效地优化读取,只需回调和传输有意义的数据,而不是整个实体图(系统通常读取的内容远远多于更新的内容)即使对于更新繁重的操作,也可以利用有界上下文将大型表表示为较小的简单实体,以便更有效地加载和更新几个关键字段。