1. 程式人生 > 實用技巧 >NHibernate之(16):探索NHibernate中使用儲存過程(中)

NHibernate之(16):探索NHibernate中使用儲存過程(中)

本節內容

引入

上一篇,怎麼使用MyGeneration提供的模板建立儲存過程和刪除物件儲存過程的使用,這篇接下來介紹在NHibernate中如何使用儲存過程建立物件、更新物件整個詳細過程,這些全是在實際運用中積累的經驗,涉及使用的錯誤資訊,如何修改儲存過程,並且比較沒有使用儲存過程的不同點,並非官方比較權威的資料,所以敬請參考,這篇繼續,如果你還沒有來及看上一篇,那趕緊去看看吧。

例項分析

2.建立物件

Step1:為了比較,我們先執行CreateCustomerTest()測試方法,沒有使用儲存過程下建立物件生成SQL語句如下:

INSERT INTO Customer (Version, Firstname, Lastname) VALUES (@p0, @p1, @p2);
select SCOPE_IDENTITY(); @p0 = '1', @p1 = 'YJing', @p2 = 'Lee'

Step2:修改對映檔案新增儲存過程,開啟Customer.hbm.xml對映檔案,在Class元素下新增<sql-insert>節點,呼叫CustomerInsert儲存過程,CustomerInsert 儲存過程有四個引數,這裡用四個問號表示:

<sql-insert>exec CustomerInsert ?,?,?,?</
sql-insert>

Step3:執行CreateCustomerTest()測試方法,出現錯誤“NHibernate.Exceptions.GenericADOException : could not insert: [DomainModel.Entities.Customer][SQL: exec CustomerInsert ?,?,?,?];System.Data.SqlClient.SqlException : 引數化查詢 '(@p0 int,@p1 nvarchar(3),@p2 nvarchar(7),@p3 int)exec CustomerIn' 需要引數 '@p3',但未提供該引數”,這應該是引數問題,仔細看看原來的儲存過程,引數位置有些問題。

Step4:修改CustomerInsert儲存過程,去掉SET NOCOUNT ON並把引數移動位置,程式碼片段如下:

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

Step4:執行CreateCustomerTest()測試方法失敗,還是以上問題,是不是生成器問題?

這裡,先看看NHibernate中最常用的兩個內建生成器:

native:根據底層資料庫的能力選擇identity、sequence 或者hilo中的一個。

increment:生成型別為Int64、Int16或Int32的識別符號,只當沒有其他程序同時往同一個表插入資料時,能夠保持唯一性。

附:

  • identity:對DB2、MySQL、SQL Server、Sybase資料庫內建標識欄位提供支援。資料庫返回的識別符號用Convert.ChangeType加以轉換,因此能夠支援任何整型。
  • sequence:在DB2、PostgreSQL、Oracle資料庫中使用序列或者Firebird的生成器。資料庫返回的識別符號用Convert.ChangeType加以轉換,因此能夠支援任何整型。
  • hilo:使用一個高/低位演算法高效地生成Int64、Int32或Int16型別的識別符號。給定一個表和欄位(預設分別是 hibernate_unique_key 和next_hi)作為高位值的來源。高/低位演算法生成的識別符號只在一個特定的資料庫中是唯一的。如果是使用者提供的連線,不要使用此生成器。

測試上面方法時,使用NHibernate內建生成器型別native,它根據資料庫配置自動選擇了identity型別,identity型別對錶的主鍵提供支援,可以為主鍵插入顯式值(標識增量加一)。我們在第一步就是使用NHibernate內建的生成器型別為主鍵增量加一,沒有任何問題,但是看看CustomerInsert儲存過程,主鍵CustomerId使用SCOPE_IDENTITY()插入顯式值(標識增量加一),我們就沒有必要使用內建的生成器來插入值了,把設定IDENTITY_INSERT為OFF,NHibernate正好提供了increment型別,生成型別僅僅是整型。

解決方法有兩種:

  • 1.修改儲存過程:如果你在開發,你最好修改儲存過程,因為在面向物件開發中,儘量不要使用儲存過程,何況現在儲存過程破壞了你的設計。
  • 2.修改主鍵生成型別:如果你已經部署好你的資料庫,你沒有許可權修改儲存過程的話,那麼只要修改程式來將就儲存過程了。

還有一點注意:如果你主鍵生成器型別為“native”,那麼儲存過程的引數必須相一致。

Step5:修改主鍵生成器型別

為了演示,這裡我們修改主鍵生成器型別,還可以總結錯誤資訊。使用increment型別,關閉IDENTITY_INSERT,這時執行儲存過程,由儲存過程來為表'Customer'中的標識列(主鍵)插入顯式值(標識增量加一)。

<generator class ="increment"></generator>

Step6:執行CreateCustomerTest()測試方法成功,生成SQL如下,其中p0是Version,p3是CustomerId

exec CustomerInsert @p0,@p1,@p2,@p3;
@p0 = '1', @p1 = 'YJing', @p2 = 'Lee' ,@p3 = '18' 
錯誤提示

其實我在除錯過程中還有一些錯誤,這裡總結一下:

方案1:使用主鍵生成器型別為"native"

  • 直接建立物件:正常
  • 儲存過程建立物件:引數化查詢 '(@p0 int,@p1 nvarchar(5),@p2 nvarchar(7),@p3 int)exec CustomerIn' 需要引數 '@p3',但未提供該引數。解決方法:使用increment型別

方案2:使用主鍵生成器型別為"increment"

  • 直接建立物件:當IDENTITY_INSERT設定為OFF時,不能為表'Customer'中的標識列插入顯式值。解決方法:使用native型別
  • 儲存過程建立物件:Batch update returned unexpected row count from update; actual row count: -1; expected: 1。解決方法:去掉SET NOCOUNT ON

另外,如果你不喜歡儲存過程的話,你也可以這樣寫,效果和使用儲存過程一樣。

<sql-insert>INSERT INTO Customer (Version, Firstname, Lastname) VALUES (?,?,?)</sql-insert>

但是這樣的話,主鍵生成器型別必須為"increment"。

3.更新物件

Step1:為了比較,我們先執行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'

Step2:修改對映檔案新增儲存過程,開啟Customer.hbm.xml對映檔案,在Class元素下新增<sql-update>節點,呼叫CustomerUpdate儲存過程,CustomerUpdate儲存過程有四個引數,這裡用四個問號表示:

<sql-update>exec CustomerUpdate ?,?,?,?</sql-update>

Step3:執行UpdateCustomerTest()測試方法,出現錯誤“Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [DomainModel.Entities.Customer#15]”,這個錯誤同刪除物件儲存過程一樣,我們修改CustomerUpdate儲存過程,去掉SET NOCOUNT ON,再次執行UpdateCustomerTest()測試方法,輸出結果如下:

exec CustomerUpdate @p0,@p1,@p2,@p3; 
@p0 = '2', @p1 = 'YJingCnBlogs', @p2 = 'Lee', @p3 = '15', @p4 = '1'

這段根據我們寫的儲存過程實質SQL語句為:

UPDATE [Customer] SET [Version] = '2', [Firstname] = 'YJingCnBlogs',
    [Lastname] = 'Lee' WHERE [CustomerId] ='1'

雖然NHibernate知道Version列是版本控制,它自動由原來的1更新為2,但是看看上面生成的SQL語句還是不怎麼舒服,其@p4引數無緣無故的在那裡。

Step4:修改CustomerUpdate儲存過程,把版本控制用好,編寫如下:

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

Step4:執行UpdateCustomerTest()測試方法,出現錯誤“過程或函式 'CustomerUpdate' 需要引數 '@OldVersion',但未提供該引數”,Oh!對映檔案中呼叫儲存過程忘了增加一個引數,現在是五個引數了!

Step5:修改儲存過程引數數量,開啟對映檔案在<sql-update>中新增一個引數即新增“,?”

<sql-update>exec CustomerUpdate ?,?,?,?,?</sql-update>

Step6:執行UpdateCustomerTest()測試方法,NHibernate生成語句如下,這下完美了。

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>

結語

這一篇和上一篇介紹瞭如何使用儲存過程刪除物件、建立物件、更新物件,還有一種使用<sql-query>來呼叫儲存過程,非常方便,下篇繼續介紹。

本系列連結:NHibernate之旅系列文章導航

NHibernate Q&A

下次繼續分享NHibernate!