ADO.NET Entity Framework генерирует неожиданные, проблемные вставки - PullRequest
0 голосов
/ 17 февраля 2009

Мне нужна помощь в понимании ADO.NET Entity Framework.

Я пытаюсь представить иерархические данные и управлять ими в элементе управления WPF TreeView с помощью ADO.NET Entity Framework.

Объект ADO.NET Entity Framework с родителями и дочерними элементами http://img14.imageshack.us/img14/7158/thingpi1.gif

Каждая из этих вещей имеет одного родителя и ноль или более детей.

Моя кнопка "Удалить" ...

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

Я наблюдаю за профилировщиком SQL Server во время отладки приложения:

  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 ...

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 с родителем и потомками и связанным объектом http://img25.imageshack.us/img25/3268/thingbrandct4.gif

Я думал, что это не имеет значения, поэтому я не включил его в вопрос выше.

Очевидно, это было причиной моих неожиданных проблем с вставками в мою таблицу Thing.

Но почему? Кто-нибудь может объяснить, почему это происходит? Я хотел бы понять это лучше.

Ответы [ 3 ]

2 голосов
/ 24 февраля 2009

Почему вы снова загружаете родителя при добавлении нового потомка, когда у вас уже есть объект? Хотя это не является проблемой для базы данных, это приведет к несоответствиям на уровне объектов. Вы можете просто использовать существующего родителя следующим образом:

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 голос
/ 23 февраля 2009

Здесь происходят две вещи. Я не совсем понимаю отношения между ними, но думаю, что смогу помочь вам.

Первое, что вам нужно понять, это то, что Entity Framework плохо справляется с удалением менее полностью материализованного экземпляра. Вот почему Включить изменяет поведение, которое вы видите. Поэтому, если у вас есть объект, который объединяет список дочерних элементов, вам нужно загрузить эти дочерние элементы перед вызовом delete. Только если дочерние экземпляры находятся в памяти, они будут удалены перед родительскими. Поэтому, с включением или без включения, вам нужно сделать что-то подобное перед вызовом Delete.

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

Если вы назвали «Включить в отношения», то это ничего не изменит, если вы этого не сделаете, тогда вы убедитесь, что все материализовано перед вами

Второе, что уникально понять, это то, что вставка новой сущности с отношением к существующей сущности - это концептуально две разные вставки . Это является следствием того факта, что отношения являются первоклассными в Entity Framework. Первая вставка - это сама сущность, вторая - это отношение. В этом случае нет отдельной таблицы для отношения, поэтому только одна фактическая вставка в базу данных по мере необходимости. Однако Entity Framework может выяснить это только в том случае, если он сопоставлен правильно.

Так что же происходит в этом случае? Далее следуют мои предположения, основанные на некоторых вещах, которые я здесь вижу. Но я думаю, что ситуация даже сложнее, чем я описываю, поэтому я считаю, что последующее неверно в некоторых деталях. Это может быть достаточно близко, чтобы помочь вам решить актуальную проблему.

  1. У вас был экземпляр, который не был полностью материализован до того, как вы попытались удалить его.
  2. Когда вы пытались удалить что-то еще, фреймворк пытался изучить те же отношения. Он нашел что-то необычное и безуспешно пытался вернуть его в нормальное состояние.
  3. Затем вы попытались удалить снова, 2 повторения, но на этот раз это еще менее успешно из-за ограничений базы данных.
  4. Использование «Включить» устраняет проблему в 1.
1 голос
/ 19 февраля 2009

У меня не было детального взгляда на код, но первое, что нужно учесть, это то, что вам не нужно вызывать DeleteObject на каждом уровне вашей иерархии. EF, как и другие O / RM, отслеживает объекты и их ассоциации для вас.

Скажем, например, что у вас есть отношения родитель-> ребенок 1 .. *. Если вы запросите родительский объект, удалите дочерний объект из родительской коллекции Children и затем вызовете SaveChanges (), EF сгенерирует соответствующие операторы DELETE SQL для вас - вам не нужно это отслеживать самостоятельно.

Итак, лучший способ реализовать ваш сценарий - сделать это:

  1. Запрос иерархии объектов из EF.
  2. Свяжите это со своим пользовательским интерфейсом. Позвольте вашему пользовательскому интерфейсу изменять объекты в памяти по своему усмотрению.
  3. Когда вы закончите, вызовите SaveChanges и дайте EF понять, что делать.

Дайте мне знать, если это поможет.

...