【问题标题】:Entity Framework 4.1: saving many to many relationship saves exsting child entities again in the dbEntity Framework 4.1:保存多对多关系将现有子实体再次保存在数据库中
【发布时间】:2011-11-20 21:11:59
【问题描述】:

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

我有一个简单的场景,我有一个产品实体和一个组合产品实体。它们之间是多对多的关系,所以一个Product可以属于多个CombinedProduct,一个CombinedProduct可以包含多个product。

我首先创建一个产品并将其保存到数据库中。稍后我创建了一个 CombinedProduct 并添加了这个产品。当我尝试保存此 CombinedProduct 时,Entity Framework 将产品实体再次添加到数据库中,而不仅仅是添加关系......这真的让我发疯了。

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

您可以在下面找到所有这些的代码(简化和代码剥离):

产品实体

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 之前,但这几乎不是解决方案。

希望有人能帮助我,这让我发疯了! (我使用 Entity Framework 4.1,代码优先)

编辑

添加产品:这是在它自己的数据上下文中以其他形式完成的:

Using myDataContext As New DataContext
    myDataContext.SingleProducts.Add(singleProduct)
    myDataContext.SaveChanges()
End Using

组合产品创建

Dim myCombinedProduct = New CombinedProduct
myCombinedProduct.Products.Add(product)

我添加的产品首先在它自己的数据上下文中再次获取:

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

编辑

完整的故事希望更清楚:

我有一个带有两种形式的 winforms 应用程序:一种用于管理产品,另一种用于管理组合产品。该应用程序是 N 层(用户层、业务层和数据层)。

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

组合的产品形式是另一回事:

  1. 在加载表单时,我从数据库中检索所有产品,通过业务和数据层。在数据层中检索产品的函数有它自己的 DataContext(使用块)。此函数返回一个 iEnumerable 产品。
  2. 检索到的产品作为对象添加到多个组合框中。
  3. 您选择要添加到组合产品中的产品,方法是选择它们
  4. 保存时,我在用户层创建了一个新的 CombinedProduct 实体,从组合框中检索产品对象并将它们添加到新的 CombinedProduct 对象中
  5. 我将 CombinedProduct 对象发送到我执行许多业务规则的业务层
  6. 如果一切顺利,combinedProduct 将发送到数据层,我尝试将其再次保存在它自己的数据上下文中(使用块)。

所以我有多个 DataContexts,因为它们在数据层中生存和死亡。

希望这能让事情更清楚一点。

【问题讨论】:

  • “我首先创建一个产品并将其保存到数据库中。稍后我创建一个组合产品并添加此产品。” 你能展示这个过程的代码吗?这是真正重要的代码 sn-p。
  • 添加产品这是在它自己的数据上下文中以其他形式完成的 Using myDataContext As New DataContext myDataContext.SingleProducts.Add(singleProduct) myDataContext.SaveChanges() End Using 组合产品创建 Dim myCombinedProduct = New CombinedProduct myCombinedProduct.Products.Add(product) 我添加的产品首先在它自己的数据上下文中再次获取 ` Using myDataContext As New DataContext Return myDataContext.Products.FirstOrDefault(Function(p) p.ProductId = id) End Using `
  • 您可以编辑您的问题以获取此类详细信息(问题下方的“编辑”链接)。我现在已经完成了,就像下次的信息一样。

标签: entity-framework entity-framework-4.1


【解决方案1】:

我不明白你按什么顺序执行哪些步骤,但正确的顺序应该是:

如果一切都发生在一种情况下:

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

加载product 附加到上下文并避免重复。它必须在您将myCombinedProduct 添加到上下文之前发生。

如果您在另一个上下文中加载product

Using myDataContext As New DataContext
    myDataContext.Products.Attach(product)

    Dim myCombinedProduct = New CombinedProduct
    myCombinedProduct.Products.Add(product)

    myDataContext.CombinedProducts.Add(myCombinedProduct)
End Using

您必须将product 附加到新上下文中,再次您将myCombinedProduct 添加到上下文中。

编辑

如果您的新myCombinedProduct 包括产品集合分离状态进入数据层,则以下应该可以工作:

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

    myDataContext.CombinedProducts.Add(myCombinedProduct)
End Using

【讨论】:

  • 嗯,那是我想错了。在我的解决方案(即 N-Tier)中,我尝试在将产品添加到组合产品后附加它们。我的数据层中的每个函数都有一个新的数据上下文,因为大多数添加产品的操作都是在我没有上下文的业务层中完成的。所以我看到了两种解决方案:重做所有东西,只需将产品 id 传递给带有组合产品对象的数据层,然后在那里获取/添加产品,或者为整个 winforms 应用程序使用一个数据上下文,但这似乎根本不对。我希望有第三种解决方案...
  • @DirkDooms:在将产品添加到 myCombinedProduct 中的集合后附加不是问题,仅在添加到上下文后附加(我的示例中的最后一行)。我仍然不清楚你在什么情况下以什么顺序到底在做什么。您可以尝试编辑您的问题并使其尽可能清晰吗?
  • @Slaumu:我通过帖子编辑希望更清楚。感谢您到目前为止的时间和帮助!
【解决方案2】:

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

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

同样在多对多中,两个实体都必须添加到上下文中,关系只是要填充连接表

查看讨论,您似乎为每种实体类型都有一个存储库,并且正在通过这些存储库方法获取对象。如果是这样,那么 IMO 这种设计就有问题,因为对象关系存储在一个公共上下文中。在你的情况下,所有这些存储库都应该具有相同的 dbcontexts。你可能会认为去数据库会有一些性能问题,但是即使你附加了你也会去数据库。

【讨论】:

  • 我害怕这样的事情。我应该如何在 winforms 应用程序中仅使用一个 Datacontext 来完成?我应该在应用程序启动时创建一个数据上下文并简单地继续使用那个,还是这是个坏主意?
  • 不推荐长时间运行的数据上下文。一种选择可能是将您的产品再次放入 dataContext 中(您已经拥有该产品的密钥)并限制您的 dataContexts 范围,例如每当您想联系 db 时,只需打开一个上下文,获取您需要的产品,修改它们值,保存并处理 dbcontext。
  • 但这对我来说似乎很奇怪?当上下文已经知道它们时,我必须首先从数据库中再次获取产品(我认为无论如何,因为当我尝试附加它们时,EF 说它无法附加它们,因为具有相同密钥的产品已经在上下文)
  • 但我认为这意味着在 Winform 的整个生命周期中都在 DataContext 上,对吧?如果这是一个可接受的解决方案或后果是什么,我一直在尝试查找一些文档或博客,但到目前为止还没有运气......
  • 和每一个一样,这取决于,我会保持简短,我会从表单创建一个上下文,创建我在使用中需要的存储库并处理数据上下文。看看这篇文章是否有帮助blogs.msdn.com/b/dsimmons/archive/2008/02/17/…
猜你喜欢
  • 1970-01-01
  • 2016-12-18
  • 1970-01-01
  • 1970-01-01
  • 2020-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多