实体框架自我引用多对多有效负载(物料清单BOM)

时间:2010-10-13 16:53:07

标签: entity-framework many-to-many byte-order-mark self-reference payload

我回答这个问题一段时间没有回答,我相信它可能是EF最奇怪的实现,虽然它很实用。这是我以前的帖子:

Entity Framework Self Referencing Hierarchical Many To Many

我决定再次询问额外的关键字Payload以及更清晰的理解。

在Apress出版物中:实体框架4.0食谱:问题解决方案方法,关于第2页的方法2-6。 26的标题是使用有效载荷建模多对多关系。方法2-7的标题是建模自我关联关系。

阅读那将为我的问题提供基础,不同之处在于我有一个自我参考多对多的有效载荷,据我所知,书中或宇宙中的任何地方都没有讨论过。< / p>

简单地说,我有一个带有ID和Type字段的Resource表。我还有一个ResourceHierarchy表,它用作联结或桥接表,因为它有一个由Parent_ID和Child_ID以及复合外键组成的复合主键。因此,资源实体可以充当子资源或父资源,也可以同时充当。

到目前为止,实体框架已经生成了资源实体,但是ResourceHierarchy实体实际上将从EDMX Designer中隐藏,因为在EDMX文件中它只被视为关系而不是实体。

生成的资源实体将具有导航属性,例如Resources和Resources1,我将其重命名为Parents and Children。

所以我可以编写这样的代码:(它没有做任何我只是展示一些例子)

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的螺丝会怎样?所以现在你看到我们在资源实体上需要一个Quantity属性。

快进几个月,我目前正在参与我的项目,并在意识到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

因此我们将有效负载附加到db中的ResourceHierarchy Entity。如果我们从EDMX设计器重新生成模型,则ResourceHierarchy不再是关系,而是现在的实体。如果我选择仅从EDMX设计器中刷新ResourceHierarchy表,我可以在存储模型中看到Required属性,但它在Conceptual或Mapping模型中没有,因为ResourceHierarchy是一个关系。但是,如果我删除Resource表和ResourceHierarchy表并重新生成它们,现在可以使用Required列显示ResourceHierarchy表,它现在是一个实体。

可以使用此设置,但这比仅仅能够访问ResourceHierarchy Relationship并检索Required属性要困难得多。即使ResourceHierarchy EntityType在存储模型中包含Required属性,我也无法在访问AssociationSet后从代码访问Required属性。如果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。

我的想法是,我希望产品由资源组成,这些资源本身由其他资源组成,或者用于组成其他资源,每个资源都需要一定数量的资源。它基本上是物料清单BOM。 EF不支持BOM模型吗?

SQL Server 2008中新的HIERARCHYID功能是否有帮助?

3 个答案:

答案 0 :(得分:2)

所以我最终得到了一个非常优雅的解决方案。

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 Entity导航属性从Resource重命名为Descendant,Resource1重命名为祖先。

之前我可以编写这样的代码:

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,因为后代不需要所需数量的祖先,只有祖先需要指定需要多少后代。

所以这是一个跳跃,但是优雅的一个。

请让我知道你的想法,特别是如果我忽略了一些问题。

答案 1 :(得分:0)

需要注意的事项......

当我们想要为资源添加后代时,我们需要记住,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 to DescendantRoles。

AncestorRoles将转换为ResourceHierarchiesWhereCurrentResourceIsAnAncestor。 DescendantRoles将转换为ResourceHierarchiesWhereCurrentResourceIsADescendant。

所以我们可以这样做:

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

很抱歉要更改命名法,但我认为这有助于了解正在发生的事情。

答案 2 :(得分:0)

MSDN Libary链接:http://msdn.microsoft.com/en-us/library/ms742451.aspx标题为PropertyPath XAML语法,并有一个标题为Source Traversal(绑定到集合的层次结构)的部分

这是我想要使用的HierarchicalDataTemplate:

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

仅显示第一个资源。运行以下代码后,TreeView在TreeView中显示一个ResourceTreeItem。

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 } )

即使我获得了TreeView.ItemsSource的CollectionViewSource并调用了Refresh(),它也不会显示出来。我三重检查了关系,它就在那里。

我认为这是PropertyPath Traversal语法的错误。

解决方案是将TreeParent属性添加到R​​esource partial class声明中并使用3个ValueConverters这是一个很长的故事,但这是因为datacontext自然地从Resource交替到ResourceHierarchy。 RequiredConverter是检查TreeParent并找到Required属性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接收它们的绑定,而且必需的属性就像疯子一样。

所以,道德是如果你需要有效负载,那么EF不适合你。