【问题标题】:ADO.NET Entity Framework generates unexpected, problem INSERTsADO.NET Entity Framework 生成意外的问题 INSERT
【发布时间】:2009-02-16 21:58:06
【问题描述】:

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

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

ADO.NET Entity Framework Object with parent and children 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" ... />

Visual Basic...

部分公开课窗口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 Entity Framework Object with parent and children and a related Object http://img25.imageshack.us/img25/3268/thingbrandct4.gif

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

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

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

【问题讨论】:

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


    【解决方案1】:

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

    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
    

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

    【讨论】:

    • 实体框架保留实体身份。虽然每次从数据库加载父级可能效率低下,但它不会“引入不一致”。您可以根据需要多次加载同一个实体,它仍将被视为同一个实体。
    • 我不想做任何级联。我只从数据库中删除一行。应该没有 INSERT。
    • 有一次我有 NewThing.Parent = DirectCast(TreeViewThings.SelectedItem, Thing),但在尝试排除故障时更改了它。它可能可以改回来。
    【解决方案2】:

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

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

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

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

    如果有帮助,请告诉我。

    【讨论】:

    • 我一次只删除一件事。
    • 正是“让 EF 弄清楚该做什么”部分给我带来了麻烦。
    • 要删除子对象,只需将其从对象图中删除即可。同样,要添加新对象,请将其添加到对象图中。 EF 正在跟踪图表,因此当您调用 SaveChanges 时会知道该做什么。或许可以尝试在 UI 之外编写单元测试,以了解其工作原理。
    【解决方案3】:

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

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

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

    如果你在关系上调用了 Include,那么如果你没有调用 Include,那么它不会做任何事情,那么它将确保一切都在你之前实现

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

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

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

    【讨论】:

    • 这个失败了:From t In db.Thing.Include("Children").Include("Brand") Where ...没有第二个 Include() 它按预期工作。似乎不是“未完全物化”,而是“过度物化”。但为什么会这样呢。
    猜你喜欢
    • 2020-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-18
    相关资源
    最近更新 更多