Entity Framework Самостоятельная ссылка «многие ко многим» с полезной нагрузкой (спецификация спецификаций) - PullRequest
2 голосов
/ 13 октября 2010

Я задал этот вопрос некоторое время назад без ответа, я думаю, что это может быть самой странной реализацией для EF, хотя это довольно практично.Вот мой предыдущий пост:

Entity Framework Самостоятельная ссылка на иерархию «Многие ко многим»

Я решил еще раз спросить с дополнительным ключевым словом «Полезная нагрузка» и более ясное понимание.

В публикации Apress: Entity Framework 4.0 Рецепты: подход к решению проблем, рецепт 2-6 на стр.26 называется Моделирование отношений «многие ко многим» с полезной нагрузкой.Рецепт 2-7 называется «Моделирование само-ссылочных отношений».

Чтение, которое дало бы вам основание для моей проблемы, разница в том, что у меня есть само-ссылочный «многие-ко-многим» с полезной нагрузкой, котораяНасколько мне известно, это не обсуждается ни в книге, ни где-либо во вселенной.

Проще говоря, у меня есть таблица ресурсов с полями ID и Type.У меня также есть таблица ResourceHierarchy, которая служит таблицей соединений или мостов в том смысле, что она имеет составной первичный ключ, состоящий из Parent_ID и Child_ID, и составной внешний ключ.Таким образом, сущность ресурса может служить либо дочерним ресурсом, либо родительским ресурсом, либо ОБА.

К настоящему времени Entity Framework сгенерировал бы ресурсный объект, но объект ResourceHierarchy фактически будет скрыт от дизайнера EDMX, поскольку в EDMXфайл рассматривается как только отношение, а не как сущность.

Сгенерированный ресурсный объект будет иметь свойства навигации, такие как «Ресурсы и ресурсы1», которые я переименовал в «Родители и дети».

Поэтому я могу писать коднапример: (ничего не происходит, я просто показываю несколько примеров)

List<Resource> listResources = Context.Resouces.ToList()
foreach (Resource resc in listResources)
{
List<Resource> listParents = resc.Parents.ToList()
List<Resource> listChildren = resc.Children.ToList()
foreach (Resource parent in listParents)
{
Console.WriteLine(parent.Type);
}
foreach (Resource child in listChildren)
{
Console.WriteLine(child.Type);
}
resc.Children.Add(new Resource());
Console.WriteLine(resc.Parents.First().Children.First().Type);
}

Допустим, у меня есть ресурс, который используется двумя другими ресурсами.Два других ресурса будут Родителями указанного Ресурса.Указанный Ресурс также является единственным Ребенком каждого из его Родителей.Да, Ресурс может иметь трех или более «Родителей», даже двух пап, если хотите, но будут ли предки делить ребенка?Не на моих часах.Так или иначе ... мы должны думать об этом из сценария реального мира, чтобы это имело смысл с этого момента.

Вот код, который поможет нам начать:

Resource parent1 = new Resource();
Resource parent2 = new Resource();
Resource child = new Resource();

parent1.Type = "WidgetA";
parent2.Type = "WidgetB";
child.Type = "1/4 Screw"

parent1.Children.Add(child);
parent2.Children.Add(child);

Product product1 = new Product();
Product product2 = new Product();

product1.Resources.Add(parent1);
product2.Resources.Add(parent2);

Итак, у нас есть два виджета с винтом.WidgetA и WidgetB перечислены как продукты на веб-сайте.Что если WidgetA продаст, что будет с винтом WidgetB?Итак, теперь вы видите, что нам нужно свойство «Количество» в Ресурсном объекте.

Ускорьте много месяцев назад, пока я нахожусь в моем проекте, и принимаю положение плода после понимания того, насколько ограничен EF.

Эта часть становится немного сложнее.Если

child.Quantity = 4
parent1.Quantity = 1
parent2.Quantity = 1

Как мы узнаем или настроим его так, чтобы мы могли назначить 2 дочерних для parent1 и 2 дочерних для parent2?

Это можно сделать только путем добавлениядругой столбец количества (int), который мы назовем «Обязательным» для таблицы ResourceHierarchy, чтобы он выглядел следующим образом:

Parent_ID int not null,
Child_ID int not null,
Required int not null default 1

Итак, мы прикрепили полезную нагрузку к объекту ResourceHierarchy в БД.Если мы воссоздаем модель из конструктора EDMX, ResourceHierarchy больше не является отношением, а теперь является сущностью.Если я выберу вместо этого только Обновить таблицу ResourceHierarchy из дизайнера EDMX, я увижу свойство Required в модели хранения, но в концептуальных моделях или в моделях сопоставления это не так, поскольку ResourceHierarchy будет отношением.Однако, если я удаляю таблицу Resource и таблицу ResourceHierarchy и заново создаю их, таблица ResourceHierarchy теперь видна со столбцом Required, и теперь она является Entity.

С этой настройкой можно работать, но это гораздо сложнее, чем просто получить доступ к ResourceHierarchy Relationship и получить свойство Required.Даже если ResourceHierarchy EntityType включает в себя свойство Required в модели хранения, я не могу получить доступ к свойству Required из кода после доступа к AssociationSet.Если таблица ResourceHierarchy является отношением в EF, она выглядит в модели хранения следующим образом.

<EntityType Name="ResourceHierarchy">
          <Key>
            <PropertyRef Name="Parent_ID" />
            <PropertyRef Name="Child_ID" />
          </Key>
          <Property Name="Parent_ID" Type="int" Nullable="false" />
          <Property Name="Child_ID" Type="int" Nullable="false" />
          <Property Name="Required" Type="int" Nullable="false" />
</EntityType>

Если я пытаюсь объединить сгенерированные файлы EDMX, я получаю сообщение об ошибке, сообщающее, что ResourceHierarchy может быть либоСущность или отношение, но не оба.

Это называется «многие ко многим с полезной нагрузкой».Попытка реализовать это с помощью Самореференциальной Иерархии - кошмар в EF.Я работаю с VS2010, SQL 2008 и .NET 4.0 Framework.

Идея заключается в том, что я хочу, чтобы продукты были составлены из ресурсов, которые сами состоят из других ресурсов или служат для созданиядругие ресурсы, и каждому требуется определенное количество ресурсов.Это в основном спецификации материалов.EF не поддерживает модель спецификации?

Поможет ли новая функция HIERARCHYID в SQL Server 2008 случайно?

Ответы [ 3 ]

2 голосов
/ 14 октября 2010

Итак, я получил удивительно изящное решение.

CREATE TABLE Resource
(
ID INT NOT NULL,
Type VARCHAR(25) NOT NULL
)

ALTER TABLE Resource
ADD CONSTRAINT PK_Resource PRIMARY KEY (ID)

CREATE TABLE ResourceHierarchy
(
Ancestor_ID INT NOT NULL,
Descendant_ID INT NOT NULL,
Required INT NOT NULL DEFAULT 1
)

ALTER TABLE ResourceHierarchy
ADD CONSTRAINT PK_ResourceHierarchy PRIMARY KEY (Ancestor_ID, Descendant_ID)
ALTER TABLE ResourceHierarchy
ADD CONSTRAINT FK_Ancestors FOREIGN KEY (Ancestor_ID) REFERENCES Resource (ID)
ALTER TABLE ResourceHierarchy
ADD CONSTRAINT FK_Descendants FOREIGN KEY (Descendant_ID) REFERENCES Resource (ID)

Когда генерировался EDMX, я переименовал свойства навигации объекта ресурса из ResourceHierarchy в DescendantRelationships и ResourceHierarchy1 в AncestorRelationships. Затем я переименовал свойства навигации объекта ResourceHierarchy из Resource в Descendant и Resource1 в Ancestor.

Тогда как раньше я мог написать такой код:

Resource resource = new Resource();
resource.Descendants.Add(new Resource());
foreach (Resource descendant in resource.descendants)
{
descendant.Type = "Brawr";
List<Resource> ancestors = descendant.Ancestors.ToList();
}

Конечно, этот подход не позволил мне получить доступ к свойству Required.

Теперь я должен сделать следующее:

Resource ancestor = new Resource();
Resource descendant = new Resource();

ResourceHierarchy rh = new ResourceHierarchy { Ancestor = ancestor, Descendant = descendant, Required = 1 };

ancestor.DescendantRelationships.Add(rh);

Но посмотрите, теперь я могу получить свойство Required:

int req = ancestor.DescendantRelationships.First().Required;

Можно переименовать поле Обязательное в RequiredDescendants, поскольку потомкам не нужно требуемое количество предков, только предки должны указывать, сколько требуется потомков.

Так что это хоп, но изящный.

Пожалуйста, дайте мне знать ваши мысли, особенно если я пропустил ошибку или что-то в этом роде.

0 голосов
/ 15 октября 2010

Ссылка MSDN Libary: http://msdn.microsoft.com/en-us/library/ms742451.aspx называется Синтаксис XAML PropertyPath и имеет раздел с заголовком Обход источника (Привязка к иерархиям коллекций)

Это шаблон иерархии данных, который я хочу использовать:

<HierarchicalDataTemplate DataType="{x:Type Inventory:Resource}" ItemsSource="{Binding Path=AncestorRoles/Descendant}">
<CustomControls:ResourceTreeItem Type="{Binding ResourceType.Type}"/>
</HierarchicalDataTemplate>

Отображается только первый ресурс.После запуска следующего кода TreeView показывает один ResourceTreeItem в TreeView.

ObservableCollection<Resource> Resources = new ObservableCollection<Resource>
Resources.Add(new Resource { ResourceType.Type = "WidgetA" });
MyTreeView.ItemsSource = Resources;

Так что это работает.Однако, когда я запускаю следующий код, TreeView не обновляется.

Resource resource = MyTreeView.Items[0] as Resource;
resource.AncestorRoles.Add( new ResourceHierarchy { Descendant = new Resource { ResourceType = "Screw" }, Required = 1 } )

Даже если я получаю CollectionViewSource TreeView.ItemsSource и вызываю Refresh (), он не отображается.Я трижды проверил отношения, и все там.

Я думаю, что это ошибка синтаксиса PropertyPath Traversal.

Решением было добавить свойство TreeParent в объявление частичного класса Resource и использовать 3ValueConverters - это длинная история, но это потому, что текст данных естественным образом чередуется с ResourceHierarchy.RequiredConverter - это тот, который проверяет TreeParent и находит обязательное свойство Payload.

class ValidatorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            Resource resource = value as Resource;
            ResourceHierarchy rh = value as ResourceHierarchy;
            if (resource != null)
            {
                value = resource;
            }
            else if (rh != null)
            {
                value = rh.Descendant;
            }

            return value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    class ResourceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            Resource resource = value as Resource;
            ResourceHierarchy hierarchy = value as ResourceHierarchy;

            if (resource != null)
            {
                if (resource.AncestorRoles.Count > 0)
                {
                    value = resource.AncestorRoles;
                }
                else
                {
                    value = resource;
                }
            }
            else if (hierarchy != null)
            {
                value = hierarchy.Descendant.AncestorRoles;
            }

            return value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    class RequiredConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            Resource resource = (Resource)value;
            Resource parent = ((Resource)value).TreeParent as Resource;
            if (parent != null)
            {
                value = parent.AncestorRoles.Where(p => p.Descendant == resource).First().Required;
            }
            else
            {
                value = 0;
            }

            return value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

А вот и последний HierarchicalDataTemplate:

<HierarchicalDataTemplate DataType="{x:Type Inventory:Resource}" ItemsSource="{Binding Path=., Converter={StaticResource resourceconv}}">
<StackPanel DataContext="{Binding Path=., Converter={StaticResource validatorconv}}">
<CustomControls:ResourceTreeItem Required="{Binding Path=., Converter={StaticResource requiredconv}}" Free="{Binding Free}" OnHand="{Binding OnHand, Mode=TwoWay}" Type="{Binding ResourceType.Type}"/>
</StackPanel>
</HierarchicalDataTemplate>

StackPanel служит только для добавления другого слоя DataContext,Я оставил в свойствах Free, OnHand и Type, чтобы вы могли видеть, что 3 свойства получают свою привязку от StackPanels DataContext, а свойство Required делает его похожим на сумасшедшего.

Так что если вынужна полезная нагрузка, может быть, EF не для вас.

0 голосов
/ 14 октября 2010

На что следует обратить внимание ...

Когда мы хотим добавить потомка к ресурсу, нам нужно помнить, что DescendantRelationships дает нам иерархию, в которой ссылочный ресурс действует как потомок для других ресурсов.

Итак, чтобы добавить потомка к ресурсу, мы должны сделать следующее:

Resource resource = new Resource { Type = "WidgetA" };
Resource descendant = new Resource { Type = "Screw" };
resource.AncestorRelationships.Add(new ResourceHierarchy { Descendant = descendant, Required = 1 };

Конечно, все зависит от того, как вы называете свои свойства навигации, я просто говорю, что будьте осторожны,AncestorRelationships будет свойством перехода к навигации для добавления потомков и наоборот.Лучше всего переименовать AncestorRelationships в AncestorRoles, а DescendantRelationships в DescendantRoles.

AncestorRoles будет переводиться в ResourceHierarchiesWhereCurrentResourceIsAnAncestor.DescendantRoles преобразуются в ResourceHierarchiesWhereCurrentResourceIsADescendant.

Итак, мы могли бы сделать:

// print descendant types
foreach (ResourceHierarchy rh in resource.AncestorRoles)
{
Console.WriteLine(rh.Descendant.ResourceType.Type);
}

Извините, что так сильно изменил номенклатуру, но я думаю, что это помогает понять, что происходит.

...