NHibernate 作為 .NET 領域最成熟的 ORM 框架,功能強大但學習曲線陡峭。本文整理實務開發中最高頻的四大異常——LazyInitializationException、無主鍵表映射、複合主鍵實作、外鍵重複參數——並提供標準解決模式與最佳實踐。
異常堆疊:NHibernate.LazyInitializationException: could not initialize proxy - no Session
根因:many-to-one 預設 lazy="proxy",當嘗試存取導航屬性時,若 Session 已關閉,無法發出 SQL 載入關聯物件。
解決方案:
<!-- 方案 A:關閉延遲載入(適用於經常需要的關聯) -->
<many-to-one name="Order" column="ORDER_ID" class="Order" lazy="false" />
<!-- 方案 B:在 set 集合上關閉延遲(會產生額外查詢,慎用) -->
<set name="OrderDetails" lazy="false"> ... </set>
最佳實踐:僅在 many-to-one 設定 lazy="false";集合保持預設 lazy="true",在 Service Layer 以 FetchMode.Eager 或 FetchMode.Join 明確指定抓取策略,避免 N+1 問題。
場景**:明細表(如 OrderDetail)常以複合鍵 (OrderId, ProductId) 唯一識別,資料庫未建立單一 PK 欄位。
錯誤做法**:省略 <id> 節點 → NHibernate 拋出映射驗證錯誤。
正確做法:使用 <composite-id> 定義複合主鍵**
<composite-id>
<key-property name="OrderId" column="ORDER_ID" />
<key-property name="ProductId" column="PRODUCT_ID" />
</composite-id>
也可將所有欄位納入複合鍵(極端情況):
<composite-id>
<key-property name="Amount" />
<key-property name="CreateTime" />
<key-property name="ModifyTime" />
</composite-id>
規則**:NHibernate 使用複合主鍵物件作為快取鍵與集合元素比較依據,若未正確實作 Equals / GetHashCode,會導致快取失效、重複實體、集合操作異常。
標準實作模板**
public class OrderDetail
{
public virtual int OrderId { get; set; }
public virtual int ProductId { get; set; }
public virtual decimal Amount { get; set; }
public override bool Equals(object obj)
{
var other = obj as OrderDetail;
if (other == null) return false;
return OrderId == other.OrderId && ProductId == other.ProductId;
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 23 + OrderId.GetHashCode();
hash = hash * 23 + ProductId.GetHashCode();
return hash;
}
}
}
注意**:GetHashCode 必須基於不可變的主鍵屬性計算,避免物件加入 HashSet/Dictionary 後雜湊值改變導致找不到元素。
異常訊息**:Index 4 is invalid for SqlParameterCollection with Count=4
根因**:從表映射主表時,many-to-one 的外鍵欄位同時出現在 insert 與 update 敘述,導致參數重複。
錯誤映射**
<many-to-one name="Dictate" column="DICTATE_ID" class="Dictate" />
修正映射:標記 insert/update=false**
<many-to-one name="Dictate" column="DICTATE_ID" class="Dictate"
insert="false" update="false" />
原理**:外鍵值由主表端(如 Dictate.OrderDetails 的 key 欄位)維護,從表端僅負責讀取,不參與寫入,避免參數重複。
<composite-id>。Equals / GetHashCode。many-to-one 加上 insert="false" update="false"。NHibernate.Profiler 或 SQL Profiler 監控實際 SQL,驗證映射正確性。