承接一對多關係,本文以 Order-Product 為核心,完整演示 NHibernate 中多對多關係的雙向映射配置,並展示原生 SQL、HQL、Criteria API 三種關聯查詢方式的實戰代碼。
Order 與 Product 關係:一個訂單包含多個產品,一個產品屬於多個訂單。中間表 OrderProduct 維護關聯。
public class Order
{
public virtual int OrderId { get; set; }
public virtual DateTime OrderDate { get; set; }
public virtual Customer Customer { get; set; } // 多對一
public virtual IList<Product> Products { get; set; } // 多對多
}
<class name="DomainModel.Entities.Order,DomainModel" table="`Order`">
<id name="OrderId" column="OrderId" type="Int32" unsaved-value="0">
<generator class="native" />
</id>
<property name="OrderDate" column="OrderDate" type="DateTime" not-null="true" />
<!-- 多對一:訂單屬於客戶 -->
<many-to-one name="Customer" column="Customer" not-null="true"
class="DomainModel.Entities.Customer,DomainModel"
foreign-key="FK_CustomerOrders" />
<!-- 多對多:訂單包含產品 -->
<bag name="Products" generic="true" table="OrderProduct">
<key column="`Order`" foreign-key="FK_OrderProducts"/>
<many-to-many column="Product"
class="DomainModel.Entities.Product,DomainModel"
foreign-key="FK_ProductOrders"/>
</bag>
</class>
public class Product
{
public virtual int ProductId { get; set; }
public virtual string Name { get; set; }
public virtual float Cost { get; set; }
public virtual IList<Order> Orders { get; set; } // 多對多
}
<class name="DomainModel.Entities.Product,DomainModel" table="Product">
<id name="ProductId" column="ProductId" type="Int32" unsaved-value="0">
<generator class="native"/>
</id>
<property name="Name" column="Name" type="string" not-null="true" length="50"/>
<property name="Cost" column="Cost" type="float" not-null="true"/>
<!-- 多對多:產品屬於訂單 -->
<bag name="Orders" generic="true" table="OrderProduct">
<key column="Product" foreign-key="FK_ProductOrders"/>
<many-to-many column="`Order`"
class="DomainModel.Entities.Order,DomainModel"
foreign-key="FK_OrderProducts"/>
</bag>
</class>
<bag> + <many-to-many>table="OrderProduct" 指定中間表key column 為本端外鍵欄位,many-to-many column 為對端外鍵foreign-key 屬性供 SchemaExport 生成 DDL 使用查詢目標:獲取擁有特定產品訂單的客戶列表
public IList<Customer> UseSQL_GetCustomersWithOrdersHavingProduct(DateTime orderDate)
{
return _session.CreateSQLQuery(
"select distinct {customer.*} from Customer {customer}" +
" inner join [Order] o on o.Customer={customer}.CustomerId" +
" inner join OrderProduct op on o.OrderId=op.[Order]" +
" inner join Product p on op.Product=p.ProductId" +
" where o.OrderDate > :orderDate")
.AddEntity("customer", typeof(Customer))
.SetDateTime("orderDate", orderDate)
.List<Customer>();
}
要點:{customer.*} 簡寫全屬性,AddEntity 指定返回實體類型。
public IList<Customer> UseHQL_GetCustomersWithOrdersHavingProduct(DateTime orderDate)
{
return _session.CreateQuery(
"select distinct c from Customer c, " +
"c.Orders.elements o where o.OrderDate > :orderDate")
.SetDateTime("orderDate", orderDate)
.List<Customer>();
}
優勢:面向對象語法,映射文件已定義關聯,無需重複指定 Join 條件。
public IList<Customer> UseCriteriaAPI_GetCustomerswithOrdersHavingProduct()
{
return _session.CreateCriteria(typeof(Customer))
.Add(Restrictions.Eq("Firstname", "YJing"))
.CreateCriteria("Orders")
.Add(Restrictions.Gt("OrderDate", new DateTime(2008, 10, 1)))
.CreateCriteria("Products")
.Add(Restrictions.Eq("Name", "Cnblogs"))
.List<Customer>();
}
機制:CreateCriteria("關聯屬性名") 導航至子實體,鏈式調用構建多層約束。圖示說明導航路徑:Customer → Orders → Products。
public IList<Customer> UseCriteriaAPI_GetCustomerswithOrdersHavingProduct(int productId)
{
return _session.CreateCriteria(typeof(Customer))
.CreateCriteria("Orders")
.CreateCriteria("Products")
.Add(Restrictions.Eq("ProductId", productId))
.List<Customer>();
}
多對多映射核心在於中間表配置與雙向 bag 定義。三種查詢方式各有優勢:
建議根據團隊技術棧與業務場景選擇主力查詢方式,下篇將探討延遲/立即加載機制。