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对象,并不立 ...
随机推荐
- CSS中相对定位与绝对定位
看了几个讲解定位的博客,觉得还不错,分享之: 博客一:http://blog.sina.com.cn/s/blog_4bcf4a5e010008o0.html 文章中,主要需要参考的有两点: 1,相对 ...
- RSA非对称加密
先上RSA加密算法的一些简介(截图自轩辕老师的课件): 嗯--RSA就是这么一回事,于是有了如下题目: 1.In an RSA system, the public key of a given us ...
- hdu 2095
ps:真是日了狗...英语渣渣理解题目不行,开了个100W数组来算,还优化了下时间,还是超时了,看了题解才知道用异或. N个数异或,会得出其中是奇数的一个.比如 1^1^3^2^2 = 3. 1^ ...
- DIY FSK RFID Reader
This page describes the construction of an RFID reader using only an Arduino (Nano 3.0 was tested, b ...
- HDU5128 细心、细心、细心
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5128 题意:给你n(n < 30)个点的坐标,然后让你求出这n个点能构成的两个最大矩形的面积,有 ...
- UVA 208 (DFS)
题意:找出1到T的所有路径: 坑点:一开始以为是到终点,读错了题意,没测试第二个样例,结果WA了4遍,坑大了: #include <iostream> #include <cmath ...
- linux,python 常用的处理log的命令
一般的log文件都是需要过滤 ps:管道符| 管道符前面的输出值 grep 过滤查找 将是error的log过滤显示 grep '221.2.100.138' web.access.log gr ...
- 16年青岛网络赛 1002 Cure
题目链接:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1002&cid=723 Cure Time Limit: 30 ...
- 快速理解Java中的五种单例模式
解法一:只适合单线程环境(不好) package test; /** * @author xiaoping * */ public class Singleton { private static S ...
- Apache配置代理服务器的方法(1)
众所周知Apache是目前最优秀的HTTP服务器.实际上它不仅能当作服务器使用,也能够被用来架设代理服务器.本文就如何使用Apache架设HTTP代理服务器进行说明. 本文将基于Win32版的Apac ...