web-dev-qa-db-ja.com

異種の関連付け-多くのクラスの1つに関連付ける必要があるオブジェクトのデータモデリング

ClassAClassBClassCの3つのクラスがあるとします。ここで、クラスMessageがあるとします。 one-to-manyこれら3つのクラスのそれぞれとメッセージの関係。

目標は、MessageClassAClassB、またはClassCオブジェクトのいずれかに「属している」必要があることです。 ClassA/ClassB/ClassCオブジェクトは、0個または多数Messagesを持つことができます。 Messageクラスは3つのクラスのいずれでも同じです(つまり、3つのクラスA、B、Cのそれぞれに異なるクラスのメッセージがあってはなりません)。

次に、リレーショナルDBでのデータモデリングについて考えます(また、ORMを使用しています(この場合はEntity Framework Code First))。これらの3つのクラスのそれぞれと、Messageクラスをテーブルに追加します。

しかし、その後、問題は私が上で説明した関係を適用しようとしています。これにはいくつかの方法があります。

  • メッセージテーブルに、null許容の3つの外部キー(ClassAIDClassBIDClassCID)を追加します。例えばメッセージがClassAオブジェクトに属している場合、ClassAIDはそのオブジェクトのIDを値として持ち、ClassBIDClassCIDはnullになります。これにより、Messageオブジェクトの値がこれらのFKの1つだけにあることを確認するために、制約/トリガーを作成する必要があります。また、メッセージがどのオブジェクトに属しているかを知りたいときはいつでも、ビジネスロジックレイヤーとプレゼンテーションレイヤーで少し醜くなります。MessageObjectには、ClassA、ClassB、およびClassCオブジェクトへのnull可能な参照があり、チェックする必要があります。 UIに表示する前に(メッセージからそのオブジェクトへのリンクなど)。

Nullable FK's

  • 継承を実装し、ClassAClassBClassCを一般化します。メッセージには、一般化クラスのIDのFKがあります。これにより複雑さが増し、メッセージがどのClassA/B/Cに関連付けられているかを簡単に判断するという問題は実際には解決されません。

Inheritance

  • NULL可能FKを介して3つのテーブルをメッセージ1に接続するClassToMessageテーブル(リンクテーブルと同様)を追加します。前の2つを組み合わせたようなものですが、同じ理由でまだ正確な解決策ではありません。

Link Table to Message

Entiy Frameworkアプローチを念頭に置いて、望ましいシナリオを達成するためのエレガントな方法があるかどうかを知りたいのですが。前もって感謝します。

EDIT 1:

Messageオブジェクトを、オブジェクトのツリーのような構造のコメント(またはファイル)として想像してください(ClassAオブジェクトにはClassBを含めることができ、ClassBにはClassC 's ...を含めることができます)。これらすべてのクラスのオブジェクトに、0個または多くのコメントを付ける必要があります。すべてのコメントに同じコメントテーブルが必要です(コメントがClassAまたはClassB...インスタンスに属しているかどうかは関係ありません)。また、特定のオブジェクトまたはコメントが属するオブジェクトのコメントを簡単に見つけられるようにする必要もあります。

編集2:

他の2つの選択肢:

  • ClassAMessage、ClassBMessageを使用して、階層ごとのテーブル(TPH)を実装します。それでも問題は解決しません...

Table Per Hierarchy

  • メッセージには、それを所有するオブジェクトのIDとオブジェクトのタイプ(クラスA、クラスBなど)が格納されます。実際の関係はありません。サービスレイヤーまたはビジネスロジックが残りを行います(問題ありません)。

No Relationship

StackOverflowでこの問題に対処しているのを見てきましたが、適切な解決策が見つからないようです。

編集3:

私は代替案#4(階層ごとのテーブル/クラステーブルの継承)を追求しており、これをより具体的なS.O.に進化させました。質問: Entity Framework 6 Code First-ツリー構造に関するコメント

7
user1987392

「階層ごとのテーブル」と呼ばれるものは、マーティンファウラーなどが「クラステーブルの継承」と呼んでいるものに似ています。 図を参照 。継承を実装するのではなく、これを模倣継承と呼びます。

クラステーブル継承のいくつかのケースで有利に使用できる1つの手法は、「共有主キー」と呼ばれます。 SEの タグwiki を参照してください。

共有主キーの場合の利点は、共有主キーを参照するFK参照が、ジェネリッククラステーブルのエントリと適切な特殊クラステーブルのエントリの両方を参照できることです。これは非常に便利で、共有主キーを実装するために必要な追加のプログラミングを正当化できます。

1
Walter Mitty

ClassAとClassBが何を表すかが異なります本当に-一般的な名前は、適切な設計上の決定を行うのに役立ちません。たとえば、それらが共通の基本クラスでモデル化できる、またはモデル化すべきものであり、実際の「is-a」関係がある場合は、そのルートに進みます。

ClassAとClassBにこれらのメッセージ以外の共通点がない場合、継承はおそらく最善のアプローチではありません。この場合、ソリューション1と3の両方が可能です。どちらが適切かは、データベースがどの程度「防水」されているか、およびどのようなアクセスパターンとクエリが期待されるかによって異なります。

1
Doc Brown

編集の最後のオプションを使用しますが、少し変更します。

http://yuml.me/adbd9b9e.png

これでは、具象クラスの変更を抽象化しました。

ただし、データベースでは、参照している具象クラスの種類とその具象クラスのIDを保存する列挙型の値を保存しています。この情報はファクトリーに渡すことができます。ファクトリーは、以前の列挙型とIDに基づく具体的なクラスのいずれかであるgenericClassを返すメソッドを提供する必要があります。

- Message
   + int Id
   + int TypeEnum
   + int ClassId

- Class
   + int Id
   + int TypeEnum

Class.TypeEnumは、Message.TypeEnumの "緩い"制約です​​。

これにより、ヌル値を許容する外部キーのために列を追加する必要を心配する必要がなくなります。

私が指摘したように、Message-classとClassA、ClassB、... ClassXの間のデータベース制約(外部キー)は使用できず、手動で処理する必要があることを考慮する必要があります。

1
Allmighty