データベース内のポリモーフィズムにどのように対処しますか?

StackOverflow https://stackoverflow.com/questions/45621

  •  09-06-2019
  •  | 
  •  

質問

私は持っている Person, SpecialPerson, 、 そして User. Person そして SpecialPerson 彼らは単なる人間です。サイト上ではユーザー名やパスワードはありませんが、記録保持のためにデータベースに保存されます。ユーザーはすべて同じデータを持っています Person そして潜在的に SpecialPerson, 、サイトに登録されているユーザー名とパスワードとともに。


この問題にどう対処しますか?いただけますか Person 個人に共通するすべてのデータを保存し、キーを使用してデータを検索するテーブル SpecialPerson (相手が特別な人の場合)とユーザー(相手がユーザーの場合)、あるいはその逆はどうですか?

役に立ちましたか?

解決

オブジェクトの継承をデータベース テーブルにマッピングするには、通常 3 つの方法があります。

タイプの特別なフィールドを持つすべてのオブジェクトのすべてのフィールドを含む 1 つの大きなテーブルを作成できます。これは高速ですが、スペースを無駄にします。ただし、最新のデータベースは空のフィールドを保存しないことでスペースを節約します。また、テーブル内のすべてのユーザーのみを検索する場合、テーブルにあらゆる種類のユーザーが含まれるため、処理が遅くなる可能性があります。すべての or マッパーがこれをサポートしているわけではありません。

すべてのテーブルに基本クラスのフィールドが含まれるように、さまざまな子クラスごとに異なるテーブルを作成できます。これはパフォーマンスの観点からは問題ありません。しかし、メンテナンスの観点からはそうではありません。基本クラスが変更されるたびに、すべてのテーブルが変更されます。

提案したように、クラスごとにテーブルを作成することもできます。このようにして、すべてのデータを取得するには結合が必要になります。したがって、パフォーマンスが低くなります。それが最もクリーンな解決策だと思います。

もちろん、何を使用したいかは状況によって異なります。どのソリューションも完璧ではないため、長所と短所を比較検討する必要があります。

他のヒント

マーティン・ファウラーの作品を見てみましょう エンタープライズ アプリケーション アーキテクチャのパターン:

  • 単一テーブルの継承:

    リレーショナル データベースにマッピングする場合、複数のテーブルの継承構造を処理するときにすぐにマウントされる可能性がある結合を最小限に抑えるよう努めます。単一テーブルの継承は、継承構造のすべてのクラスのすべてのフィールドを単一のテーブルにマップします。

  • クラステーブルの継承:

    オブジェクトに明確にマップされ、継承構造内のどこにでもリンクできるデータベース構造が必要です。クラス テーブルの継承では、継承構造内のクラスごとに 1 つのデータベース テーブルを使用することでこれをサポートします。

  • 具象テーブルの継承:

    オブジェクト インスタンスの観点からテーブルを考えると、メモリ内の各オブジェクトを取得し、それを単一のデータベース行にマップするのが賢明な方法です。これは、継承階層内の具象クラスごとにテーブルが存在する具象テーブルの継承を意味します。

ユーザー、個人、および特別な個人がすべて同じ外部キーを持っている場合、テーブルは 1 つになります。ユーザー、人物、または特別な人物に制限される Type という列を追加します。次に、Type の値に基づいて、他のオプションの列に制約を設けます。

オブジェクト コードの場合、ポリモーフィズムを表すために別個のテーブルがあるか複数のテーブルがあるかに大きな違いはありません。ただし、データベースに対して SQL を実行する必要がある場合は、サブタイプの外部キーが同じであれば、ポリモーフィズムが単一のテーブルでキャプチャされるとはるかに簡単になります。

ここで私が言おうとしていることは、データベース設計者を非難することになるでしょうが、次のようになります。

データベースを考えてみましょう ビュー インターフェース定義と同等のものとして。そして、テーブルはクラスに相当します。

したがって、あなたの例では、3 つの person クラスすべてが IPersonal インターフェイスを実装します。したがって、テーブルが 3 つあり、「User」、「person」、「Specialperson」に 1 つずつ対応します。

次に、3 つのテーブルすべてから共通のプロパティ (「インターフェイス」で定義されたもの) を単一のビューに選択するビュー「PersonView」またはその他のものを用意します。このビューの「PersonType」列を使用して、保存される人の実際のタイプを保存します。

したがって、あらゆる種類の人物に対して操作できるクエリを実行する場合は、単に PersonView ビューにクエリを実行するだけです。

これはOPの質問の意図ではないかもしれませんが、これをここに投げ込むかもしれないと思いました。

最近、プロジェクトで db ポリモーフィズムというユニークなケースが発生しました。60 ~ 120 の可能なクラスがあり、それぞれが 30 ~ 40 の一意の属性の独自のセットと、すべてのクラスで約 10 ~ 12 の共通属性を持ちました。私たちは SQL-XML の道を進むことに決め、最終的に 1 つのテーブルに落ち着きました。何かのようなもの :

PERSON (personid,persontype, name,address, phone, XMLOtherProperties)

すべての共通プロパティを列として含み、その後に大きな XML プロパティ バッグが含まれます。ORM 層は、XMLOtherProperties からのそれぞれのプロパティの読み取り/書き込みを担当しました。少し似ている :

 public string StrangeProperty
{
get { return XMLPropertyBag["StrangeProperty"];}
set { XMLPropertyBag["StrangeProperty"]= value;}
}

(最終的に XML 列を XML ドキュメントではなく Hastable としてマッピングしましたが、DAL に最適なものを使用できます)

デザイン賞を受賞することはありませんが、可能性のあるクラスの数が多数 (または不明) であれば機能します。SQL2005 でも、SQL クエリで XPATH を使用して、XML として保存されているプロパティに基づいて行を選択できます。パフォーマンス上のわずかなペナルティを受け入れるだけです。

リレーショナル データベースで継承を処理するには 3 つの基本的な戦略があり、正確なニーズに応じて、より複雑な/特注の代替手段が多数あります。

  • クラス階層ごとのテーブル。階層全体に対して 1 つのテーブル。
  • サブクラスごとのテーブル。サブクラスごとに個別のテーブルが作成され、サブクラス化されたテーブル間の 0-1 の関連付けが行われます。
  • 具象クラスごとのテーブル。具象クラスごとに 1 つのテーブルが作成されます。

これらのアプローチはそれぞれ、正規化、データ アクセス コード、データ ストレージに関する独自の問題を引き起こしますが、私の個人的な好みは、 サブクラスごとのテーブル 代替案のいずれかを選択する特定のパフォーマンスまたは構造上の理由がない限り。

ここで「建築の宇宙飛行士」になる危険を承知で、私はサブクラスに個別のテーブルを使用する傾向があります。サブクラス テーブルの主キーも、スーパータイプにリンクする外部キーにします。

この方法を採用する主な理由は、論理的な一貫性が大幅に高まり、その特定のレコードに対して NULL で意味のないフィールドが多数発生することがなくなるためです。また、この方法を使用すると、設計プロセスを繰り返すときに、サブタイプに追加のフィールドを追加することもはるかに簡単になります。

これには、クエリに JOIN を追加するという欠点があり、パフォーマンスに影響を与える可能性がありますが、私はほとんどの場合、最初に理想的な設計を採用し、後で必要であることが判明した場合は最適化を検討します。最初に「最適な」方法を選んだことが何度かありましたが、ほとんどの場合、後になって後悔しました。

したがって、私のデザインは次のようになります

PERSON (個人 ID、名前、住所、電話番号など)

SPECIALPERSON (personid REFERENCES PERSON(personid)、追加フィールド...)

USER (personid REFERENCES PERSON(personid)、ユーザー名、暗号化されたパスワード、追加フィールド...)

必要に応じて、後でスーパータイプとサブタイプを集約するビューを作成することもできます。

このアプローチの 1 つの欠点は、特定のスーパータイプに関連付けられたサブタイプを頻繁に検索することになる場合です。これに対する簡単な答えは私の頭ではありません。必要に応じてプログラムで追跡することも、いくつかのグローバル クエリを実行して結果をキャッシュすることもできます。それは実際にはアプリケーションによって異なります。

person と Special person の違いにもよりますが、おそらくこのタスクにはポリモーフィズムは望ましくないでしょう。

User テーブル、つまり User に対する null 許容の外部キー フィールドを持つ Person テーブルを作成します (つまり、Personal は User にすることができますが、そうである必要はありません)。
次に、追加のフィールドを含む Person テーブルに関連する Specialperson テーブルを作成します。特定の Person.ID のレコードが Specialperson に存在する場合、その人は特別な人です。

当社では、すべてのフィールドを 1 つのテーブルに結合することでポリモーフィズムに対処していますが、最悪の場合、参照整合性を強制することができず、非常に理解しにくいモデルになります。私はそのアプローチには絶対に反対することをお勧めします。

私はサブクラスごとのテーブルを使用し、パフォーマンスへの影響も避けますが、型に基づいてオンザフライでクエリを構築することですべてのサブクラステーブルとの結合を回避できるORMを使用します。前述の戦略は単一レコード レベルのプルには機能しますが、一括更新または選択の場合は回避できません。

はい、さらにタイプが存在する可能性がある場合は、TypeID と PersonType テーブルも検討します。ただし、3 つしかない場合は、NEC ではないはずです。

これは古い投稿ですが、概念的、手順、パフォーマンスの観点から検討してみようと思いました。

私が最初に尋ねる質問は、個人、特別な人、ユーザーの間の関係、そして誰かがそのような関係になることが可能かどうかです。 両方 特別な人であると同時にユーザーでもあります。または、その他の 4 つの可能な組み合わせ (クラス a + b、クラス b + c、クラス a + c、または a + b + c)。このクラスが値として保存されている場合、 type フィールドが存在し、したがってこれらの組み合わせが崩壊し、その崩壊が受け入れられない場合は、1 対多のリレーションシップを可能にするセカンダリ テーブルが必要になると思います。使用状況と、組み合わせ情報が失われた場合のコストを評価するまでは、それを判断しないことがわかりました。

私が 1 つのテーブルに傾くもう 1 つの要因は、シナリオの説明です。 User ユーザー名 (varchar(30) など) とパスワード (varchar(32) など) を持つ唯一のエンティティです。共通フィールドの可能な長さが 20 フィールドあたり平均 20 文字である場合、列サイズの増加は 400 に対して 62、つまり約 15% になります。10 年前であれば、これは最新の RDBMS システムよりもコストが高かったでしょう。特に、 varchar のようなフィールド タイプ (例:MySQL 用) が利用可能です。

また、セキュリティが懸念される場合は、 credentials ( user_id, username, password). 。このテーブルは、たとえばログイン時にコンテキストに応じて JOIN で呼び出されますが、構造的にはメイン テーブルの「誰でも」からは分離されています。そして、 LEFT JOIN 「登録ユーザー」を考慮するクエリに使用できます。

私の長年の主な考慮事項は、DB の外および現実世界でのオブジェクトの重要性 (したがって、進化の可能性) を考慮することです。この場合、あらゆる種類の人が心臓を鼓動しており (私はそう願っています)、また互いに階層関係を持っている可能性があります。したがって、今すぐでなくても、心の片隅では、そのような関係を別の方法で保存する必要があるかもしれません。これはここでの質問に明示的に関連しているわけではありませんが、オブジェクトの関係を表現する別の例です。そして今 (7 年後) には、自分の決定がどのように機能したかについて十分な洞察が得られているはずです :)

過去に私はあなたが提案したとおりにそれを行いました。共通のものには Person テーブルを用意し、派生クラスには Specialperson をリンクしました。ただし、Linq2Sql は同じテーブル内のフィールドに違いを示したいので、それを再考しています。ただし、私はエンティティ モデルについてはあまり調べていませんが、他の方法が可能であることは確かです。

個人的には、これらのさまざまなユーザー クラスをすべて 1 つのテーブルに保存します。次に、「Type」値を格納するフィールドを用意することも、どのフィールドに入力されるかによって、相手がどのタイプの人物であるかを暗示することもできます。たとえば、UserID が NULL の場合、このレコードはユーザーではありません。

1 対 1 またはなしのタイプの結合を使用して他のテーブルにリンクすることもできますが、その場合、すべてのクエリで追加の結合を追加することになります。

最初の方法は、そのルートに進むことにした場合、LINQ-to-SQL でもサポートされます (「Table Per Hierarchy」または「TPH」と呼ばれます)。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top