[Nhibernate] one-to-many
一对多。典型的Department和Employee. 二者关系一对多(一个部门下有很多员工)
SQL建立脚本:
GO
/****** Object: Table [dbo].[Emp] Script Date: 10/08/2011 13:59:11 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Emp](
[EmpId] [int] IDENTITY(1,1) NOT NULL,
[EmpName] [varchar](50) NOT NULL,
[DepId] [int] NULL,
CONSTRAINT [PK_Emp] PRIMARY KEY CLUSTERED
(
[EmpId] 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
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Emp] WITH CHECK ADD CONSTRAINT [FKD9930B51E340AA6] FOREIGN KEY([DepId])
REFERENCES [dbo].[Dep] ([DepId])
GO
ALTER TABLE [dbo].[Emp] CHECK CONSTRAINT [FKD9930B51E340AA6]
USE [ziyeNhiDB]
GO
/****** Object: Table [dbo].[Dep] Script Date: 10/08/2011 13:59:49 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Dep](
[DepId] [int] IDENTITY(1,1) NOT NULL,
[DepName] [varchar](50) NOT NULL,
CONSTRAINT [PK_Dep] PRIMARY KEY CLUSTERED
(
[DepId] 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
SET ANSI_PADDING OFF
建立好了随便插入点数据。Employee表中有一个Department外键。可以理解为两张表的关系是由employee表联系的。(后面中的inverse会提到)
两个实体类
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Iesi.Collections.Generic;
namespace ZiyeNhi.Entities
{
public class Employee
{
public virtual int EmpId { get; set; }
public virtual string EmpName { get; set; }
}
}
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Iesi.Collections.Generic;
namespace ZiyeNhi.Entities
{
public class Department
{
public virtual int DepId { get; set; }
public virtual string DepName { get; set; }
/* 因为一个Department里有多个employee
* 这里用ISet集合
* 命名空间Iesi.Collections.Generic;
*/
private ISet<Employee> employees;
public virtual ISet<Employee> Employees
{
//至于这个get为什么要这么写。留给大家猜一猜。
get
{
if (employees == null)
{
employees = new HashedSet<Employee>();
}
return employees;
}
set { employees = value; }
}
}
}
接下来配置文件(XML要改成Embedded Resource)
<hibernate-mapping
xmlns="urn:nhibernate-mapping-2.2"
namespace="ZiyeNhi.Entities"
assembly="ZiyeNhi.Entities">
<class name="Employee" table="Emp">
<id name="EmpId" column="EmpId" type="int">
<generator class="identity">
</generator>
</id>
<property name="EmpName" column="EmpName" type="string" length="50"></property>
</class>
</hibernate-mapping>
<hibernate-mapping
xmlns="urn:nhibernate-mapping-2.2"
namespace="ZiyeNhi.Entities"
assembly="ZiyeNhi.Entities">
<class name="Department" table="Dep">
<id name="DepId" column="DepId" type="int">
<generator class="identity">
</generator>
</id>
<property name="DepName" column="DepName" type="string" length="50"></property>
<!--<one-to-many>-->
<!--这里有一个inverse="true",还有一个lazy="true" 要好好把握后面会介绍-->
<set name="Employees" table="Emp" inverse="true" lazy="true" >
<!--这key中的column到底是主表的主键,还是子表的外键。
自己试下就知道了。一般这两个键的名字都写一样的就懒得管到底是主键还是外键-->
<key column="DepId" ></key>
<!--一对多,class指向Employee-->
<one-to-many class="Employee"/>
</set>
</class>
</hibernate-mapping>
一对多的关系配完了,一个部门有N个Employee.
测试代码
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using NHibernate;
using ZiyeNhi.Entities;
using Iesi.Collections;
using Iesi.Collections.Generic;
namespace ZiyeNhi.Test
{
[TestFixture]
public class DepOneToManyEmp
{
private ISessionFactory sessionFactory;
[SetUp]
public void InitTest()
{
var cfg = new NHibernate.Cfg.Configuration().Configure("Config/hibernate.cfg.xml");
sessionFactory = cfg.BuildSessionFactory();
}
[Test]
public void SaveDepartmentInfo()
{
using (ISession session = this.sessionFactory.OpenSession())
{
var emp1 = new Employee() { EmpName = "Bill Gates" };
var emp2 = new Employee() { EmpName = "Paul Allen" };
var department = new Department() { DepName = "microsoft" };
emp1.Department = department;
emp2.Department = department;
ITransaction transaction = session.BeginTransaction();
try
{
session.Save(department);
session.Save(emp1);
session.Save(emp2);
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw ex;
}
}
}
[Test]
public void GetDepartmentInfo()
{
using (ISession session = this.sessionFactory.OpenSession())
{
var department = session.CreateQuery("from Department").List<Department>().FirstOrDefault();
Console.WriteLine("部门名称:{0}", department.DepName);
Console.WriteLine("部门人数:{0}", department.Employees.Count.ToString());
if (department.Employees.Count > 0)
{
foreach (Employee emp in department.Employees)
{
Console.WriteLine("员工姓名:{0}", emp.EmpName);
Console.WriteLine("部门数量:{0}", department.Employees.First().Department.DepName);
Console.WriteLine("-------------------");
}
}
}
}
}
}
先SAVE一些信息到数据库中。
如果不喜欢这样用。可以用(cascade).进行级联,但是要慎用。
然后我们来测试查询。
2条SQL语句
第一条取出了部门的信息。然后把部门的名字显示出来了
第二条查出了部门里的Employee信息和count.然后把他们显示出来。
下面我把测试代码变一下。
public void GetDepartmentInfo()
{
using (ISession session = this.sessionFactory.OpenSession())
{
var department = session.CreateQuery("from Department").List<Department>().FirstOrDefault();
Console.WriteLine("部门名称:{0}", department.DepName);
//Console.WriteLine("部门人数:{0}", department.Employees.Count.ToString());
//if (department.Employees.Count > 0)
//{
// foreach (Employee emp in department.Employees)
// {
// Console.WriteLine("员工姓名:{0}", emp.EmpName);
// Console.WriteLine("部门数量:{0}", department.Employees.First().Department.DepName);
// Console.WriteLine("-------------------");
// }
//}
}
}
结果
只显示一条语句就是查询Department信息。
那肯定啊,因为没有查询Employee的信息 他怎么会生成2条SQL语句呢。
在把配置文件改一下:
<hibernate-mapping
xmlns="urn:nhibernate-mapping-2.2"
namespace="ZiyeNhi.Entities"
assembly="ZiyeNhi.Entities">
<class name="Department" table="Dep">
<id name="DepId" column="DepId" type="int">
<generator class="identity">
</generator>
</id>
<property name="DepName" column="DepName" type="string" length="50"></property>
<!--<one-to-many>-->
<set name="Employees" table="Emp" inverse="true" lazy="false" >
<key column="DepId" ></key>
<one-to-many class="Employee"/>
</set>
</class>
</hibernate-mapping>
lazy改为了false: 编译测试。
怎么样 是2条吧。
lazy="true",就是说启用了department下的employee属性为延迟加载。当我们调用这个属性的时候才加载;
比如:
我们调用了department.Employees.Count.ToString();这个时候才加载
如果设置为lazy="false",无论你调用或者没有调用它都会加载。 这玩意高级。
好了。下面我们在来改一下。
public void GetDepartmentInfo()
{
var department = GetDepartment();
Console.WriteLine("部门名称:{0}", department.DepName);
//Console.WriteLine("部门人数:{0}", department.Employees.Count.ToString());
}
public Department GetDepartment()
{
using (ISession session = this.sessionFactory.OpenSession())
{
var department = session.CreateQuery("from Department").List<Department>().FirstOrDefault();
return department;
}
}
先把部门人数注释了。运行结果
OK 没问题
然后把注释去掉,获取部门名称和人数。
结果
部门名称显示出来了。但是Count没有加载出来。
语句太长我把没显示出来的信息贴出来。
NHibernate: select department0_.DepId as DepId3_, department0_.DepName as DepName3_ from Dep department0_
部门名称:microsoft
14:10:13,148 ERROR [TestRunnerThread] LazyInitializationException [(null)]- Initializing[ZiyeNhi.Entities.Department#185]-failed to lazily initialize a collection of role: ZiyeNhi.Entities.Department.Employees, no session or session was closed
NHibernate.LazyInitializationException: Initializing[ZiyeNhi.Entities.Department#185]-failed to lazily initialize a collection of role: ZiyeNhi.Entities.Department.Employees, no session or session was closed
就是说session已经关闭了。为什么呢。因为我们通过
{
using (ISession session = this.sessionFactory.OpenSession())
{
var department = session.CreateQuery("from Department").List<Department>().FirstOrDefault();
return department;
}
}
这个方法来获取Department的 获取到的对象session就关闭了(用了using)
那么我在调用它的Employee的时候就会失效。session关闭;lazy失效。如果把lazy="true"设置为lazy="false",那是没有问题的。
那意思就是说我们调用它的属性就必须在session没有关闭之前加载(调用它的属性)。
public void GetDepartmentInfo()
{
var department = GetDepartment();
Console.WriteLine("部门名称:{0}", department.DepName);
Console.WriteLine("部门人数:{0}", department.Employees.Count.ToString());
}
public Department GetDepartment()
{
using (ISession session = this.sessionFactory.OpenSession())
{
var department = session.CreateQuery("from Department").List<Department>().FirstOrDefault();
int count = department.Employees.Count;//这句
return department;
}
}
结果:
OK了吧。在session没有关闭之前我们加载他的属性就可以了。
如果在分层的情况下呢。业务层去调用数据层。难道还要在数据层把这些属性加载好?这样就达不到想要的效果了。
要么把session提到业务层?要么手动控制session的状态?
高手总是在最后出现->spring.net!