2

私はしばらく前にこの質問をしましたが、答えはありませんでした。非常に実用的ですが、これは EF の最も奇妙な実装であると思います。ここに私の以前の投稿があります:

Entity Framework 自己参照階層多対多

追加のキーワード Payload とより明確な理解で再度質問することにしました。

Apress の出版物: Entity Framework 4.0 Recipes: A Problem-Solution Approach、ページのレシピ 2-6。26 のタイトルは、ペイロードを使用した多対多の関係のモデル化です。レシピ 2-7 のタイトルは、自己参照関係のモデル化です。

それを読むと、私の問題が何であるかの基礎が得られます。違いは、私の知る限り、本や宇宙のどこにも議論されていない、ペイロードを使用した自己参照多対多があることです。

簡単に言うと、ID フィールドと Type フィールドを持つ Resource テーブルがあります。また、Parent_ID と Child_ID で構成される複合主キーと複合外部キーを持つという点で、ジャンクションまたはブリッジ テーブルとして機能する ResourceHierarchy テーブルもあります。したがって、リソース エンティティは、子リソースまたは親リソース、またはその両方として機能できます。

ここまでで、Entity Framework は Resource Entity を生成しますが、ResourceHierarchy Entity は、EDMX ファイルではエンティティではなく関係のみとして扱われるため、実際には EDMX Designer から隠されます。

生成された Resource Entity には、Resources や Resources1 などのナビゲーション プロパティがあり、それらの名前を Parents と 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);
}

他の 2 つのリソースによって共有されているリソースがあるとします。他の 2 つのリソースは、そのリソースの親になります。上記のリソースは、それぞれの親の唯一の子でもあります。はい、リソースは 3 つ以上の「親」を持つことができます。必要に応じて父親が 2 人でもかまいませんが、先祖は子供を共有しますか? 私の時計ではありません。とにかく... この時点から意味をなすためには、現実世界のシナリオからこれを考える必要があります。

開始するためのコードを次に示します。

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

したがって、ネジのある 2 つのウィジェットがあります。WidgetA と WidgetB は、Web サイトに製品としてリストされています。WidgetAが売れたらWidgetBのネジはどうなるの?これで、Resource Entity に Quantity プロパティが必要であることがわかりました。

何ヶ月も早送りして、私が現在プロジェクトに参加しており、EFがいかに制限されているかを理解した後、胎児の位置を占めています.

この部分はもう少し複雑になります。もしも

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

子の 2 つを親 1 に、子の 2 つを親 2 に割り当てることができるようにするには、どうすればそれを知ることができるでしょうか。

これは、ResourceHierarchy テーブルに「Required」と呼ばれる別の quantity(int) 列を追加することによってのみ実行できるため、次のようになります。

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

そのため、ペイロードをデータベースの ResourceHierarchy エンティティにアタッチしました。EDMX デザイナーからモデルを再生成すると、ResourceHierarchy は関係ではなくなり、エンティティになります。代わりに、EDMX デザイナーから ResourceHierarchy テーブルのみを更新することを選択した場合、ストレージ モデルに Required プロパティが表示されますが、ResourceHierarchy はリレーションシップであるため、概念モデルまたはマッピング モデルのどこにもありません。ただし、Resource テーブルと ResourceHierarchy テーブルを削除して再生成すると、ResourceHierarchy テーブルが Required 列で表示され、Entity になります。

この設定で作業することは可能ですが、単に 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 機能は、ひょっとしたら役に立ちますか?

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 エンティティ ナビゲーション プロパティの名前を 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;

子孫は必要な量の祖先を必要とせず、必要な子孫の数を指定する必要があるのは祖先のみであるため、Required フィールドの名前を RequiredDescendants に変更することができます。

ホップですが、優雅なホップです。

特に私が落とし穴や何かを見落としている場合は、あなたの考えを教えてください.

于 2010-10-13T20:56:36.700 に答える
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 に答える
0

MSDN ライブラリ リンク : http://msdn.microsoft.com/en-us/library/ms742451.aspxは、PropertyPath XAML Syntax というタイトルで、Source Traversal (コレクションの階層へのバインド) というタイトルのセクションがあります。

これは、使用したい HierarchicalDataTemplate です。

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

最初のリソースのみが表示されます。次のコードを実行すると、TreeView に ResourceTreeItem が 1 つ表示されます。

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 プロパティを Resource 部分クラス宣言に追加し、3 つの ValueConverters を利用することでした。これは長い話ですが、データ コンテキストが 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 からバインディングを受け取っており、Required プロパティが狂ったように動作していることがわかります。

したがって、教訓は、ペイロードが必要な場合は、EF が適していない可能性があるということです。

于 2010-10-14T20:55:01.433 に答える