2

不久前我问了这个问题而没有答案,我相信这可能是 EF 最奇怪的实现,尽管它非常实用。这是我以前的帖子:

实体框架自引用分层多对多

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

在 Apress 出版物中:Entity Framework 4.0 Recipes: A Problem-Solution Approach,第 2-6 页的配方。26 题为使用有效负载建模多对多关系。配方 2-7 的标题是为自指关系建模。

阅读这将为您提供我的问题的基础,不同之处在于我有一个带有有效负载的自我参照多对多,据我所知,这本书或宇宙中的任何地方都没有讨论过。

简单地说,我有一个包含 ID 和类型字段的资源表。我还有一个 ResourceHierarchy 表,它用作联结表或桥表,因为它有一个由 Parent_ID 和 Child_ID 组成的复合主键和一个复合外键。因此,资源实体可以用作子资源或父资源或两者兼而有之。

到目前为止,实体框架将生成资源实体,但 ResourceHierarchy 实体实际上对 EDMX 设计器是隐藏的,因为在 EDMX 文件中,它仅被视为关系而不是实体。

生成的资源实体将具有诸如 Resources 和 Resources1 之类的导航属性,我将其重命名为 Parent 和 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?

这只能通过向 ResourceHierarchy 表添加另一个数量(int)列来完成,我们将其称为“必需”,因此它看起来像:

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

因此,我们已将有效负载附加到 db 中的 ResourceHierarchy 实体。如果我们从 EDMX 设计器重新生成模型,ResourceHierarchy 不再是关系,而是现在是实体。如果我选择仅从 EDMX 设计器刷新 ResourceHierarchy 表,我可以在存储模型中看到 Required 属性,但它不在概念模型或映射模型中的任何位置,因为 ResourceHierarchy 将是一种关系。但是,如果我删除 Resource 表和 ResourceHierarchy 表并重新生成它们,则 ResourceHierarchy 表现在在Required 列中可见并且它现在是一个实体。

可以使用此设置,但它比简单地访问 ResourceHierarchy 关系和检索所需属性要困难得多。即使 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 框架。

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

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

4

3 回答 3

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 重命名为 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);

但是检查一下,我现在可以像这样访问所需的属性:

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

可以将Required 字段重命名为RequiredDescendants,因为后代不需要一定数量的祖先,只有祖先需要指定需要多少后代。

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

请让我知道你的想法,特别是如果我忽略了一个陷阱或其他东西。

于 2010-10-13T20:56:36.700 回答
0

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

这是我要使用的 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 语法的错误。

解决方案是在 Resource 部分类声明中添加 TreeParent 属性并使用 3 个 ValueConverters,这说来话长,但这是因为数据上下文自然地从 Resource 交替到 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 不适合你。

于 2010-10-14T20:55:01.433 回答
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 重命名为 DescendantRoles。

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

所以我们可以这样做:

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

很抱歉这么多地改变了命名法,但我认为这有助于理解发生了什么。

于 2010-10-14T14:50:51.040 回答