EF7繼承對映

YataoFeng發表於2023-03-05

Entity Framework 7 (EF7)中的繼承對映允許您將類層次結構對映到資料庫中的表層次結構。具體而言,這意味著您可以建立一個基類,然後從該基類派生多個子類,並將這些子類對映到不同的資料庫表。這使得在資料庫中儲存不同型別的資料變得更加方便,同時還能保持物件導向程式設計的優雅性。
EF7提供了三種型別的繼承對映:單表繼承(Table Per Hierarchy,TPH)、分層表繼承(Table Per Type,TPT)和單獨的表繼承(Table Per Concrete Class,TPC)。下面分別介紹這三種對映型別。
效能基準

單表繼承對映(Table Per Hierarchy,TPH)

單表繼承對映將整個類層次結構對映到單個資料庫表中。每個類在表中對應一行,其中包括一個用於區分不同子類的Discriminator列。例如,考慮以下類層次結構:

public abstract class Animal
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

public class Dog : Animal
{
    public string Breed { get; set; }
}

public class Cat : Animal
{
    public bool IsIndoor { get; set; }
}

使用單表繼承對映,可以將這個類層次結構對映到以下資料庫表:

+----+-------+-----+-------+------------+------------+
| Id | Name  | Age | Breed | IsIndoor   | Discriminator |
+----+-------+-----+-------+------------+------------+
| 1  | Fido  | 3   | Poodle| NULL       | Dog          |
+----+-------+-----+-------+------------+------------+
| 2  | Felix | 5   | NULL  | true       | Cat          |
+----+-------+-----+-------+------------+------------+

可以看到,每個Animal子類都在同一個表中表示為一行,並使用Discriminator列進行區分。
要使用TPH對映,在EF7中,可以使用Fluent API進行配置,如下所示:

modelBuilder.Entity<Animal>()
    .HasDiscriminator<string>("Discriminator")
    .HasValue<Dog>("Dog")
    .HasValue<Cat>("Cat");

這個程式碼片段將Animal類設定為基類,然後使用HasDiscriminator方法指定Discriminator列的名稱。接下來,使用HasValue方法為每個Animal子類指定一個值,以便在Discriminator列中進行區分。

分層表繼承對映(Table Per Type,TPT)

分層表繼承對映將基類和每個子類對映到不同的資料庫表中。每個子類的表都包含基類的所有列和子類特有的列。例如,對於上述類層次結構,使用分層表繼承對映,可以將Animal、Dog和Cat分別對映到三個不同的表中:

Animal table:
+----+-------+-----+
| Id | Name  | Age |
+----+-------+-----+
| 1  | Fido  | 3   |
+----+-------+-----+
| 2  | Felix | 5   |
+----+-------+-----+

Dog table:
+----+-------+-----+-------+
| Id | Name  | Age | Breed |
+----+-------+-----+-------+
| 1  | Fido  | 3   | Poodle|
+----+-------+-----+-------+

Cat table:
+----+-------+-----+----------+
| Id | Name  | Age | IsIndoor |
+----+-------+-----+----------+
| 2  | Felix | 5   | true     |
+----+-------+-----+----------+

可以看到,每個表都只包含基類和對應的子類的列。每個子類表還包含一個外來鍵,指向基類表的主鍵。
要使用TPT對映,在EF7中,可以使用Fluent API進行配置,如下所示:

modelBuilder.Entity<Animal>()
    .ToTable("Animals");

modelBuilder.Entity<Dog>()
    .ToTable("Dogs")
    .HasOne(a => a.Animal)
    .WithOne()
    .HasForeignKey<Dog>(d => d.Id);

modelBuilder.Entity<Cat>()
    .ToTable("Cats")
    .HasOne(a => a.Animal)
    .WithOne()
    .HasForeignKey<Cat>(c => c.Id);

這個程式碼片段中,Animal被對映到Animals表中。對於每個Animal子類,使用ToTable方法將其對映到相應的表中。在每個子類表中,使用HasOne方法指定一個導航屬性,表示它與基類表中的一行相關聯。使用HasForeignKey方法指定外來鍵屬性和基類表中的主鍵。

單獨的表繼承對映(Table Per Concrete Class,TPC)

單獨的表繼承對映將每個類都對映到一個獨立的資料庫表中。每個表都只包含該類的列。這意味著基類和子類之間沒有直接的關係。例如,對於上述類層次結構,使用單獨的表繼承對映,可以將Animal、Dog和Cat分別對映到三個不同的表中:

Animal table:
+----+-------+-----+
| Id | Name  | Age |
+----+-------+-----+
| 1  | Fido  | 3   |
+----+-------+-----+
| 2  | Felix | 5   |
+----+-------+-----+

Dog table:
+----+-------+-----+-------+
| Id | Name  | Age | Breed |
+----+-------+-----+-------+
| 1  | Fido  | 3   | Poodle|
+----+-------+-----+-------+
    
Cat table:
+----+-------+-----+----------+
| Id | Name  | Age | IsIndoor |
+----+-------+-----+----------+
| 2  | Felix | 5   | true     |
+----+-------+-----+----------+

要使用TPC對映,在EF7中,可以使用Fluent API進行配置,如下所示:

modelBuilder.Entity<Animal>()
    .ToTable("Animals");

modelBuilder.Entity<Dog>()
    .ToTable("Dogs");

modelBuilder.Entity<Cat>()
    .ToTable("Cats");

這個程式碼片段中,每個類都被對映到不同的表中,使用ToTable方法指定表名。

繼承對映的查詢

在EF7中,使用繼承對映時,可以查詢基類和子類的例項。例如,以下程式碼查詢所有動物的名字:

using (var context = new AnimalContext())
{
    var names = context.Set<Animal>()
        .Select(a => a.Name)
        .ToList();
}

這個查詢將返回Animal表和所有子類表中的所有名稱。如果要只查詢特定型別的動物,可以在查詢中使用OfType方法,例如:

using (var context = new AnimalContext())
{
    var dogNames = context.Set<Animal>()
        .OfType<Dog>()
        .Select(d => d.Name)
        .ToList();
}

這個查詢只返回Dog表中的名稱。

總結

繼承對映是一種重要的ORM技術,允許將類層次結構對映到資料庫表層次結構中。EF7支援三種繼承對映策略:分層表繼承對映、單獨的表繼承對映和聯合表繼承對映。使用Fluent API可以很容易地配置這些對映。在查詢資料時,可以使用OfType方法篩選出特定型別的例項。

相關文章