ADO.NET实体框架生成意外的问题INSERT

时间:2009-02-16 21:58:06

标签: .net wpf linq entity-framework ado.net-entity-data-model

我需要一些帮助来理解ADO.NET实体框架。

我正在尝试使用ADO.NET实体框架在WPF TreeView控件中表示和操作分层数据。

ADO.NET Entity Framework Object with parent and children http://img14.imageshack.us/img14/7158/thingpi1.gif

这些事物中的每一个都有一个父母,零个或多个孩子。

我的“删除”按钮......

Private Sub ButtonDeleteThing_Click(...)
    db.DeleteObject(DirectCast(TreeViewThings.SelectedItem, Thing))
    db.SaveChanges()
End Sub

我正在调试我的应用程序时监视SQL Server Profiler:

  1. 第一个按钮点击删除就好了。
  2. 第二个按钮单击会插入一个重复的父项(但具有空的GUID uniqueidentifier主键),然后执行删除。
  3. 第三个按钮单击失败(违反PRIMARY KEY约束),因为它无法插入带有空GUID主键的另一行。
  4. 意外的,生成的T-SQL重复...

    exec sp_executesql N'insert [dbo].[Thing]([Id], [ParentId], ...)
    values (@0, @1, ...) ',N'@0 uniqueidentifier,@1 uniqueidentifier,...',
    @0='00000000-0000-0000-0000-000000000000',
    @1='389D987D-79B1-4A9D-970F-CE15F5E3E18A',
    ...
    

    但是,这不仅仅是删除。我的“添加”按钮与意外插入具有类似的行为。它遵循相同的模式。

    这让我觉得我将如何将这些实体类绑定到WPF TreeView或我的数据模型本身有一个更基本的问题。

    以下是相关代码......

    ... XAML

    <TreeView Name="TreeViewThings"
              ItemsSource="{Binding}"
              TreeViewItem.Expanded="TreeViewThings_Expanded"
              TreeViewItem.Selected="TreeViewThings_Selected"
              ... >
        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type local:Thing}"
                                      ItemsSource="{Binding Children}">
                <TextBlock Text="{Binding Path=Title}" />
            </HierarchicalDataTemplate>
        </TreeView.Resources>
    </TreeView>
    <Button Name="ButtonAddThing" Content="Add Thing" ... />
    <Button Name="ButtonDeleteThing" Content="Delete Thing" ... />
    

    Visual Basic ...

    Partial Public Class Window1
    
        Dim db As New ThingProjectEntities
    
        Private Sub Window1_Loaded(...) Handles MyBase.Loaded
            TreeViewThings.ItemsSource = _
                From t In db.Thing.Include("Children") _
                Where (t.Parent Is Nothing) _
                Select t
        End Sub
    
        Private Sub TreeViewThings_Expanded(...)
            Dim ExpandedTreeViewItem As TreeViewItem = _
                DirectCast(e.OriginalSource, TreeViewItem)
            LoadTreeViewChildren(ExpandedTreeViewItem)
        End Sub
    
        Sub LoadTreeViewChildren(ByRef Parent As TreeViewItem)
            Dim ParentId As Guid = DirectCast(Parent.DataContext, Thing).Id
            Dim ChildThings As System.Linq.IQueryable(Of Thing)
            ChildThings = From t In db.Thing.Include("Children") _
                          Where t.Parent.Id = ParentId _
                          Select t
            Parent.ItemsSource = ChildThings
        End Sub
    
        Private Sub ButtonAddThing_Click(...)
            Dim NewThing As New Thing
            NewThing.Id = Guid.NewGuid()
            Dim ParentId As Guid = _
                DirectCast(TreeViewThings.SelectedItem, Thing).Id
            NewThing.Parent = (From t In db.Thing _
                               Where t.Id = ParentId _
                               Select t).First
            ...
            db.AddToThing(NewThing)
            db.SaveChanges()
            TreeViewThings.UpdateLayout()
        End Sub
    
        Private Sub ButtonDeleteThing_Click(...)
            db.DeleteObject(DirectCast(TreeViewThings.SelectedItem, Thing))
            db.SaveChanges()
        End Sub
    
        ...
    
    End Class
    

    我做错了什么?为什么会产生这些奇怪的插入?


    更新

    我取得了突破。但是,我仍然无法解释它。

    当我简化这个问题的代码时,我已经摆脱了原因。

    而不是使用Linq如:

    From t In db.Thing.Include("Children") Where ...
    

    我一直在使用Linq,如:

    From t In db.Thing.Include("Children").Include("Brand") Where ...
    

    你看,My Thing实体与另一个品牌实体有关。

    ADO.NET Entity Framework Object with parent and children and a related Object http://img25.imageshack.us/img25/3268/thingbrandct4.gif

    我认为这是无关紧要的,所以我没有把它包含在上面的问题中。

    显然这是我意想不到的问题的原因,在我的Thing表中插入了问题。

    但是,为什么?任何人都可以解释为什么会这样吗?我想更好地理解它。

3 个答案:

答案 0 :(得分:2)

为什么在添加新子项时再次加载父项,当您已经拥有该对象时?虽然这对数据库没有问题,但它会在对象级别引入不一致。您可以像这样使用现有的父级:

Private Sub ButtonAddThing_Click(...)
    Dim NewThing As New Thing
    NewThing.Id = Guid.NewGuid()
    Dim Parent As Thing = DirectCast(TreeViewThings.SelectedItem, Thing)
    NewThing.Parent = Parent
    ...
    db.AddToThing(NewThing)
    db.SaveChanges()
    TreeViewThings.UpdateLayout()
End Sub

关于删除:您是否在数据库中指定了级联删除?

答案 1 :(得分:1)

我没有详细查看代码,但首先要考虑的是您不需要在层次结构的每个级别调用DeleteObject。 EF像其他O / RM一样跟踪对象及其关联。

例如,假设您有一个parent-&gt; child 1 .. *关系。如果查询父项,从父项Children集合中删除子对象,然后调用SaveChanges(),EF将为您生成相应的DELETE SQL语句 - 您不需要自己跟踪它。

因此,实现您的方案的更好方法是:

  1. 从EF中查询对象层次结构。
  2. 将其绑定到您的UI。让你的UI根据你的需要修改内存中的对象。
  3. 完成后,请致电SaveChanges并让EF找出要做的事情。
  4. 如果有帮助,请告诉我。

答案 2 :(得分:1)

这里有两件事。我不完全理解他们之间的关系,但我想我可以帮助你。

您需要了解的第一件事是实体框架不能很好地删除一个不完全实现的实例。这就是Include改变您看到的行为的原因。因此,如果您有一个聚合子项列表的实体,则需要在调用delete之前加载这些子项。仅当子实例在内存中时才会在父实例之前删除它们。因此,无论是否包含Include,您都需要在调用Delete之前执行类似的操作。

if (!thing.BrandReference.IsLoaded) thing.BrandReference.Load();

如果你在关系中打电话给Include,那么如果你没有,那么这将无所作为,那么它将确保在你之前实现一切

独特理解的第二件事是inserting a new entity with a relationship to an existing entity is conceptually two different inserts。这是因为关系在实体框架中是一流的。第一个插入是实体本身,第二个是关系。在这种情况下,关系没有单独的表,因此只需要根据需要对数据库进行一次实际插入。但是,如果映射正确,实体框架只能解决这个问题。

那么在这种情况下发生了什么?以下是基于我在这里看到的一些事情的猜测。但我认为情况比我描述的情况复杂得多,所以我认为以下内容在某些细节上是不正确的。但它可能足以帮助您解决实际问题。

  1. 在尝试删除之前,您的实例不完全实现。
  2. 当您尝试删除其他内容时,框架会尝试查看相同的关系。它发现了一些事情,并试图将事情恢复到良好状态,但没有成功。
  3. 然后你试图再次删除,2次重复,只是这次因为数据库限制而更不成功。
  4. 使用Include可以解决1中的问题。