0

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

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

ADO.NET 实体框架对象与父和子 http://img14.imageshack.us/img14/7158/thingpi1.gif

这些事物中的每一个都有一个单亲和零个或多个孩子。

我的“删除”按钮...

私有子按钮DeleteThing_Click(...)
    db.DeleteObject(DirectCast(TreeViewThings.SelectedItem, Thing))
    db.SaveChanges()
结束子

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

  1. 第一个按钮单击删除就好了。
  2. 第二个按钮单击插入一个重复的父项(但具有空的 GUID 唯一标识符主键),然后执行删除。
  3. 第三个按钮单击失败(违反 PRIMARY KEY 约束),因为它无法插入具有空 GUID 主键的另一行。

意外生成的 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" ... />

视觉基本...

部分公开课窗口1

    Dim db 作为新事物ProjectEntities

    Private Sub Window1_Loaded(...) 处理 MyBase.Loaded
        TreeViewThings.ItemsSource = _
            从 t 在 db.Thing.Include("Children") _
            哪里(t.Parent 什么都不是)_
            选择
    结束子

    私有子 TreeViewThings_Expanded(...)
        将 ExpandedTreeViewItem 调暗为 TreeViewItem = _
            DirectCast(e.OriginalSource,TreeViewItem)
        LoadTreeViewChildren(ExpandedTreeViewItem)
    结束子

    Sub LoadTreeViewChildren(ByRef Parent As TreeViewItem)
        Dim ParentId As Guid = DirectCast(Parent.DataContext, Thing).Id
        将 ChildThings 调暗为 System.Linq.IQueryable(Of Thing)
        ChildThings = From t In db.Thing.Include("Children") _
                      其中 t.Parent.Id = ParentId _
                      选择
        Parent.ItemsSource = ChildThings
    结束子

    私有子按钮AddThing_Click(...)
        将新事物视为新事物
        NewThing.Id = Guid.NewGuid()
        将 ParentId 调暗为 Guid = _
            DirectCast(TreeViewThings.SelectedItem, Thing).Id
        NewThing.Parent =(从 t 在 db.Thing _
                           其中 t.Id = ParentId _
                           选择 t).First
        ...
        db.AddToThing(新事物)
        db.SaveChanges()
        TreeViewThings.UpdateLayout()
    结束子

    私有子按钮DeleteThing_Click(...)
        db.DeleteObject(DirectCast(TreeViewThings.SelectedItem, Thing))
        db.SaveChanges()
    结束子

    ...

结束类

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


更新:

我取得了突破。但是,我还是无法解释。

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

而不是使用 Linq,例如:

从 t 在 db.Thing.Include("Children") 哪里...

我一直在使用 Linq,例如:

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

您会看到,My Thing 实体与另一个 Brand 实体相关。

ADO.NET 实体框架对象与父子和相关对象 http://img25.imageshack.us/img25/3268/thingbrandct4.gif

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

显然,这是在我的 Thing 表中插入意外问题的原因。

但为什么?谁能解释为什么会这样?我想更好地理解它。

4

3 回答 3

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

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

于 2009-02-24T11:17:54.997 回答
1

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

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

因此,实现您的方案的更好方法是执行以下操作:

  1. 从 EF 中查询对象层次结构。
  2. 将其绑定到您的 UI。让您的 UI 在您认为合适的时候修改内存中的对象。
  3. 完成后,调用 SaveChanges 并让 EF 弄清楚要做什么。

让我知道这是否有帮助。

于 2009-02-19T20:54:15.603 回答
1

这里有两件事。我不完全了解他们之间的关系,但我想我可以让你走上你的路。

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

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

如果你在关系上调用了 Include ,那么如果你没有调用,这将无济于事,那么它将确保一切都在你之前实现

unique 理解的第二件事是插入一个与现有实体有关系的新实体在概念上是两个不同的插入。这是实体框架中关系是一流的事实的结果。第一个插入是实体本身,第二个是关系。在这种情况下,关系没有单独的表,因此只有一个根据需要实际插入到数据库中。但是,实体框架只有在正确映射的情况下才能解决这个问题。

那么在这种情况下发生了什么?以下是我根据我在这里看到的一些事情的推测。但我认为情况比我描述的还要复杂,所以我认为下面的一些细节是不正确的。不过,它可能足以帮助您解决实际问题。

  1. 在您尝试删除它之前,您有一个未完全实现的实例。
  2. 当您尝试删除其他内容时,框架会尝试查看相同的关系。它发现事情不正常,并试图将事情恢复到良好状态,但没有成功。
  3. 然后你再次尝试删除,重复2次,只是这次更不成功,由于数据库限制。
  4. 使用 Include 修复了 1 中的问题。
于 2009-02-23T15:26:04.313 回答