加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > asp.Net > 正文

NHibernate中关于Inverse的理解和使用

发布时间:2020-12-15 21:16:14 所属栏目:asp.Net 来源:网络整理
导读:在项目中NHibernate进行ORMapping,操作数据库变得非常简单,但是NHibernate中有很多特性不是很容易理解,比如Inverse这个功能就是其中的一个。 在使用NHibernate进行数据库操作的时候,比如数据插入的时候,经常用到级联功能,比如最常见的就是一个订单对应

在项目中NHibernate进行ORMapping,操作数据库变得非常简单,但是NHibernate中有很多特性不是很容易理解,比如Inverse这个功能就是其中的一个。

在使用NHibernate进行数据库操作的时候,比如数据插入的时候,经常用到级联功能,比如最常见的就是一个订单对应多个明细行,在保存订单时只需要Save订单对象即可,订单下的所有明细行会级联保存。在对象模型层面,Order对象中有个属性IList Items,对应其中的订单明细OrderItem。对于OrderItem对象,其中可以没有Order对象的引用,如果有Order对象的引用,那么就是双向关联Bidirectional!

对于Bidirectional的情况,那么在保存数据到数据库时就会涉及到一个问题,如果两边的数据不一致,也就是mismatch,到底是以Order中的Items为准还是以OrderItem中的Order为准?NHibernate Cookbook中是这样说的:

To work around this mismatch,NHibernate ignores one side of the bidirectional relationship. The foreign key in the database is populated based on either the OrderItems reference to the? Order or the? Orders collection of? OrderItems,but not both. We determine which end of the relationship controls the foreign key using the inverse attribute on the collection. By default,the Order controls the foreign key. Saving a new? Order with one OrderItem will result in the following three SQL statements:

 "" (Id)  (@p0) 
INSERT  OrderItem (Id)  (@p0) 
 OrderItem  OrderId = @p0  Id = @p1 

When we specify inverse="true",the OrderItem controls the foreign key. This is preferable because it eliminates the extra? UPDATE statement,resulting in the following?? two SQL statements:

<pre class="csharpcode">INSERT <span class="kwrd">INTO "<span class="kwrd">Order" (Id) <span class="kwrd">VALUES (@p0)
INSERT <span class="kwrd">INTO OrderItem (OrderId,Id) <span class="kwrd">VALUES (@p0,@p1)

大体意思就是,NHibernate默认使用Order的属性作为有效的关联,换句话说,只需要把OrderItem一个个的加入到Order的Items集合即可,最终结果不需要关心OrderItem中引用的Order到底是什么或者为空。如果在Mapping配置Order的Item时设置inverse="true",那么NHibernate就会使用OrderItem的Order引用作为关联。

SQL语句上可以看到明细的区别,在默认Inverse为false的情况下,在保存OrderItem时,其数据库的字段OrderId是设为null,然后再将Order的Id重新Update到OrderItem中。

【注意:这里是说最终结果,而不是中间结果,在Insert OrderItem的时候,其OrderId为该对象对应的Order对象的Id,如果该Order对象未保存,则OrderId为null,如果是已保存的,则是该Order的Id,然后接下来会更新该OrderId。】

接下来举一个具体的例子,部门和员工,一对多关系,部门D1,D2,员工U1和U2,D1的Users里面有U1和U2,U1对象引用D1,U2对象引用D2。

<pre class="csharpcode">Department d1=<span class="kwrd">new Department(){Name = <span class="str">"D1"};
Department d2=<span class="kwrd">new Department(){Name = <span class="str">"D2"};
User u1=<span class="kwrd">new User(){Name = <span class="str">"U1",Department = d1};
User u2=<span class="kwrd">new User(){Name = <span class="str">"U2",Department = d2};
d1.Users=<span class="kwrd">new List(){u1,u2};

默认不设置Inverse的情况下如果先保存d1,后保存d2,会生成如下的SQL:

<pre class="csharpcode">NHibernate: INSERT <span class="kwrd">INTO DEPARTMENT (NAME,DEPARTMENT_ID) <span class="kwrd">VALUES (@p0,@p1);@p0 = <span class="str">'D1' [Type: String (0)],@p1 = 100000000100000 [Type: Int64 (0)]
NHibernate: INSERT <span class="kwrd">INTO <span class="kwrd">USER (NAME,DEPARTMENT_ID,USER_ID) <span class="kwrd">VALUES (@p0,@p1,@p2);@p0 = <span class="str">'U1' [Type: String (0)],@p1 = 100000000100000 [Type: Int64 (0)],@p2 = 100000000100000 [Type: Int64 (0)]
NHibernate: INSERT <span class="kwrd">INTO <span class="kwrd">USER (NAME,@p2);@p0 = <span class="str">'U2' [Type: String (0)],@p1 = <span class="kwrd">NULL [Type: Int64 (0)],@p2 = 100000000100001 [Type: Int64 (0)]
NHibernate: INSERT <span class="kwrd">INTO DEPARTMENT (NAME,@p1);@p0 = <span class="str">'D2' [Type: String (0)],@p1 = 100000000100001 [Type: Int64 (0)]
NHibernate: <span class="kwrd">UPDATE <span class="kwrd">USER <span class="kwrd">SET NAME = @p0,DEPARTMENT_ID = @p1 <span class="kwrd">WHERE USER_ID = @p2;@p0 = <span class="str">'U2' [Type: String (0)],@p1 = 100000000100001 [Type: Int64 (0)],@p2 = 100000000100001 [Type: Int64 (0)]
NHibernate: <span class="kwrd">UPDATE <span class="kwrd">USER <span class="kwrd">SET DEPARTMENT_ID = @p0 <span class="kwrd">WHERE USER_ID = @p1;@p0 = 100000000100000 [Type: Int64 (0)],@p1 = 100000000100000 [Type: Int64 (0)]
NHibernate: <span class="kwrd">UPDATE <span class="kwrd">USER <span class="kwrd">SET DEPARTMENT_ID = @p0 <span class="kwrd">WHERE USER_ID = @p1;@p0 = 100000000100000 [Type: Int64 (0)],@p1 = 100000000100001 [Type: Int64 (0)]

仔细分析这些SQL语句,就会发现在insert保存U1时,其DepartmentId是有值的,而Insert保存U2时,其DepartmentId是null,这是因为D2现在还没有保存到数据库,没有Id,所以插入Null,接下来是保存D2,在保存了D2后有了Id,那么就需要更新U2的DepartmentId,让其等于D2的Id。以上都是插入过程,接下来还要进行外键更新操作,保证数据库中的外键与对象中Department中设置的Users保持一致,所以Update每个User表即可。

如果是改为Inverse=True,那么然后保存d1和d2,那么对应的SQL是:

<pre class="csharpcode">NHibernate: INSERT <span class="kwrd">INTO DEPARTMENT (NAME,@p2 = 100000000100001 [Type: Int64 (0)]

可以看出,最大的不同是没有了最后两句更新外键的SQL。如果我们再调整下保存的顺序,先保存D2,然后再保存D1,那么对应的SQL是:

<pre class="csharpcode">NHibernate: INSERT <span class="kwrd">INTO DEPARTMENT (NAME,@p1 = 100000000100000 [Type: Int64 (0)]
NHibernate: INSERT <span class="kwrd">INTO DEPARTMENT (NAME,@p1 = 100000000100001 [Type: Int64 (0)]
NHibernate: INSERT <span class="kwrd">INTO <span class="kwrd">USER (NAME,@p2 = 100000000100001 [Type: Int64 (0)]

显然第一种SQL语句进行了外键的update操作,没有第二三次的效率高,而且,必须要设置数据库中OrderItem的OrderId允许为空。从数据库模型来说,这个不合理啊!

所以一般建议在Mapping时设置Inverse为True。对应的,在Code中也需要设置OrderItem对Order的引用。

Inverse更大的用处是在ManyToMany的时候。如果两边Inverse=False的情况下,ManyToMany是任意一边设置集合并保存就有效,如果两边都设置的话,会保存多次。比如有员工E1和E2,奖品A1和A2,其是多对多关系,如果要设置E1员工获得A1和A2奖,那么需要设置各自的集合:

<div class="csharpcode">

 Emp e1= Emp(){Name = };
 Emp e2 =  Emp() { Name =  };
 Award a1= Award(){Name = };
 Award a2 =  Award() { Name =  };
 e1.Awards= List(){a1,a2};
 a1.Emps= List(){e1};
 a2.Emps =  List() { e1 };

从DomainModel来说,这样设置是对的,但是生成SQL却有问题:

<div class="csharpcode">

NHibernate: INSERT  EMP (NAME,EMP_ID)  (@p0,@p1);@p0 =  [Type: String (0)],@p1 = 1000000001 [Type: Int64 (0)]
NHibernate: INSERT  AWARD (NAME,AWARD_ID)  (@p0,@p1);@p0 =  [Type: String (0)],@p1 = 1000000001 [Type: Int64 (0)]
NHibernate: INSERT  AWARD (NAME,@p1);@p0 =  [Type: String (0)],@p1 = 1000000002 [Type: Int64 (0)]
NHibernate: INSERT  EMP (NAME,@p1);@p0 =  [Type: String (0)],@p1 = 1000000002 [Type: Int64 (0)]
NHibernate: INSERT  AWARD_EMP (EMP_ID,@p1);@p0 = 1000000001 [Type: Int64 (0)],@p1 = 1000000001 [Type: Int64 (0)]
NHibernate: INSERT  AWARD_EMP (EMP_ID,@p1 = 1000000002 [Type: Int64 (0)]
NHibernate: INSERT  AWARD_EMP (AWARD_ID,@p1 = 1000000001 [Type: Int64 (0)]
NHibernate: INSERT  AWARD_EMP (AWARD_ID,@p1);@p0 = 1000000002 [Type: Int64 (0)],@p1 = 1000000001 [Type: Int64 (0)]

明明应该是往中间表插入2条记录的,但是这样5-8行却变成了插入4条记录。如果中间表设置了联合主键,那么必然会报错,插入失败。

这个时候可以在Award端设置Inverse=True,Emp端设置Inverse=False,表示其多对多关系不在Award方维护,只在Emp端维护:

<pre class="csharpcode"><span class="kwrd">public <span class="kwrd">class AwardMapping : IAutoMappingOverride
{
<span class="kwrd">public <span class="kwrd">void Override(AutoMapping mapping)
{
mapping.HasManyToMany(a => a.Emps).Inverse();
}
}
<span class="kwrd">public <span class="kwrd">class EmpMapping : IAutoMappingOverride
{
<span class="kwrd">public <span class="kwrd">void Override(AutoMapping mapping)
{
mapping.HasManyToMany(a => a.Awards).Not.Inverse();
}
}

这样设置了Mapping后,就可以生成正确的SQL语句,当然如果把C#代码中的6行和7行去掉,结果也是正确的,因为现在系统只认Emp中的Awards集合了。但是如果删除第5行,保留6-7行则不行。

总结:

Inverse用于设置双向关联时Nhibernate在设置外键时依赖的对象,默认Inverse=False,一对多时表示依赖一端的集合,如果为True表示依赖多段对象中对一端对象的引用。

多对多时不能让两端的Inverse为False,这样会造成数据的重复插入;必须设置一端为False,一端为True。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读