一、典型设置
cascade:(默认为none)级联。指明哪些操作会从对象级联到关联的对象。
inverse: (默认为false) 标记这个集合作为双向关联关系中的方向一端。在双向关联时才需要设置。在设为false的一端对cascade进行维护。处于性能的考虑,一般在数据少的 一端或者被依赖端设置inverse="true",而让数据多的一段维护cascade。
1.one-to-one
1.1 数据库表结构
其中T_Person为主表,T_Employee为子表。T_Employee的PersonId参照T_Peson的PersonId。
1.2 示例映射类文件
1.3 示例映射文件
1.4 说明
constrained(约束): 表明该类对应的表对应的数据库表,和被关联的对象所对应的数据库表之间,通过一个外键引用对主键进行约束。这个选项影响Save()和Delete()在级联执行时的先后顺序(也在schema export tool中被使用)。
property-ref: (可选) 指定关联类的一个属性,这个属性将会和本外键相对应。如果没有指定,会使用对方关联类的主键。
- <generator class="foreign">:表示使用另外一个相关联的对象的标识符,来创建主健。T_Employee的PersonId来自T_Person的ParentId。
Employee依赖于Person,所以通常在Person设置cascade。
2.另一种one-to-one
2.1数据库表结构
其中T_Person1为主表,T_Employee1为子表。T_Employee1的PersonId设置唯一约束,参照T_Person1的PersonId。
2.2示例映射类文件
2.3示例映射文件
2.4说明
这种one-to-one实际上是一种特殊的one-to-many,如果T_Employee1的PersonId不设置唯一约束,则可成为 one-to-many。所以在T_Employee端设置many-to-one而不是one-to-one,记住要加上unique="true"表 示唯一约束。
3.one-to-many
3.1 数据库表结构
T_Parent为主表,T_Child的ParentId参照T_Parent的ParentId。
注意:对于单向的one-to-many映射,cascade过程中会用到把T_Child表的ParentId设置为Null,所以ParentId应设为允许NULL;
而双向one-to-many映射,建议把T_Child的ParentI设置为不允许NULL
3.2 示例映射类文件(单向)
3.3 示例映射文件(单向)
3.4 示例映射类文件(双向)
3.5 示例映射类文件(双向)
3.6 说明
在NHibernate配置文件中使用<set>, <list>, <map>, <bag>, <array> 和 <primitive-array>等元素来定义集合。<bag>是典型的一个,代码中我们用IList和它对应。我们以后会详 细讲集合这个话题。
lazy表示允许延迟加载。表示在需要使用时才加载需要的数据。例如使用lazy时我们Load一个Parent他的Children为空,只有我 们访问它的某一个Child时数据才会被加载;而不设置lazy我们Load一个Parent时其Children将同时加载。注意:使用lazy加载必 须保证对应ISession的打开,否则懒加载会失败。
one-to-many可以设置单向和双向映射,设置单向时Child一段不设置many-to-one,而设置了ParentId的属性。
双向映射需要设置inverse而单向不需要。
单项映射在cascade时会对把T_Child的ParentId使用Update为Null的操作。
建议尽量使用双向映射。
4.many-to-many
4.1 数据库表结构
4.2 示例映射类文件
4.3 示例映射文件
4.4 说明
many-to-many性能不佳,数据量大时应尽可能避免使用。并尽可能使用lazy="true"。
在数据量少的一端设置inverse="true",让数据量多的一段维护cascade。
二、cascade分析
1.总述
cascade:(默认为none)级联。指明哪些操作会从对象级联到关联的对象。
orphans:孤儿,没有夫对象的子对象。对于代码Child.Parent==null,对于数据库T_Child表中ParentId为Null的数据。
delete orphans表示cascade时删除孤儿。
一般系统中是不允许孤儿存在的,我们可以通过数据库的约束来限制孤儿,例如T_Child的ParentId设为Not NULL。
如果确实存在孤儿请考虑适合的cascade策略。
| cascade类型 | 对应操作 |
| all | Save / Delete / Update |
| all-delete-orphan | Save / Delete / Update + delete orphans |
| delete-orphan | Delete + delete orphans |
| none | No Cascades |
| delete | Delete |
| save-update | Save / Update |
2.one-to-one
a.初始化数据
| PersonId | Name | Job |
| 1 | DDL | 编程 |
| 2 | LLY | NULL |
b.测试方法
TestAddPersonWithAddEmployee
TestUpdatePersonWithAddEmployee
TestUpdatePersonWithUpdateEmployee
TestDeletePersonWithDeleteEmployee
c.测试结果
| 测试方法 | save-update | delete | delete-orphan | all | all-delete-orphan | none |
| TestAddPersonWithAddEmployee | Y | N | N | Y | Y | N |
| TestUpdatePersonWithAddEmployee | Y | N | N | Y | Y | N |
| TestUpdatePersonWithUpdateEmployee | Y | Y | Y | Y | Y | Y |
| TestDeletePersonWithDeleteEmployee | N | Y | Y | Y | Y | N |
2.另一种one-to-one
a.初始化数据
| PersonId | Name | EmployeeId | Job |
| 1 | DDL | 1 | 编程 |
| 2 | LLY | NULL | NULL |
b.测试方法
TestAddPersonWithAddEmployee
不支持
TestUpdatePersonWithAddEmployee
TestUpdatePersonWithUpdateEmployee
TestDeletePersonWithDeleteEmployee
c.测试结果
| 测试方法 | save-update | delete | delete-orphan | all | all-delete-orphan | none |
| TestAddPersonWithAddEmployee | 不支持 | 不支持 | 不支持 | 不支持 | 不支持 | 不支持 |
| TestUpdatePersonWithAddEmployee | Y | N | N | Y | Y | N |
| TestUpdatePersonWithUpdateEmployee | Y | Y | Y | Y | Y | Y |
| TestDeletePersonWithDeleteEmployee | N | Y | Y | Y | Y | N |
3.one-to-many(双向)
a.初始化数据
| ParentId | ParentName | ChildId | ChildName |
| 1 | Parent1 | 1 | Child1 |
| 2 | Parent2 | NULL | NULL |
b.测试方法
TestAddParentWithAddChild
TestUpdateParentWithAddChild
TestUpdateParentWithUpdateChild
TestDeleteParentWithChild
c.测试结果
| 测试方法 | save-update | delete | delete-orphan | all | all-delete-orphan | none |
| TestAddParentWithAddChild | Y | N | N | Y | Y | N |
| TestUpdateParentWithAddChild | Y | N | N | Y | Y | N |
| TestUpdateParentWithUpdateChild | Y | Y | Y | Y | Y | Y |
| TestDeleteParentWithChild | N | Y | Y | Y | Y | N |
4.one-to-many(单向)
a.初始化数据
同上
b.测试方法
TestAddParentWithAddChild
不支持
TestUpdateParentWithAddChild
TestUpdateParentWithUpdateChild
TestDeleteParentWithChild
c.测试结果
| 测试方法 | save-update | delete | delete-orphan | all | all-delete-orphan | none |
| TestAddParentWithAddChild | 不支持 | 不支持 | 不支持 | 不支持 | 不支持 | 不支持 |
| TestUpdateParentWithAddChild | Y | N | N | Y | Y | N |
| TestUpdateParentWithUpdateChild | Y | Y | Y | Y | Y | Y |
| TestDeleteParentWithChild | Parent被删除,Child成为孤儿 | Y | Y | Y | Y | Parent被删除,Child成为孤儿 |
5.many-to-many
many-to-many和别的关联映射有所不同。例子中:Role和User没有直接的依赖关系,而是通过一张中间表完成。在删除User时一般不会要求删除Role,而是删除之间的关系(即从中间表删除数据)。
a.初始化数据
| UserId | UserName | Password | RoleId | RoleName | |
| 1 | DDL | 1 | NULL | 1 | 角色1 |
| 1 | DDL | 1 | NULL | 2 | 角色2 |
| 2 | LLY | 2 | NULL | 1 | 角色1 |
| 3 | 陌生人 | 3 | NULL | NULL | NULL |
| NULL | NULL | NULL | NULL | 3 | 角色3 |
b.测试方法
TestAddRoleToUser
TestRemoveRoleFromUser
TestUpdateUserWithRole
TestDeleteUserWithSetRole
c.测试结果
| 测试方法 | save-update | delete | delete-orphan | all | all-delete-orphan | none |
| TestAddRoleToUser | Y | Y | Y | Y | Y | Y |
| TestRemoveRoleFromUser | Y | Y | Y | Y | Y | Y |
| TestUpdateUserWithRole | Y | Y | Y | Y | Y | Y |
| TestDeleteUserWithSetRole | N | Uer被删除,但和其有关的Role也被删除 | Uer被删除,但和其有关的Role也被删除 | Uer被删除,但和其有关的Role也被删除 | Uer被删除,但和其有关的Role也被删除 | N |
三、结论
关联是NHibernate里面功能很强的一块,但也使很容易滥用而造成引起性能或其他问题的地方。所以请慎重的使用。
在使用之前,请考虑好以下问题:
1.关系分析,一对多,多对多,还是一对一。
2.确定依赖关系,例如:Child依赖于Parent。
3.是否允许存在孤儿,例如:存在Child没有Parent(即在数据库中ParentId为Null的Child)。
4.在对等的关系中是否有主动端。例如:需要给Parent设置Child还是给Child找Parent,或者两边都可以操作。
5.关联两端可能出现的数据量以及在页面显示时是否分页。如果数据量大或者页面上需要分页显示,建议不要采用关联映射。如果数据量大,性能会不好,如果需要分页,关联所有数据似乎没有什么意思。
6.页面上可能的对象操作方法。例如:先读取一个Parent,然后添加Child,然后保存。
请根据你的情况设置关联映射,而对于cascade标准的设置可以满足绝大多数的需要,如一有特殊情况,请按照上面的分析选择合适的。