NHibernate之旅(16):探索NHibernate中使用存储过程(中)
2008-11-06 16:40 李永京 阅读(...) 评论(...) 编辑 收藏本节内容
- 引入
- 实例分析
- 2.创建对象
- 3.更新对象
- 结语
引入
上一篇,怎么使用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>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>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>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>
结语
这一篇和上一篇介绍了如何使用存储过程删除对象、创建对象、更新对象,还有一种使用
本系列链接:NHibernate之旅系列文章导航
NHibernate Q&A- 欢迎加入NHibernate中文社区,一起讨论NHibernate知识!
- 请到NHibernate中文社区下载本系列相关源码。
下次继续分享NHibernate!