Entity framework 实体框架4.1:保存多对多关系将在数据库中再次保存现有子实体

Entity framework 实体框架4.1:保存多对多关系将在数据库中再次保存现有子实体,entity-framework,entity-framework-4.1,Entity Framework,Entity Framework 4.1,我有一个非常令人沮丧的问题(无论如何对我来说): 我有一个简单的场景,其中我有一个产品实体和一个组合产品实体。它们之间存在多对多关系,因此一个产品可以属于多个组合产品,而一个组合产品可以包含多个产品 我首先创建一个产品并将其保存到数据库中。稍后,我创建一个组合产品并添加此产品。当我试图保存这个组合产品时,产品实体将通过实体框架再次添加到数据库中,而不仅仅是添加关系。。。这真让我发疯 在保存之前,我已经尝试将产品再次附加到上下文,但实体抱怨它已经有一个具有相同密钥的产品 下面是所有这些的代码(简化

我有一个非常令人沮丧的问题(无论如何对我来说):

我有一个简单的场景,其中我有一个产品实体和一个组合产品实体。它们之间存在多对多关系,因此一个产品可以属于多个组合产品,而一个组合产品可以包含多个产品

我首先创建一个产品并将其保存到数据库中。稍后,我创建一个组合产品并添加此产品。当我试图保存这个组合产品时,产品实体将通过实体框架再次添加到数据库中,而不仅仅是添加关系。。。这真让我发疯

在保存之前,我已经尝试将产品再次附加到上下文,但实体抱怨它已经有一个具有相同密钥的产品

下面是所有这些的代码(简化和代码剥离):

产品实体

Public Class SingleProduct
  Property SingleProductId As Integer
  Property CombinedProducts As ICollection(Of CombinedProduct)
End Class
组合产品

Public Class CombinedProduct
  Public Sub New()
    Me.Products = New HashSet(Of SingleProduct)()
  End Sub

  Property CombinedProductId As Integer
  Property Products As ICollection(Of SingleProduct)
End Class
多对多关系定义

Protected Overrides Sub OnModelCreating(modelBuilder As DbModelBuilder)
   modelBuilder.Entity(Of CombinedProduct)().
                        HasMany(Function(c) c.Products).
                        WithMany(Function(p) p.CombinedProducts).
                        Map(Sub(m)
                                m.ToTable("CombinedProductSingleProducts")
                                m.MapLeftKey("SingleProductId")
                                m.MapRightKey("CombinedProductId")
                            End Sub)
End Sub
用于保存的代码

Using myDataContext As New DataContext
  myDataContext.CombinedProducts.Add(product)
  myDataContext.SaveChanges()
End Using
在保存之前已尝试附加,但无效

For Each prd In product.Products
   If myDataContext.Entry(prd).State = EntityState.Detached Then
      myDataContext.SingleProducts.Attach(prd)
   End If
Next
我找到的一个“解决方案”是在保存之前,清除产品列表,再次从数据库中获取产品,并将其添加到CombinedProduct中,但这很难是解决方案

希望有人能帮我,这让我快发疯了! (我使用实体框架4.1,首先使用代码)

编辑

Public Class CombinedProduct
  Public Sub New()
    Me.Products = New HashSet(Of SingleProduct)()
  End Sub

  Property CombinedProductId As Integer
  Property Products As ICollection(Of SingleProduct)
End Class
添加产品:这是在它自己的数据上下文中以其他形式完成的:

Using myDataContext As New DataContext
    myDataContext.SingleProducts.Add(singleProduct)
    myDataContext.SaveChanges()
End Using
组合产品创建

Dim myCombinedProduct = New CombinedProduct
myCombinedProduct.Products.Add(product)
我添加的产品首先在其自己的datacontext中再次获取:

Using myDataContext As New DataContext
    Return myDataContext.Products.FirstOrDefault(Function(p) p.ProductId = id)
End Using
编辑

Public Class CombinedProduct
  Public Sub New()
    Me.Products = New HashSet(Of SingleProduct)()
  End Sub

  Property CombinedProductId As Integer
  Property Products As ICollection(Of SingleProduct)
End Class
完整的故事,希望更清楚:

我有一个winforms应用程序,它有两个表单:一个用于管理产品,另一个用于管理组合产品。应用程序是N层(用户层、业务层和数据层)

在管理产品的表单上,您可以简单地添加/更新/删除产品。在这种形式下,一切正常

组合产品形式是另一个问题:

  • 加载表单时,我从数据库中检索所有产品,通过业务层和数据层。在数据层中检索产品的函数有自己的DataContext(使用块)。此函数返回产品的iEnumerable
  • 检索到的产品作为对象添加到许多组合框中
  • 您可以通过选择产品来选择要添加到组合产品中的产品
  • 保存时,我在用户层中创建一个新的CombinedProduct实体,从组合框中检索产品对象并将其添加到新的CombinedProduct对象
  • 我将CombinedProduct对象发送到业务层,在那里我执行许多业务规则
  • 如果一切正常,组合产品将被发送到数据层,在那里我尝试再次将其保存在自己的数据上下文中(使用block)
  • 因此,我有多个数据上下文,因为它们在数据层中生死存亡


    希望这能让事情变得更清楚。

    在EF中,您试图关联的所有实体都必须属于同一个datacontext,否则它不会将您的产品识别为现有产品,而是会添加一个新的 在首次使用时,必须首先从db获取产品

    我从不需要附加/分离实体,但我见过大多数人在这样做时遇到问题

    此外,在多对多中,两个实体都必须添加到上下文中,关系只会填充联接表


    从讨论中可以看出,对于每种实体类型,您都有一个存储库,并且通过这些存储库方法获取对象。如果是这样的话,那么这个设计就有问题了,因为对象关系存储在一个公共上下文中。您可能认为访问数据库会有一些性能问题,但即使在连接数据库时,您也会访问数据库。

    我不太清楚您执行哪些步骤的顺序,但正确的顺序应该是:

    如果所有事情都发生在一个上下文中:

    Using myDataContext As New DataContext
        Dim product = myDataContext.Products.FirstOrDefault(Function(p) p.ProductId = id)
    
        Dim myCombinedProduct = New CombinedProduct
        myCombinedProduct.Products.Add(product)
    
        myDataContext.CombinedProducts.Add(myCombinedProduct)
    End Using
    
    加载
    产品
    会附加到上下文并避免重复。必须在将
    myCombinedProduct
    添加到上下文之前执行此操作

    如果在另一个上下文中加载
    产品

    Using myDataContext As New DataContext
        myDataContext.Products.Attach(product)
    
        Dim myCombinedProduct = New CombinedProduct
        myCombinedProduct.Products.Add(product)
    
        myDataContext.CombinedProducts.Add(myCombinedProduct)
    End Using
    
    在将
    myCombinedProduct
    添加到上下文之前,您必须将
    product
    附加到新上下文

    编辑

    Public Class CombinedProduct
      Public Sub New()
        Me.Products = New HashSet(Of SingleProduct)()
      End Sub
    
      Property CombinedProductId As Integer
      Property Products As ICollection(Of SingleProduct)
    End Class
    
    如果您的新
    myCombinedProduct
    包括产品集合以分离状态进入数据层,则以下操作应有效:

    Using myDataContext As New DataContext
        For Each prd In myCombinedProduct.Products
            myDataContext.SingleProducts.Attach(prd)
        Next
    
        myDataContext.CombinedProducts.Add(myCombinedProduct)
    End Using
    

    我害怕这样的事情。如何在winforms应用程序中仅使用一个Datacontext?我是否应该在应用程序启动时创建一个datacontext并继续使用它,或者这是一个坏主意?不建议使用长时间运行的datacontext。一种选择是再次将产品放入dataContext(您已经拥有产品的密钥)并限制dataContext的作用域,比如,每当您想联系db时,只需打开一个上下文,获取所需的产品,修改其值,保存并处置dbcontext。但这对我来说真的很奇怪吗?我必须首先从数据库中再次获取产品,当上下文已经知道它们时(我想无论如何,因为当我尝试附加它们时,EF说它不能附加它们,因为具有相同密钥的产品已经在上下文中),但我想这意味着在Winform生命周期的整个时间内都在DataContext上,对吗?我一直在尝试寻找一些文档或博客,如果这是一个可接受的解决方案,或者会有什么后果,但到目前为止没有运气…就像每个人一样,这取决于情况,我会保持简短,我