我需要一些帮助来理解ADO.NET实体框架。
我正在尝试使用ADO.NET实体框架在WPF TreeView控件中表示和操作分层数据。
这些事物中的每一个都有一个父母,零个或多个孩子。
我的“删除”按钮......
Private Sub ButtonDeleteThing_Click(...) db.DeleteObject(DirectCast(TreeViewThings.SelectedItem, Thing)) db.SaveChanges() End Sub
我正在调试我的应用程序时监视SQL Server Profiler:
意外的,生成的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实体与另一个品牌实体有关。
我认为这是无关紧要的,所以我没有把它包含在上面的问题中。
显然这是我意想不到的问题的原因,在我的Thing表中插入了问题。
但是,为什么?任何人都可以解释为什么会这样吗?我想更好地理解它。
答案 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语句 - 您不需要自己跟踪它。
因此,实现您的方案的更好方法是:
如果有帮助,请告诉我。
答案 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。这是因为关系在实体框架中是一流的。第一个插入是实体本身,第二个是关系。在这种情况下,关系没有单独的表,因此只需要根据需要对数据库进行一次实际插入。但是,如果映射正确,实体框架只能解决这个问题。
那么在这种情况下发生了什么?以下是基于我在这里看到的一些事情的猜测。但我认为情况比我描述的情况复杂得多,所以我认为以下内容在某些细节上是不正确的。但它可能足以帮助您解决实际问题。