本节内容
引入
上一篇,怎么使用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元素下添加节点,调用CustomerInsert存储过程,CustomerInsert 存储过程有四个参数,这里用四个问号表示:- <sql-insert>exec CustomerInsert ?,?,?,?</sql-insert>
复制代码 Step3:执行CreateCustomerTest()测试方法,出现错误“NHibernate.Exceptions.GenericADOException : could not insert: [DomainModel.Entities.Customer][SQL: <sql-insert>exec CustomerInsert ?,?,?,?</sql-insert>];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'中的标识列(主键)插入显式值(标识增量加一)。- [/code]Step6:[b]执行CreateCustomerTest()测试方法[/b]成功,生成SQL如下,其中p0是Version,p3是CustomerId
- [code]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元素下添加节点,调用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>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!
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |