NHibernate系列文章十八:NHibernate关系之一对多(附程序下载)
摘要
这篇文章介绍NHibernate最实用的内容:关系映射。
NHibernate的关系映射方式有三种:
Set:无序对象集合,集合中每一个元素不能重复。
List:有序对象集合,集合中的元素可以重复。
Bag:无序对象集合,集合中的元素可以重复。
Map:键值对集合,相当于Hashtable或Dictionary。
这篇文章以一对多关系为例,介绍怎样在NHibernate中建立一对多关系映射。一对多关系是在现实项目中最经常碰到的一种关系。后面文章介绍多对多关系。
这篇文章的附件:NHibernate Demo下载。
1. 建立数据库关系
创建Order表

创建Order表的SQL语句:
USE [NHibernateDemoDB]
GO IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_Order_Customer]') AND parent_object_id = OBJECT_ID(N'[dbo].[Order]'))
ALTER TABLE [dbo].[Order] DROP CONSTRAINT [FK_Order_Customer]
GO USE [NHibernateDemoDB]
GO /****** Object: Table [dbo].[[Order]] Script Date: 06/28/2016 10:38:37 ******/
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Order]') AND type in (N'U'))
DROP TABLE [dbo].[Order]
GO USE [NHibernateDemoDB]
GO /****** Object: Table [dbo].[[Order]] Script Date: 06/28/2016 10:38:37 ******/
SET ANSI_NULLS ON
GO SET QUOTED_IDENTIFIER ON
GO CREATE TABLE [dbo].[Order](
[Id] [int] IDENTITY(1,1) NOT NULL,
[CustomerId] [int] NULL,
[Ordered] [datetime] NULL,
[Shipped] [datetime] NULL,
[Street] [nvarchar](100) NULL,
[City] [nvarchar](100) NULL,
[Province] [nvarchar](100) NULL,
[Country] [nvarchar](100) NULL,
CONSTRAINT [PK_Order] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] GO ALTER TABLE [dbo].[Order] WITH CHECK ADD CONSTRAINT [FK_Order_Customer] FOREIGN KEY([CustomerId])
REFERENCES [dbo].[Customer] ([Id])
GO ALTER TABLE [dbo].[Order] CHECK CONSTRAINT [FK_Order_Customer]
GO
Customer表和Order表的关系:

2. 创建Order类,修改Customer类
Order类:
using System; namespace Demo.XML.Entities.Domain
{
public class Order
{
public virtual int Id { get; set; }
public virtual DateTime Ordered { get; set; }
public virtual DateTime? Shipped { get; set; }
public virtual Address ShipTo { get; set; }
public virtual Customer Customer { get; set; }
}
}
Customer属性表示一个Order对象从属于一个Customer对象。
Customer类:
using System;
using System.Collections.Generic; namespace Demo.XML.Entities.Domain
{
public class Customer
{
public Customer()
{
MemberSince = DateTime.UtcNow;
Orders = new HashSet<Order>();
} public virtual int Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual double AverageRating { get; set; }
public virtual int Points { get; set; }
public virtual bool HasGoldStatus { get; set; }
public virtual DateTime MemberSince { get; set; }
public virtual CustomerCreditRating CreditRating { get; set; }
public virtual Address Address { get; set; }
public virtual ISet<Order> Orders { get; set; } public virtual void AddOrder(Order order)
{
Orders.Add(order);
order.Customer = this;
}
} public enum CustomerCreditRating
{
Excellent, VeryVeryGood, VeryGood, Good, Neutral, Poor, Terrible
}
}
- 为Customer类添加了一个无参数的构造函数,为实体对象的属性赋默认值。
- 实体类的方法必须是virtual修饰的。
- Orders属性是ISet类型,表示一个Customer对象有一个Order集合。
- 这里示例使用的是双向关联,如果你愿意也可以写单向关联。
3. 添加Order.hbm.xml文件,修改Customer.hbm.xml文件
Order.hbm.xml:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Demo.XML.Entities" namespace="Demo.XML.Entities.Domain">
<class name="Order" table="`Order`">
<id name="Id">
<generator class="native"/>
</id>
<property name="Ordered"/>
<property name="Shipped"/>
<component name="ShipTo">
<property name="Street"/>
<property name="City"/>
<property name="Province"/>
<property name="Country"/>
</component>
<many-to-one name="Customer" column="CustomerId" cascade="save-update"/>
</class>
</hibernate-mapping>
在多的这一端使用:
<many-to-one name="属性名" column="属性对应的列名" />
这里的属性是Customer
Customer.hbm.xml:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Demo.XML.Entities" namespace="Demo.XML.Entities.Domain">
<class name="Customer" table="Customer">
<id name="Id">
<generator class="native"/>
</id>
<property name="FirstName" not-null="true"/>
<property name="LastName" not-null ="true"/>
<property name="AverageRating"/>
<property name="Points"/>
<property name="HasGoldStatus"/>
<property name="MemberSince"/>
<property name="CreditRating" type="CustomerCreditRating"/>
<component name="Address">
<property name="Street"/>
<property name="City"/>
<property name="Province"/>
<property name="Country"/>
</component>
<set name="Orders" table="`Order`" cascade="all-delete-orphan">
<key column="CustomerId"/>
<one-to-many class="Order"/>
</set>
</class>
</hibernate-mapping>
在一的这一端使用:
<set name="属性名" table="Many那端的表名">
<key column="Many那端的外键对应的列名"/>
<one-to-many class="Many那端的类名"/>
</set>
因为Customer类的Orders属性类型是ISet,所以这里使用Set。对应的,如果Orders类型是IList,则使用List。
cascade表示主从表的级联关系,有五个取值:
- none:没有任何级联操作
- all:对save、update和delete操作都产生级联
- delete:删除主表记录,级联删除从表记录
- save-update:对save和update操作产生级联
- all-delete-orphan:对save、update和delete操作都产生级联,并且在删除主表记录时删除从表的“孤立”(没有外键关联)记录。
在主表方的映射文件设置casade:主表的操作怎样关联到从表。
在从表方的映射文件设置casade:从表的操作怎样关联到主表。
如果不设置主从表的级联,则需要既要对主表的操作写Save/Update代码,又要对从表的操作写Save/Update代码,这样就非常麻烦。
4、添加ICustomerService接口、CustomerService类。
using Demo.Service.Infrastructure.Interface;
using Demo.XML.Entities.Domain; namespace Demo.Service.Interface
{
public interface ICustomerService : IService<Customer>
{
}
}
ICustomerService继承IService<Customer>泛型接口,继承了最常用的QueryAll/Save/Update/Delete等方法,还可以添加Customer业务自身的方法接口。
using Demo.Service.Interface;
using Demo.Service.Infrastructure;
using Demo.XML.Entities.Domain; namespace Demo.Service
{
public class CustomerService : Service<Customer>, ICustomerService
{
}
}
按照上面的接口和类,添加IOrderService接口和OrderService类。
5. 关联映射关系的添加、修改、删除
1)添加Customer对象,级联添加Order对象
using Demo.Service;
using Demo.Service.Interface;
using Demo.XML.Entities.Domain;
using System; namespace Demo.ConsoleApp
{
class Program
{
static readonly ICustomerService customerService = new CustomerService();
static readonly IOrderService orderService = new OrderService(); static void Main(string[] args)
{
HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize(); var newCustomer = CreateCustomer();
Console.WriteLine("New Customer:");
customerService.Save(newCustomer);
int id = newCustomer.Id; var list = customerService.GetAll(); Console.WriteLine("Completed");
Console.ReadLine();
} private static Customer CreateCustomer()
{
var customer = new Customer
{
FirstName = "Daniel",
LastName = "Tang",
Points = ,
HasGoldStatus = true,
MemberSince = new DateTime(, , ),
CreditRating = CustomerCreditRating.Good,
AverageRating = 42.42424242,
Address = new Address
{
Street = "123 Somewhere Avenue",
City = "Nowhere",
Province = "Alberta",
Country = "Canada"
}
}; var order1 = new Order
{
Ordered = DateTime.Now
};
customer.AddOrder(order1);
var order2 = new Order
{
Ordered = DateTime.Now.AddDays(-),
Shipped = DateTime.Now,
ShipTo = new Address
{
Street = "123 Somewhere Avenue",
City = "Nowhere",
Province = "Alberta",
Country = "Canada"
}
};
customer.AddOrder(order2); return customer;
}
}
}
执行程序,Customer表和Order表记录都添加成功。

2)从Customer对象上清除Orders,再添加Order对象
static void Main(string[] args)
{
HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize(); var customer = customerService.GetById();
if (customer.Orders.Count > )
{
customer.Orders.Clear();
}
customer.AddOrder(new Order {
Ordered = DateTime.Now.AddDays(-),
Shipped = DateTime.Now,
ShipTo = new Address
{
Street = "Zhuhai Road",
City = "Zhuhai",
Province = "Guangdong",
Country = "China"
}
});
customerService.Update(customer); Console.WriteLine("Completed");
Console.ReadLine();
}
执行程序,得到监控结果。先插入新的Order记录,然后修改数据库内三条Order记录的CustomerId值,已存在的两条Order记录的CustomerId设置成null,新添加的这一条Order记录设置CustomerId为Customer主键值。最后删除之前的两条Order记录。

3)删除Customer对象
static void Main(string[] args)
{
HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize(); customerService.Delete(); Console.WriteLine("Completed");
Console.ReadLine();
}
执行程序,得到监控结果。先修改Order记录的CustomerId为null,然后删除Order对象,最后删除Customer对象。

4)添加Order对象,级联添加Customer对象
static void Main(string[] args)
{
HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize(); var customer = CreateCustomer();
var order = new Order
{
Ordered = DateTime.Now
};
customer.AddOrder(order);
orderService.Save(order); Console.WriteLine("Completed");
Console.ReadLine();
}
执行程序,得到监控结果。先插入Customer对象,再插入三条Order对象,虽然插入的三条Order对象的CustomerId为Customer记录的主键值,但是还是在后面执行了三条修改CustomerId的update语句。

5)修改Order对象所属的Customer对象
手动向数据库Customer表添加一条Customer记录,假设新生成的记录的Id为3。
static void Main(string[] args)
{
HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize(); var customer = customerService.GetById();
var order = orderService.GetById();
order.Customer = customer;
orderService.Update(order); Console.WriteLine("Completed");
Console.ReadLine();
}
执行程序,得到监控结果。

5、inverse
通过监控看到上面的关联关系的添加修改删除操作执行了不少非必要的update操作。如果使用inverse属性,可以省去不少不必要的update执行语句。
修改Customer.hbm.xml文件。
<set name="Orders" table="`Order`" cascade="all-delete-orphan" inverse="true">
<key column="CustomerId"/>
<one-to-many class="Order"/>
</set>
重建数据库表Customer表和Order表,重新执行第四节的步骤1:“添加Customer对象,级联添加Order对象”,得到监控结果。

重新执行步骤2:“从Customer对象上清除Orders,再添加Order对象”,得到监控结果。

重新执行步骤3:“删除Customer对象”,得到监控结果。

重新执行步骤4:“添加Order对象,级联添加Customer对象”,得到监控结果。

重新执行步骤5:“修改Order对象所属的Customer对象”,得到监控结果。

因为在这个示例里,定义了双向关联,NHibernate需要维护双向之间的关联关系。所以没加inverse属性之前,添加修改删除生成了很多不必要的update语句。
“inverse=true”(默认值是false)的作用是让NHibernate忽略掉其中一个方向的关联,加在主表一方的时候,忽略从表到主表的关联,只维护主表到从表的关联,因此去掉了那些不必要的update语句。
读者也可以试着将“inverse=true”加在从表一方,看生成的监控结果是怎样的。
6、设置关系属性为null
1)修改Order对象的Customer属性为null。
var order = orderService.GetById();
order.Customer = null;
orderService.Update(order);
上面3行代码的结果是将Order对象的Customer属性设为null,执行后将Order表记录的CustomerId设置为null。
在添加Order记录的时候,设置Customer属性为空也是可以的,但是通常这没有什么意义。
也可以在Order.hbm.xml文件内定义Customer属性不能为空。如果为空,在Insert/Update时,会抛出异常。
<many-to-one name="Customer" column="CustomerId" cascade="save-update" not-null="true"/>
2)在Customer对象中的Orders集合调用Clear方法,然后Update这个Customer对象,此时将删除Customer对象关联的Order对象。
结语
这篇文章介绍了NHibernate关系映射概念,有四种集合可以实现关系映射:Set、Bag、List和Map。以1-many为例介绍了怎样在NHibernate映射文件和实体类中中建立1-many映射。用一个示例介绍了主从表的级联关系,级联关系中的添加修改删除操作。最后介绍了inverse属性以及作用,inverse的作用是在双向关联的关系中,让NHibernate忽略掉其中一个方向的关联,少执行很多不必要的update语句。
下一篇文章介绍NHibernate关系映射多对多映射。
NHibernate系列文章十八:NHibernate关系之一对多(附程序下载)的更多相关文章
- NHibernate系列文章十:NHibernate对象二级缓存下
摘要 上一节对NHibernate二级缓存做了简单介绍,NHibernate二级缓存是由SessionFactory管理的,所有Session共享.这一节介绍二级缓存其他两个方面:二级缓存查询和二级缓 ...
- WCF技术剖析之二十八:自己动手获取元数据[附源代码下载]
原文:WCF技术剖析之二十八:自己动手获取元数据[附源代码下载] 元数据的发布方式决定了元数据的获取行为,WCF服务元数据架构体系通过ServiceMetadataBehavior实现了基于WS-ME ...
- NHibernate系列文章十五:NHibernate组件
摘要 前面文章介绍了NHibernate对简单.net数据类型的映射对照表.NHibernate也可以映射复杂数据类型,这里介绍通过组件映射NHibernate值对象. 1. NHibernate引用 ...
- NHibernate系列文章十六:使用程序集管理NHibernate项目(附程序下载)
摘要 在实际的项目中,经常是将NHibernate的实体关系映射类做成独立的工程(assembly dll),只对外提供Session调用的接口.这个程序集作为数据访问层,可以被上面的多个工程(ASP ...
- NHibernate系列文章一:NHibernate介绍
摘要 NHibernate是一个成熟的开源的面向对象的.net映射框架.大量的实际项目中正在使用该框架.他是建立在ADO.Net基础之上.目前的版本是NHibernate 4.0.4.本系列文章都是基 ...
- NHibernate系列文章十七:NHibernate Session管理(附程序下载)
摘要 NHibernate的Session的管理涉及到NHibernate的两个最重要的对象ISessionFactory和ISession.ISessionFactory的生成非常消耗资源,通常都在 ...
- NHibernate系列文章二十:NHibernate关系之一对一(附程序下载)
摘要 NHibernate一对一关系虽然不经常碰到,但是在对于数据库结构优化的时候,经常会碰到一对一关系.比如,产品详细信息比较多的时候,可以把产品详细信息放到另一张表里面,Product主表只记录产 ...
- NHibernate系列文章十九:NHibernate关系之多对多关系(附程序下载)
摘要 NHibernate的多对多关系映射由many-to-many定义. 从这里下载本文的代码NHibernate Demo 1.修改数据库 添加Product表 添加ProductOrder表 数 ...
- NHibernate系列文章十二:Load/Get方法
摘要 NHibernate提供两个方法按主键值查找对象:Load/Get. 1. Load/Get方法的区别 Load: Load方法可以对查询进行优化. Load方法实际得到一proxy对象,并不立 ...
随机推荐
- linux上安装php+gd扩展
515 cd zlib-1.2.3 516 ./configure --prefix=/usr/local/zlib2 517 make && make install 518 cd ...
- Roman to Integer -- LeetCode 13
Given a roman numeral, convert it to an integer. Input is guaranteed to be within the range from 1 t ...
- C#异步批量下载文件
C#异步批量下载文件 实现原理:采用WebClient进行批量下载任务,简单的模拟迅雷下载效果! 废话不多说,先看掩饰效果: 具体实现步骤如下: 1.新建项目:WinBatchDownload 2.先 ...
- bigworld源码分析(3)——dbMgr分析
dbMgr主要是玩家数据的读取和保存的,例如在bigworld源码分析(3)中,玩家在认证的时候,loginApp需要通过dbMgr来验证玩家数据是否合法,这就是针对玩家的账号数据进行查询.本篇中,我 ...
- Neo4j Index Notes
Motivation GraphDatabasesBook: Robinson I., Webber J., Eifrem E. Graph Databases. 2013. 这本该是入门概念性质的书 ...
- IIS 发布网站 ashx无法访问
IIS6 问题 1.是否安装相应的.net版本 2.查看.net版本是否一致 3.查看web 服务扩展中.net版本是否允许. 4.添加相应的MIME类型文件 在IIS中右键网站→属性→主目录→配置→ ...
- HDU 1536 sg函数
S-Nim Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submi ...
- charles 结合mocky 模拟数据
重定向(模拟造数据) 例如:E代送商户端订单列表,模拟99+订单 接口:http://api.edaisong.com/20151022/order/consigneeaddressb 打开http: ...
- Java笔记1-Java相关概念和如何实现跨平台
一.Java相关概念 1.Java语言的核心特点跨平台面向对象 2.Java的历史版本JDK1.0,JDK1.1,JDK1.2....JDK5.0,JDK6.0,JDK7.0,JDK8.0 注意:JD ...
- python windows终端窗口下输出编码错误
windows简体中文版下终端默认字符集gbk,执行chcp 65001临时修改字符集. 修改默认字符集:注册表HKEY_CURRENT_USER\Console项中CodePage值修改为65001