承接上一篇關於刪除操作存儲過程的介紹,本文將詳細探討在 NHibernate 中如何使用存儲過程實現實體對象的新增與更新。所有內容均源自實際專案經驗,涵蓋錯誤排查、存儲過程修改技巧,以及與原生 NHibernate 操作的差異對比,供開發者參考借鑒。
首先執行 CreateCustomerTest() 測試方法,觀察未使用存儲過程時 NHibernate 生成的 SQL 語句:
INSERT INTO Customer (Version, Firstname, Lastname) VALUES (@p0, @p1, @p2);
select SCOPE_IDENTITY(); @p0 = '1', @p1 = 'YJing', @p2 = 'Lee'
可以看到 NHibernate 使用 native 生成器策略,自動選擇 identity 模式由資料庫生成主鍵。
修改 Customer.hbm.xml,在 <class> 元素下添加 <sql-insert> 節點:
<sql-insert>exec CustomerInsert ?,?,?,?</sql-insert>
此處四個問號對應存儲過程的四個參數。
執行測試報錯:
NHibernate.Exceptions.GenericADOException: could not insert... 參數化查詢 '(@p0 int,@p1 nvarchar(3),@p2 nvarchar(7),@p3 int)exec CustomerIn' 需要參數 '@p3',但未提供該參數
錯誤提示參數 @p3 缺失,經排查發現存儲過程參數順序與 NHibernate 預期不一致。
調整 CustomerInsert 存儲過程,移除 SET NOCOUNT ON 並重新排列參數,將輸出參數 @CustomerId 置於最後:
ALTER PROCEDURE [dbo].[CustomerInsert]
(
@Version int,
@Firstname nvarchar(50) = NULL,
@Lastname nvarchar(50) = NULL,
@CustomerId int = NULL OUTPUT
)
AS
INSERT INTO [Customer] ([Version], [Firstname], [Lastname])
VALUES (@Version, @Firstname, @Lastname)
SELECT @CustomerId = SCOPE_IDENTITY();
RETURN @@Error
修改後測試仍失敗,核心原因在於主鍵生成策略衝突:
SCOPE_IDENTITY()SCOPE_IDENTITY() 顯式賦值主鍵此時有兩種解決方案:
<generator> 改為 increment,關閉 IDENTITY_INSERT,由存儲過程負責主鍵生成修改映射文件生成器配置:
<generator class="increment"></generator>
再次執行測試,成功生成 SQL:
exec CustomerInsert @p0,@p1,@p2,@p3;
@p0 = '1', @p1 = 'YJing', @p2 = 'Lee' ,@p3 = '18'
| 生成器類型 | 直接操作 | 存儲過程操作 | 解決方案 |
|---|---|---|---|
| native | 正常 | 缺少 @p3 參數 | 改用 increment |
| increment | IDENTITY_INSERT OFF 時報錯 | Batch update returned unexpected row count | 移除 SET NOCOUNT ON |
若不願使用存儲過程,可直接在映射中編寫 SQL,效果等同:
<sql-insert>INSERT INTO Customer (Version, Firstname, Lastname) VALUES (?,?,?)</sql-insert>
注意:此方式同樣要求生成器為 increment。
執行 UpdateCustomerTest(),觀察原生 SQL 包含版本控制欄位:
UPDATE Customer SET Version = @p0, Firstname = @p1, Lastname = @p2
WHERE CustomerId = @p3 AND Version = @p4;
@p0 = '2', @p1 = 'YJingCnBlogs', @p2 = 'Lee', @p3 = '13', @p4 = '1'
在映射文件添加 <sql-update>:
<sql-update>exec CustomerUpdate ?,?,?,?</sql-update>
執行報錯 Row was updated or deleted by another transaction,同刪除操作一樣,需移除存儲過程中的 SET NOCOUNT ON 以便 NHibernate 獲取受影響行數。
修改 CustomerUpdate 加入 @OldVersion 參數:
ALTER PROCEDURE [dbo].[CustomerUpdate]
(
@Version int,
@Firstname nvarchar(50) = NULL,
@Lastname nvarchar(50) = NULL,
@CustomerId int,
@OldVersion int
)
AS
UPDATE [Customer]
SET [Version] = @Version, [Firstname] = @Firstname, [Lastname] = @Lastname
WHERE [CustomerId] = @CustomerId AND [Version] = @OldVersion
RETURN @@Error
映射文件中 <sql-update> 增加第五個參數:
<sql-update>exec CustomerUpdate ?,?,?,?,?</sql-update>
測試通過,生成 SQL 精確對應版本控制邏輯:
exec CustomerUpdate @p0,@p1,@p2,@p3,@p4;
@p0 = '4', @p1 = 'YJingCnBlogsCnBlogs', @p2 = 'Lee', @p3 = '13', @p4 = '3'
<sql-update>UPDATE [Customer] SET [Version] = ?,[Firstname] = ?,[Lastname] = ?
WHERE [CustomerId] =? AND [Version] =?</sql-update>
本文結合上篇內容,完整演示了 NHibernate 中存儲過程在刪除、新增、更新三大操作中的實戰應用與坑點規避。下一篇將介紹 <sql-query> 調用存儲過程進行查詢的靈活用法。