當業務邏輯中存在繼承階層(如「產品」為基類,「書籍」與「其他商品」為衍生類),但資料庫僅有一張 Products 表時,LINQ to SQL 的 單一資料表繼承映射 可完美解決物件關聯阻抗不匹配。本文以圖書銷售系統為例,完整示範從資料庫設計、實體類別定義、繼承映射屬性設定,到資料操作的全流程。
單一 Products 表包含所有欄位,關鍵欄位 TypeID 作為 判別器:
TypeID = '1':基類 ProductTypeID = '2':書籍 BookTypeID = '3':其他商品 OtherProduct[Table(Name = "dbo.Products")]
[InheritanceMapping(Code = "1", Type = typeof(Product), IsDefault = true)]
[InheritanceMapping(Code = "2", Type = typeof(Book))]
[InheritanceMapping(Code = "3", Type = typeof(OtherProduct))]
public partial class Product {
[Column(IsPrimaryKey = true, DbType = "Int NOT NULL")] public int ProductID { get; set; }
[Column(DbType = "VarChar(50)")] public string Name { get; set; }
[Column(DbType = "Int")] public int? Stock { get; set; }
[Column(DbType = "Decimal(18,2)")] public decimal? Price { get; set; }
[Column(IsDiscriminator = true, Storage = "_TypeID", DbType = "VarChar(50)")]
public string TypeID { get; set; }
}
關鍵點:
InheritanceMapping 定義判別碼與類型對應IsDiscriminator = true 標記 TypeID 為判別欄位IsDefault = true 指定預設類型(資料庫中無法辨識的值將對應此類型)public partial class Book : Product {
[Column(DbType = "VarChar(50)")] public string ISBN { get; set; }
[Column(DbType = "VarChar(50)")] public string Author { get; set; }
}
public partial class OtherProduct : Product {
[Column(DbType = "VarChar(50)")] public string ProductFrom { get; set; }
}
衍生類別僅需定義專屬欄位,繼承基類共用欄位與主鍵。
public partial class MyDataContext : DataContext {
public Table<Product> Products => GetTable<Product>();
public MyDataContext(string connection) : base(connection, mappingSource) { }
// 連線字串建構略...
}
建議:將實體類別與上下文從 .dbml 設計師分離,手動維護映射屬性,避免設計師覆寫自訂繼承設定。
using (var dc = new MyDataContext(connStr)) {
var book = new Book { ProductID = 1, Name = "C#", Price = 20, Stock = 100, Author = "lfm", ISBN = "123456" };
dc.Products.InsertOnSubmit(book);
dc.SubmitChanges();
}
// 產生 SQL: INSERT INTO Products (TypeID, ...) VALUES ('2', ...)
var prod = new OtherProduct { ProductID = 2, Name = "C#", Price = 20, Stock = 100, ProductFrom = "bj" };
dc.Products.InsertOnSubmit(prod); // TypeID 自動填 '3'
var books = from p in dc.Products where p is Book select p;
foreach (var b in books) Console.WriteLine(b.Name);
LINQ to SQL 會自動轉換為 WHERE TypeID = '2' 的 SQL。
| 優點 | 缺點 |
|---|---|
| 單一表格,無 JOIN 查詢效能佳 | 衍生類別專屬欄位在其他類型列為 NULL,浪費儲存空間 |
| 物件模型豐富,支援多型查詢 | 資料庫端無法強制 NOT NULL 於衍生類別必填欄位 |
| 程式碼維護簡單,無需複雜映射設定 | 不適用於衍生類別欄位極多或差異極大的情況 |
適合:繼承層級淺、共用欄位多、查詢效能敏感的案例。