++C++; // 未確認飛行 C MVVMパターンを使ったクロス・ターゲット開発 C#たんと学ぶ わりと硬派なソフトウェア開発講座 番外編「Windows Phone 7」

Top総合 目次C# によるプログラミング入門

[雑記] O/R インピーダンスミスマッチ(クラスの継承)

このエントリーをはてなブックマークに追加

目次

キーワード

概要

Ver. 3.0

[雑記] O/R インピーダンスミスマッチ」 では、オブジェクト指向とリレーショナルデータベースの間のデータ構造の差、 階層構造とテーブル結合の差について話をしました。 これに加えて、オブジェクト指向独特の概念として、クラスの継承というものがあります。

ここでは、Entity Framework を使って、クラスの継承階層をテーブルにマッピングする例を紹介します。

クラスの継承階層

継承」 や 「多態性」 で説明したように、オブジェクト指向の基本的な概念の1つに継承というものがあります。

例えば、矩形や円などの図形を考えたとき、これらの図形には「面積を求められる」という共通の性質があります。 このような場合、共通の性質を基底クラスにまとめてしまうのがオブジェクト指向のやり方です。 この様子を図示したものと、サンプルコードを以下に示します。

図1: クラスの継承階層の例

public abstract class Shape
{
    public abstract float GetArea();
}

public class Rectangle : Shape
{
    public float Width { get; set; }

    public float Height { get; set; }

    public override float GetArea()
    {
        return this.Width * this.Height;
    }
}

public class Circle : Shape
{
    public float Radius { get; set; }

    public override float GetArea()
    {
        return (float)(Math.PI * this.Radius * this.Radius);
    }
}

継承階層を RDB のテーブルで表現

前節で説明したような継承階層を RDB 上で表現するにはいくつか方法がありますが、 ここでは2つほど紹介します。

テーブルの共有

1つ目の方法は、クラスの継承階層で1つのテーブルを共有します。 (table per hierarchy と呼びます。) テーブルの各行がどの型かを判別するための列(discriminator: discriminate は「区別・識別する」)を作ります。

図2: 継承階層を共有テーブル化

シンプルですが、型によって使われない列が出るという問題もあります。

別テーブルを作成

もう1つは、クラスごとに別のテーブルを作ります。 (table per type と呼びます。)

図3: 継承階層を複数のテーブルに分割

共有テーブルのように無駄な列ができることはありませんが、 複数のテーブルが見かけ上、1つのテーブルであるかのように見せる仕組みが必要になります。

Entity Framework における継承構造の O/R マッピング

Entity Framework では、データベース コンテキストを作る際に、 DbContext クラスの OnModelCreating メソッドをオーバーライドすることで、 継承階層のテーブル化方法をカスタマイズできます。

table per hierarchy にしたい場合は以下のように書きます。

public class TablePerHierarchyContext : DbContext
{
    public DbSet<Shape> Shapes { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Shape>()
            .Map<Rectangle>(x => x.Requires("type").HasValue("R"))
            .Map<Circle>(x => x.Requires("type").HasValue("C"));
    }
}

一方、 table per type にしたい場合は以下のように書きます。

public class TablePerTypeContext : DbContext
{
    public DbSet<Shape> Shapes { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Rectangle>().ToTable("Rectangle");
        modelBuilder.Entity<Circle>().ToTable("Circle");
    }
}
サンプル データ作成

作成した2つのデータベース コンテキストを使って、サンプル データを作成してみましょう。

private static void Create()
{
    using (var db = new TablePerHierarchyContext())
    {
        Create(db.Shapes);
        db.SaveChanges();
    }

    // ↑↓見ての通り、コンテキストが違う以外は全く一緒。

    using (var db = new TablePerTypeContext())
    {
        Create(db.Shapes);
        db.SaveChanges();
    }
}

private static void Create(System.Data.Entity.DbSet<Shape> shapes)
{
    shapes.Add(new Rectangle { Width = 10, Height = 20 });
    shapes.Add(new Rectangle { Width = 15, Height = 12 });
    shapes.Add(new Circle { Radius = 1.5f });
    shapes.Add(new Circle { Radius = 3 });
}

TablePerHierarchyContext によって作られるデータベースは以下のようになります。

図4: TablePerHierarchyContext によって作られるデータベース

これに対して、 TablePerTypeContext によって作られるデータベースは以下のようになります。

図5: TablePerHierarchyContext によって作られるデータベース

データの参照

作成したデータを参照してみましょう。

private static void Query()
{
    using (var db = new TablePerTypeContext())
    {
        Query(db.Shapes);
    }

    // ↑↓見ての通り、コンテキストが違う以外は全く一緒。

    using (var db = new TablePerHierarchyContext())
    {
        Query(db.Shapes);
    }
}

private static void Query(System.Data.Entity.DbSet<Shape> shapes)
{
    foreach (var x in shapes)
    {
        Console.WriteLine("{0}: {1}", x.GetType().Name, x.GetArea());
    }
}
Table Per Hierarchy
Rectangle: 200
Rectangle: 180
Circle: 7.068583
Circle: 28.27433
Table Per Type
Circle: 7.068583
Circle: 28.27433
Rectangle: 200
Rectangle: 180

LINQ to SQL 版

 ⊞  (LINQ to SQL 版)

[お問い合わせ](q)