ADO.NET 实体框架生成意外的问题插入
我需要一些帮助来理解 ADO.NET 实体框架。
我正在尝试使用 ADO.NET 实体框架在 WPF TreeView 控件中表示和操作分层数据。
具有父级和子级的 ADO.NET 实体框架对象 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:
- 单击第一个按钮即可删除。
- 单击第二个按钮将插入重复的父项(但具有空 GUID uniqueidentifier 主键),然后执行删除。
- 第三个按钮单击失败(违反 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...
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 实体与另一个 Brand 实体相关。
具有父级和子级的 ADO.NET 实体框架对象以及相关对象 http://img25。 imageshack.us/img25/3268/thingbrandct4.gif
我认为这是无关紧要的,所以我没有将其包含在上面的问题中。
显然,这是我在“事物”表中插入意外问题的原因。
但为什么? 谁能解释为什么会发生这种情况? 我想更好地理解它。
I need some help understanding the ADO.NET Entity Framework.
I'm trying to represent and manipulate hierarchical data in a WPF TreeView control with the ADO.NET Entity Framework.
Each of these Things has a single parent and zero or more children.
My "delete" button...
Private Sub ButtonDeleteThing_Click(...) db.DeleteObject(DirectCast(TreeViewThings.SelectedItem, Thing)) db.SaveChanges() End Sub
I'm monitoring the SQL Server Profiler while I debug my application:
- The first button click deletes just fine.
- The second button click inserts a duplicate parent (but with empty GUID uniqueidentifier primary key) and then performs the delete.
- The third button click fails (Violation of PRIMARY KEY constraint) because it cannot insert another row with an empty GUID primary key.
The unexpected, generated T-SQL duplication...
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', ...
But, it's not just deletes. My "add" button has similar behavior with unexpected inserts. It follows the same pattern.
This makes me think there's a more fundamental problem with how I'm binding these entity classes to the WPF TreeView or with my data model itself.
Here's the relevant code...
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
What am I doing wrong? Why is it generating these strange inserts?
Update:
I've made a breakthrough. But, I still can't explain it.
I'd gotten rid of the cause when I simplified my code for this question.
Rather than using Linq such as:
From t In db.Thing.Include("Children") Where ...
I've been using Linq such as:
From t In db.Thing.Include("Children").Include("Brand") Where ...
You see, My Thing entity is related to another Brand entity.
I thought it was irrelevant, so I didn't include it in the question above.
Apparently this was the cause of my unexpected, problem inserts in my Thing table.
But, why? Can anyone explain why this was happening? I'd like to understand it better.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

发布评论
评论(3)
我还没有详细查看代码,但首先要考虑的是,您不需要在层次结构的每个级别调用 DeleteObject。 与其他 O/RM 一样,EF 会为您跟踪对象及其关联。
例如,假设您有父->子 1..* 关系。 如果您查询父级,从父级 Children 集合中删除子对象,然后调用 SaveChanges(),EF 将为您生成适当的 DELETE SQL 语句 - 您不需要自己跟踪这一点。
因此,实现您的场景的更好方法是执行以下操作:
- 从 EF 中查询对象层次结构。
- 将其绑定到您的 UI。 让您的 UI 根据您的需要修改内存中的对象。
- 完成后,调用 SaveChanges 并让 EF 确定要做什么。
让我知道这是否有帮助。
这里发生了两件事。 我不太明白他们之间的关系,但我想我可以让你上路。
您需要了解的第一件事是实体框架不能很好地处理删除未完全具体化的实例。 这就是为什么 Include 会改变您所看到的行为。 因此,如果您有一个聚合子级列表的实体,则需要在调用删除之前加载这些子级。 只有当子实例在内存中时,它们才会在父实例之前被删除。 因此,无论是否包含 Include,您都需要在调用 Delete 之前执行类似的操作。
if (!thing.BrandReference.IsLoaded) thing.BrandReference.Load();
如果您已经在关系上调用了 Include,那么这将不会执行任何操作(如果您没有这样做),那么它将确保一切都在您之前实现。
唯一需要理解的第二件事是 插入一个新实体与现有实体的关系在概念上是两个不同的插入。 这是因为关系在实体框架中是一流的。 第一个插入是实体本身,第二个插入是关系。 在这种情况下,没有单独的关系表,因此只有根据需要向数据库进行一次实际插入。 然而,实体框架只有在正确映射的情况下才能解决这个问题。
那么这个案例到底是怎么回事呢? 以下是我根据我所看到的一些事情进行的推测。 但我认为情况比我描述的还要复杂,所以我认为下面的一些细节是不正确的。 不过,它可能足以帮助您解决实际问题。
- 在您尝试删除一个实例之前,该实例尚未完全实现。
- 当您尝试删除其他内容时,框架会尝试查看相同的关系。 它发现事情不正常,并试图让事情恢复到良好状态,但没有成功。
- 然后你又尝试删除,重复了2次,只是这次由于数据库限制,更不成功。
- 使用 Include 修复了 1 中的问题。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
当您已经拥有该对象时,为什么在添加新子项时再次加载父项? 虽然这对于数据库来说不是问题,但它会导致对象级别的不一致。 您可以像这样使用现有的父级:
关于删除:您是否在数据库中指定了级联删除?
Why are you loading the parent again when adding a new child, when you already have the object? While this is no problem for the database, it will introduce inconsistencies on the object level. You can just use the existing parent like this:
About the delete: have you specified cascading deletes in the database?