在用NHibernate碰到一个级联操作问题,在各位大侠的帮助下总算是给搞清楚了,好了废话不说,看问题。
Customer对多个Order,典型的一对多关系,代码如下,不多讲。
1
public
class
Customer
2
{
3
ISet
<
Order
>
_orders
=
new
HashedSet
<
Order
>
();
4
5
public
virtual
int
CustomerID {
get
;
set
; }
6
public
virtual
string
CustomerName {
get
;
set
; }
7
public
virtual
ISet
<
Order
>
Orders
8
{
9
get
10
{
11
return
_orders;
12
}
13
set
14
{
15
_orders
=
value;
16
}
17
}
18
19
///
<summary>
20
///
添加一个订单
21
///
</summary>
22
///
<param name="o"></param>
23
///
<returns></returns>
24
public
virtual
int
AddOrder(Order o)
25
{
26
Orders.Add(o);
27
o.Customer
=
this
;
28
return
Orders.Count;
29
}
30
31
///
<summary>
32
///
移除一个订单
33
///
</summary>
34
///
<param name="o"></param>
35
///
<returns></returns>
36
public
virtual
int
RemoveOrder(Order o)
37
{
38
Orders.Remove(o);
39
return
Orders.Count;
40
}
41
}
42
43
///
<summary>
44
///
订单
45
///
</summary>
46
public
class
Order
47
{
48
public
virtual
int
OrderID {
get
;
set
; }
49
public
virtual
int
ProductNum {
get
;
set
; }
50
public
virtual
Customer Customer {
get
;
set
; }
51
}
刚开始的时候,我设置 inverse="false",在保存Customer的时候,NH自动为我保存了Order,感觉很方便,后来删除Customer的时候,引发了一个异常,说是外键不允许为空,很奇怪,我删除数据怎么会让外键为空?后来发现了问题:没看到NH输出delete语句,只看到有两条将外键更新为空的update语句,但是奇怪的是数据却被删掉了,为了搞清楚这个问题,于是有了下文。后来在李永京的建议下,使用NHProfiler监视,发现了NH生成的delete语句,看来是testDriver.Net的问题,漏掉了某些输出的sql语句。
关于级联删除,nh在级联删除的时候总是先Update,然后在delete,这是因为inverse属性设置的原因,我们着重看看这个属性。
我们先让inverse默认为false,使用如下代码创建Customer。
1
[Test]
2 public void CreateCustomer()
3 {
4 Customer cus =new Customer();
5 cus.CustomerName ="test";
6 cus.Orders.Add(new Order { ProductNum =1 });
7 cus.Orders.Add(new Order { ProductNum =2 });
8 NHibernateHelper.EncloseInTransaction(s =>
9 {
10 s.Save(cus);
11 });
12 }
第四行是一个自定义的执行事务的方法,直接粘贴李永京写的。
1
public
static
void
EncloseInTransaction(Action
<
ISession
>
work)
2
{
3
if
(sessionFactory
==
null
)
4
{
5
Configuration cfg
=
new
Configuration();
6
cfg
=
cfg.Configure();
7
sessionFactory
=
cfg.BuildSessionFactory();
8
}
9
using
(var s
=
sessionFactory.OpenSession())
10
{
11
using
(var tx
=
s.BeginTransaction())
12
{
13
try
14
{
15
work(s);
16
tx.Commit();
17
}
18
catch
19
{
20
tx.Rollback();
21
throw
;
22
}
23
}
24
}
25
}
注意,这里我们直接使用ISet的Add方法加入订单,我们看看NH生成的语句:
1
NHibernate:
INSERT
INTO
[
Customer
]
(CustomerName)
VALUES
(
@p0
);
select
SCOPE_IDENTITY
();
@p0
=
'
test
'
2
NHibernate:
INSERT
INTO
[
Order
]
(ProductNum, CustomerID)
VALUES
(
@p0
,
@p1
);
select
SCOPE_IDENTITY
();
@p0
=
1
,
@p1
=
NULL
3
NHibernate:
INSERT
INTO
[
Order
]
(ProductNum, CustomerID)
VALUES
(
@p0
,
@p1
);
select
SCOPE_IDENTITY
();
@p0
=
2
,
@p1
=
NULL
4
NHibernate: Batch commands:
5
command
0
:
UPDATE
[
Order
]
SET
CustomerID
=
@p0
WHERE
OrderID
=
@p1
;
@p0
=
6
,
@p1
=
11
6
command
1
:
UPDATE
[
Order
]
SET
CustomerID
=
@p0
WHERE
OrderID
=
@p1
;
@p0
=
6
,
@p1
=
12
有什么感觉?先插入为两条外键为空的记录,然后在用update更新外键,很别扭吧,如果你的数据库外键不允许为空,还会引发异常。先别急,接着我们看看删除。用以下代码删除一个客户 :
1[Test]
2public void DeleteCustomer()
3{
4 NHibernateHelper.EncloseInTransaction(s =>
5 {
6 Customer cus = s.Load<Customer>(1);
7 s.Delete(cus);
8 });
9}
看看NH生成的代码:
1
NHibernate:
SELECT
customer0_.CustomerID
as
CustomerID1_0_, customer0_.CustomerName
as
Customer2_1_0_
FROM
[
Customer
]
customer0_
WHERE
customer0_.CustomerID
=
@p0
;
@p0
=
2
2
NHibernate:
SELECT
orders0_.CustomerID
as
CustomerID1_, orders0_.OrderID
as
OrderID1_, orders0_.OrderID
as
OrderID0_0_, orders0_.ProductNum
as
ProductNum0_0_, orders0_.CustomerID
as
CustomerID0_0_
FROM
[
Order
]
orders0_
WHERE
orders0_.CustomerID
=
@p0
;
@p0
=
2
3
NHibernate:
UPDATE
[
Order
]
SET
CustomerID
=
null
WHERE
CustomerID
=
@p0
;
@p0
=
2
4
NHibernate: Batch commands:
5
command
0
:
DELETE
FROM
[
Order
]
WHERE
OrderID
=
@p0
;
@p0
=
3
6
command
1
:
DELETE
FROM
[
Order
]
WHERE
OrderID
=
@p0
;
@p0
=
4
7
8
NHibernate: Batch commands:
9
command
0
:
DELETE
FROM
[
Customer
]
WHERE
CustomerID
=
@p0
;
@p0
=
2
10
我们注意第三行的那个语句,发现什么了?原来NH先把子对象的外键置空,然后才去删除子对象,最后才去删除父对象。 这显然不是我们想要的,在插入的时候,我们希望是三条insert,删除的时候能不能不要那个update,有的时候外键不允许为空还会引发异常!
当设置inverse="true时,我们应该使用 Customer的AddOrder方法(代码见Customer实体类)来添加订单,如果你还是直接调用Orders属性的话,那么订单对象的Customer属性将会为空,那么插入到数据库中后外键也是空(可以把后面的代码下载下来改一下试试)! 我们修改一下创建客户的代码:
1
[Test]
2 public void CreateCustomer()
3 {
4 Customer cus =new Customer();
5 cus.CustomerName ="test";
6 cus.AddOrder(new Order { ProductNum =1 });
7 cus.AddOrder(new Order { ProductNum =2 });
8 NHibernateHelper.EncloseInTransaction(s =>10 {
9 s.Save(cus);
10 });
11 }
注意第六第七行,运行测试,我们看看输出的sql语句,正式我们想要的形式了。
当 inverse="true"时输出的sql语句
NHibernate:
INSERT
INTO
[
Customer
]
(CustomerName)
VALUES
(
@p0
);
select
SCOPE_IDENTITY
();
@p0
=
'
test
'
NHibernate:
INSERT
INTO
[
Order
]
(ProductNum, CustomerID)
VALUES
(
@p0
,
@p1
);
select
SCOPE_IDENTITY
();
@p0
=
1
,
@p1
=
4
NHibernate:
INSERT
INTO
[
Order
]
(ProductNum, CustomerID)
VALUES
(
@p0
,
@p1
);
select
SCOPE_IDENTITY
();
@p0
=
2
,
@p1
=
4
我们在看看删除输出的sql语句,是不是没有那个update?
1
NHibernate: SELECT customer0_.CustomerID as CustomerID1_0_, customer0_.CustomerName as Customer2_1_0_ FROM [Customer] customer0_ WHERE customer0_.CustomerID=@p0;@p0 = 3
2
NHibernate: SELECT orders0_.CustomerID as CustomerID1_, orders0_.OrderID as OrderID1_, orders0_.OrderID as OrderID0_0_, orders0_.ProductNum as ProductNum0_0_, orders0_.CustomerID as CustomerID0_0_ FROM [Order] orders0_ WHERE orders0_.CustomerID=@p0;@p0 = 3
3
NHibernate: Batch commands:
4
command 0:DELETE FROM [Order] WHERE OrderID = @p0;@p0 = 5
5
command 1:DELETE FROM [Order] WHERE OrderID = @p0;@p0 = 6
6
7
NHibernate: Batch commands:
8
command 0:DELETE FROM [Customer] WHERE CustomerID = @p0;@p0 = 3
讲到这里,这个inverse="true"算是清楚了,另外就是提醒下大家在分析NH性能的时候,最好使用NHProfiler来做,别跟我一样闷着头浪费很长时间。
结论:inverse 表明的是关联的方向,设置为 true 的被认为是关系的反向端, 设置为 false的被认为是关系的正向,从这个比较中可以看出,对映射关系做修 改(新增、删除)必须在主映射端完成,否则无法保存到数据库中。因此,在双向的one-to-many和many-to-many映射中,必须清楚哪个主映射端、哪个是反向端。
没什么内容,就是一点学习积累。第一次发文章,请拍砖!
源码下载 ,欢迎转载,但请注明出处 NHibernate 备忘(一) 级联问题
作者:燕之威
出处:http://www.cnblogs.com/smartcoder
本文采用知识共享Attribution-NonCommercial-ShareAlike 2.5 China Mainland许可协议进行许可,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。