首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

LINQ to SQL 實體繼承映射:單表繼承 (TPH) 完整實作指南

     編輯:關於.NET

LINQ to SQL 僅支援 **單表繼承 (Table Per Hierarchy, TPH)**:整個繼承體系對應單一資料表,透過 **判別欄位** 區分實體型別。本文以 Vehicle/Car/Truck 經典範例,完整示範 TPH 映射配置、查詢與實體具現化技巧。

資料表結構:Vehicle (單表)

欄位型別說明
VINPK, varchar車輛識別碼
Keyvarchar(1)判別欄位: V=Vehicle, C=Car, T=Truck
MfgPlantvarchar製造廠 (基類共用)
TrimCodeintCar 專屬
ModelNamevarcharCar 專屬
TonnageintTruck 專屬
AxlesintTruck 專屬

Vehicle 資料表結構

實體類別與屬性映射 (Attribute 方式)


[Table(Name = "Vehicle")]
[InheritanceMapping(Code = "V", Type = typeof(Vehicle), IsDefault = true)]
[InheritanceMapping(Code = "C", Type = typeof(Car))]
[InheritanceMapping(Code = "T", Type = typeof(Truck))]
public class Vehicle
{
    [Column(IsDiscriminator = true, Name = "Key")] // 判別欄位
    public string Key { get; set; }

    [Column(IsPrimaryKey = true, Name = "VIN")]
    public string VIN { get; set; }

    [Column(Name = "MfgPlant")]
    public string MfgPlant { get; set; }
}

public class Car : Vehicle
{
    [Column(Name = "TrimCode")]
    public int TrimCode { get; set; }

    [Column(Name = "ModelName")]
    public string ModelName { get; set; }
}

public class Truck : Vehicle
{
    [Column(Name = "Tonnage")]
    public int Tonnage { get; set; }

    [Column(Name = "Axles")]
    public int Axles { get; set; }
}

關鍵屬性解析

  • [Table]:僅基類標註,對應單一資料表。
  • [InheritanceMapping]:每個具象類別一條,**Code** 對應判別欄位值,**Type** 對應 CLR 型別,**IsDefault=true** 表示判別值不符合任何映射時的 fallback 型別 (此處為 Vehicle)。
  • [Column(IsDiscriminator=true)]:標記判別欄位,**必須存在於基類**,且型別支援字串/數值/布林。
  • 子類別 **不需** 標註 [Table],自動繼承基類表映射。

DataContext 定義與型別化查詢


[Database(Name = "MyDatabase")]
public class VehicleContext : DataContext
{
    public VehicleContext(string connection) : base(connection) { }

    // 基類表:包含所有型別
    public Table<Vehicle> Vehicles => GetTable<Vehicle>();

    // 型別化查詢屬性:利用 OfType<T>()
    public IQueryable<Car> Cars => Vehicles.OfType<Car>();
    public IQueryable<Truck> Trucks => Vehicles.OfType<Truck>();
}

等價替代寫法 (白皮書示例)


// 方式 1: Where + 型別測試
return Vehicles.Where(v => v is Car);

// 方式 2: Select + Cast + Where
return Vehicles.Select(v => v as Car).Where(c => c != null);
💡 OfType<T>() 最簡潔且語意明確,會轉譯為 SQL WHERE Key = 'C'。

查詢與具現化實測


using (var ctx = new VehicleContext(connStr))
{
    // 查詢所有 Car
    foreach (var car in ctx.Cars)
        Console.WriteLine($"Car: {car.VIN} {car.ModelName}");

    // 查詢所有 Truck
    foreach (var truck in ctx.Trucks)
        Console.WriteLine($"Truck: {truck.VIN} {truck.Tonnage}");

    // 新增 Car
    var newCar = new Car { VIN = "CAR001", Key = "C", MfgPlant = "PlantA", ModelName = "Sedan", TrimCode = 1 };
    ctx.Vehicles.InsertOnSubmit(newCar);
    ctx.SubmitChanges();
}

類別圖與關係視覺化

Vehicle/Car/Truck 類別圖

TPH 優缺點權衡

優點缺點
查詢無需 JOIN,效能極佳子類別專屬欄位大量 NULL,浪費儲存空間
Schema 簡單,單表維護無法在資料庫層級對子類別欄位加 NOT NULL 約束
多型查詢自然 (OfType)繼承層級深/寬時,單表欄位爆炸
實體具現化邏輯單一不支援 TPT (Table Per Type) / TPC (Table Per Concrete)

何時選擇替代方案?

  • 需 **多表繼承 (TPT/TPC)**、支援多資料庫、鬆耦合領域模型 → 改用 **LINQ to Entities (Entity Framework)** 或 **ADO.NET Entity Framework**。
  • 純 .NET 物件模型、無資料庫持久化 → 考慮 **NHibernate** (支援完整繼承策略)。

總結

LINQ to SQL 的 TPH 映射機制簡潔有力:[InheritanceMapping] + IsDiscriminator + OfType<T>() 三大招式即可掌控。雖受限於單表架構,但在「繼承層級淺、子類別欄位少、查詢效能優先」的場景下,TPH 仍是極具性價比的選擇。若專案演進需更豐富的 ORM 能力,平滑遷移至 Entity Framework 是自然演進路徑。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved