NHibernate的快速上手指引:https://nhibernate.info/doc/nhibernate-reference/quickstart.html
NHibernate开源项目:https://sourceforge.net/projects/nhibernate/files/NHibernate/
新建winform项目后,右击项目中的引用选择管理NuGet程序包,搜索NHibernate,然后安装即可。
这里需要注意一点,如果使用的是MS SqlServer 是不需要安装数据库驱动的系统自带了,如果是其他类型数据库需要安装数据库的驱动引用。
<configuration>
<configSections>
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate"/>
configSections>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<reflection-optimizer use="false" />
<session-factory name="sqlserver">
<property name="connection.provider">NHibernate.Connection.DriverConnectionProviderproperty>
<property name="connection.driver_class">NHibernate.Driver.Sql2008ClientDriverproperty>
<property name="dialect">NHibernate.Dialect.MsSql2008Dialectproperty>
<property name="connection.connection_string">
Data Source=192.168.1.31;Initial Catalog=SaleNew;User ID=sa;Password=7048461
property>
<property name="connection.isolation">ReadCommittedproperty>
<property name="default_schema">dboproperty>
<property name="use_outer_join">trueproperty>
<property name="hbm2ddl.auto">noneproperty>
<property name="show_sql">trueproperty>
<mapping assembly="TestNHibernate" />
session-factory>
hibernate-configuration>
configuration>
关于ISessionFactory配置这里给一个参考说明传送门
并发控制
更多参考
属性名 | 用途 |
---|---|
dialect | 设置NHibernate的Dialect类名 - 允许NHibernate针对特定的关系数据库生成优化的SQL 可用值: full.classname.of.Dialect, assembly |
default_schema | 在生成的SQL中, 将给定的schema/tablespace附加于非全限定名的表名上. 可用值: SCHEMA_NAME |
use_outer_join | 允许外连接抓取,已弃用,请使用max_fetch_depth 可用值: true 、false |
max_fetch_depth | 为单向关联(一对一, 多对一)的外连接抓取(outer join fetch)树设置最大深度. 值为0意味着将关闭默认的外连接抓取 可用值:建议在0 到3之间取值。 |
use_reflection_optimizer | 开启运行时代码动态生成来替代运行时反射机制(系统级属性). 使用这种方式的话程序在启动会耗费一定的性能,但是在程序运行期性能会有更好的提升. 注意即使关闭这个优化, Hibernate还是需要CGLIB. 你不能在hibernate.cfg.xml中设置此属性. 这个属性不能在hibernate.cfg.xml或者是应用程序配置文件hibernate-configuration 配置节中设置。 可用值: true、 false |
bytecode.provider | 指定字节码provider用于优化NHibernate反射性能。 null代表完全关闭性能优化, lcg用于轻量级的代码动态生成,codedom基于CodeDOM代码动态生成。 可用值: null 、 lcg 、codedom |
show_sql | 输出所有SQL语句到控制台. 可用值: true、 false |
hbm2ddl.auto | 在ISessionFactory创建时,自动检查数据库结构,或者将数据库schema的DDL导出到数据库. 使用 create-drop时,在显式关闭ISessionFactory时,将drop掉数据库schema. 可用值: create、 create-drop |
详细的属性配置可以参考这个文章。
数据库的ISessionFactory比较昂贵,单例模式获取一个就可以,下面给出三种常用获取ISessionFactory的方式。
var configuration = new Configuration();
//加载映射文件,它们用来把POCOs映射到持久化数据库对象
//加载指定程序集内的持久话类和映射文件
configuration.AddAssembly(typeof(Location).Assembly);
//创建会话工厂
ISessionFactory _sessionFactory = configuration.BuildSessionFactory();
var configuration = new Configuration();
configuration.Configure();
//创建会话工厂
ISessionFactory _sessionFactory = configuration.BuildSessionFactory();
可以将App.config删除,新建hibernate.cfg.xml文件
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.0" >
<session-factory name="MySessionFactory">
<property name="connection.provider">NHibernate.Connection.DriverConnectionProviderproperty>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriverproperty>
<property name="connection.connection_string">Server=.;initial catalog=NHB;User id=sa;password=1234;property>
<property name="show_sql">falseproperty>
<property name="dialect">NHibernate.Dialect.MsSql2000Dialectproperty>
<mapping assembly="NHB" />
session-factory>
hibernate-configuration>
如果自定义名为Users.cfg.xml配置文件,则需要用下面的语句:
var configuration = new Configuration();
configuration.Configure("Users.cfg.xml");
//添加指定mapping文件
configuration.AddXmlFile("Users.hbm.xml");
//创建会话工厂
ISessionFactory _sessionFactory = configuration.BuildSessionFactory();
将hibernate.cfg.xml,Users.cfg.xml的属性"复制到输出目录"改为"始终复制"
Configuration cfg = new Configuration().Configure("hibernate.cfg.xml");
如果一个项目加载多个数据库,则就可以使用上面配置程序进行多数据库会话工厂的生成
///
/// NHibernate会话工厂辅助类
///
public class NHibernateHelper
{
private static ISessionFactory _sessionFactory;
///
/// 获取SessionFactory
///
private static ISessionFactory SessionFactory
{
get
{
if (_sessionFactory == null)
{
var configuration = new Configuration();
// configuration.Configure().SessionFactoryName("sqlserver");
//加载映射文件,它们用来把POCOs映射到持久化数据库对象
configuration.AddAssembly(typeof(Location).Assembly);
//创建会话工厂
_sessionFactory = configuration.BuildSessionFactory();
}
return _sessionFactory;
}
}
///
/// 打开会话
///
///
public static ISession OpenSession()
{
return SessionFactory.OpenSession();
}
///
/// 关闭SessionFactory
///
public static void CloseSessionFactory()
{
if (_sessionFactory != null)
{
_sessionFactory.Close();
_sessionFactory = null;
}
}
}
public class Location
{
public virtual int id { get; set; }
public virtual int EmpId { get; set; }
public virtual DateTime Date { get; set; }
public virtual string Content { get; set; }
}
注意属性都是virtual
的定义的。
类名.hbm.xml
,如上面Location类的配置文件名称就是Location.hbm.xml
Location.hbm.xml
内容如下:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="TestNHibernate" namespace="TestNHibernate.mapping.pojo">
<class name="Location" table="Log" lazy="true">
<id name="id" column="FInterID">
<generator class="identity">generator>
id>
<property name="EmpId" type="int" column="FEmpID" not-null="true">property>
<property name="Date" type="DateTime" column="FDate" not-null="true">property>
<property name="Content" type="string" column="FContent" length="100" not-null="true">property>
class>
hibernate-mapping>
在这个配置文件中 name、type属性对应的是类或字段的名字及类型,table、column对应的是类或字段在数据库中对应的名称。
向数据库保存一条记录。
Location l = new Location() {EmpId=1,Date=DateTime.Now, Content="测试1"};
using (var session = SessionFactory.OpenSession())
{
using (ITransaction tx = session.BeginTransaction())
{
session.Save(l);
tx.Commit();
}
}
下面的代码替换到上面例子中事务提交前实现删除和更新。
//删除
session.Delete(t);
//更新
session.Update(t);
在程序运行过程中,使用对象的方式操作数据库的同时,必然会产生一系列的持久化对象。这些对象可能是刚刚创建并准备进行存储的,也有可能是从数据库进行查询得到的,为了区别这些对象,根据对象和当前Session的关联状态,可以将对象分为三种:
下面给出几个例子理解一下
例如:刚刚创建一个Customer对象,就是一个瞬时态的对象。
//瞬时态对象,只建立了没有向数据库中插入
var customer = new Customer()
{
ID = Guid.NewGuid(),
Name Address = new Name()
{
Address="北京",
Name="wolfy"
},
Orders=null,
Version=1
};
ISession session = NHibernateHelper.GetSession();
if (!session.Contains(customer))
{
//关联ISession保存到数据库中
session.Save(customer);
}
//变为持久态,由于表中Id字段自动增长的(如果是自动增长主键),保存数据库,Id字段自动加一
//经过NHibernate类型转换后返回Id属性值,保证数据库与实例对象同步
if (session.Contains(customer))
{
}
//得到session
ISession session = NHibernateHelper.GetSession();
//根据id得到customer对象
var customer = session.Get("Customer", new Guid("DDF637501-33071-4611B-B96A1-7FF356540CB81"));
//如果包含customer对象则删除
if (session.Contains(customer))
{
session.Evict(customer);
}
或ISession.Close():关闭当前ISession后持久态对象自动变成托管态。
//得到session
ISession session = NHibernateHelper.GetSession();
//根据id得到customer对象
Customer customer = session.Get("Customer", new Guid("DDF637501-33071-4611B-B96A1-7FF356540CB81")) as Customer;
//重新设置ISession
NHibernateHelper.ResetSession();
//脱管态对象
//在脱管态下可继续被修改
if (session.Contains(customer))
{
customer.NameAddress = new Name() { CustomerAddress="上海", CustomerName="wolfy"};
//转变为持久态对象
session.Update(customer);
}
通过上面的例子可以看出:在托管时期的修改会被持久化到数据库中;
注意:NHibernate如何知道重新关联的对象是不是“脏的(修改过的)”?如果是新的ISession,ISession就不能与对象初值来比较这个对象是不是“脏的”,我们在映射文件中定义元素和元素的unsaved-value属性,NHibernate就可以自己判断了。
如果加上一个锁:如果在托管时期没有修改,就不执行更新语句,只转换为持久态,下面的例子如果在托管时期修改对象,执行更新语句。
//得到session
ISession session = NHibernateHelper.GetSession();
//根据id得到customer对象
Customer customer = session.Get("Customer", new Guid("DDF637501-33071-4611B-B96A1-7FF356540CB81")) as Customer;
if (session.Contains(customer))
{
NHibernateHelper.ResetSession();
}
//锁
session.Lock(customer, NHibernate.LockMode.None);
//如果在托管时期没有修改,就不执行更新语句,只转换为持久态
session.Update(customer);
需注意Lock锁的使用,需要保证customer对象不为null,这里为了测试方便就没加上判断。如果为null会有异常(attempt to lock null)。
方法二:ISession.Merge():合并指定实例。不必考虑ISession状态,ISession中存在相同标识的持久化对象时,NHibernate便会根据用户给出的对象状态覆盖原有的持久化实例状态。
方法三:ISession.SaveOrUpdate():分配新标识保存瞬时态对象;更新/重新关联脱管态对象。
实现智能提示有两个方法,第一种是使用NuGet安装NHibernate的形式
另一种是
这里强调一下无论是手动下载还是NuGet方式安装的NHibernate,在它的目录中都存在一个ConfigurationTemplates文件夹,这里面有所有数据库链接配置模板,可以自己复制出来直接使用
本小结针对实体类映射文件(xxx.hbm.xml)各个元素在这里插入代码片
进行讲解,由于之前已经写完但是保存的时候没保存上,这里就只摘抄原文不进行讲解记录了
-mapping
schema="schemaName" (1)
catalog="catalogName" (2)
default-cascade="none|save-update..." (3)
auto-import="true|false" (4)
assembly="Eg" (5)
namespace="Eg" (6)
default-access="field|property|field.camelcase..."(7)
default-lazy="true|false" (8)
/>
hibernate-mapping元素允许您嵌套多个持久性
-value="discriminatorValue" (3)
mutable="true|false" (4)
schema="owner" (5)
catalog="catalog" (6)
proxy="proxyInterface" (7)
dynamic-update="true|false" (8)
dynamic-insert="true|false" (9)
select-before-update="true|false" (10)
polymorphism="implicit|explicit" (11)
where="arbitrary sql where condition" (12)
persister="persisterClass" (13)
batch-size="N" (14)
optimistic-lock="none|version|dirty|all" (15)
lazy="true|false" (16)
abstract="true|false" (17)
entity-name="entityName" (18)
check="arbitrary sql check condition" (19)
subselect="SQL expression" (20)
schema-action="none|drop|update|export|validate|all|commaSeparatedValues" (21)
rowid="unused" (22)
node="unused" (23)
/>
中schema 值.
中catalog值.
中的抽象超类层次结构.
元素,它与
,
and
互斥。还可以将
指定为 的属性。元素的内容是 SQL 查询>
<![CDATA[
SELECT cat.ID, cat.NAME, cat.SEX, cat.MATE FROM cat
]]>
>
通常,在使用 subselect 映射查询时,您需要将类标记为不可可变(mutable=‘false’),除非您指定自定义 SQL 来执行 UPDATE、DELETE 和 INSERT 操作
命名的持久化类作为接口是完全可以接受的。然后,您将使用
元素声明该接口的实现类。您可以保留任何内部类。您应该使用标准形式即指定类名。例如,Foo+Bar,例如由于 HQL 解析器的限制,内部类不能在 NHibernate 中的查询中使用,除非为它们分配一个不带 + 字符的实体名称。
这里注意一下,下面要将讲解到的元素好多属性都是可以覆盖class元素中属性值的,还有一部分属性值得意义和class元素中相同,下面表述中重复的属性名就不翻译了
-value="any|none|null|undefined|idValue" (5)
access="field|property|nosetter|className" (6)
generator="generatorClass"> (7)
>
>
使用
元素,以允许使用复合主键访问旧数据。强烈建议您不鼓励将其用于其他任何事情
可以使用
子元素。如果配置或初始化generator实例需要任何参数,则使用元素传递这些参数
>
>
>uid_table>
>next_hi_value_column>
>
>
如果不需要任何参数,则可以直接在
元素上使用generator属性声明生成器,如下所示
>
下面罗列了一些常用的主键生成方式:
(1) assigned
主键由外部程序负责生成,无需NHibernate参与。
(2) hilo
通过hi/lo 算法实现的主键生成机制,需要额外的数据库表保存主
键生成 历史状态。
(3) seqhilo
与hilo 类似,通过hi/lo 算法实现的主键生成机制,只是主键历史
状态保存在Sequence中,适用于支持Sequence的数据库,如Oracle。
(4) increment
主 键按数值顺序递增。此方式的实现机制为在当前应用实例中维持
一个变量,以保存着当前的最大值,之后每次需要生成主键的时候
将此值加1作为 主键。
这种方式可能产生的问题是:如果当前有多个实例访问同一个数据
库,那么由于各个实例各自维护主键状态,不同实例可能生成同样
的 主键,从而造成主键重复异常。因此,如果同一数据库有多个实
例访问,此方式必须避免使用。
(5) identity
采用数据库提供的主键生成机制。如DB2、SQL Server、MySQL
中的主键 生成机制。
(6) sequence
采用数据库提供的sequence 机制生成主键。如Oralce 中的
Sequence。
(7) native
由 NHibernate根据底层数据库自行判断采用identity、hilo、sequence
其中一种作为主键生成方式。) uuid.hex
由 Hibernate基于128 位唯一值产生算法生成16 进制数值(编码后
以长度32 的字符串表示)作为主键。
(8) foreign
使用外部表的字段作为主键。
关于更详细的主键生成策略,可以参见https://nhibernate.info/doc/nhibernate-reference/mapping.html#google_vignette
假设一个灵长动物表,这个表存储所有灵长动物数据,其中有一列用来区分是哪种动物的,比如值M代表猴子,P代表人,那么这个区分列就是一个discriminator元素的映射列。
我们在
元素声明表的
列。
列包含标记值,这些标记值告诉持久性层要为特定行实例化哪个子类。NHibernate中规范
列可以使用以下类型:String, Char, Int32, Byte, Short, Boolean, YesNo, TrueFalse。
-null="true|false" (4)
force="true|false" (5)
insert="true|false" (6)
formula="arbitrary SQL expression" (7)
/>
, 'b', 'c') then 0 else 1 end"
type="Int32"/>
|property|nosetter|className" (4)
unsaved-value="null|negative|undefined|value" (5)
generated="never|always" (6)
insert="false|true" (7)
/>
|false" (4)
insert="true|false" (4)
formula="arbitrary SQL expression" (5)
access="field|property|className" (6)
optimistic-lock="true|false" (7)
generated="never|insert|always" (8)
lazy="true|false" (9)
lazy-group="groupName" (10)
not-null="true|false" (11)
unique="true|false" (12)
unique-key="uniqueKeyName" (13)
index="indexName" (14)
length="L" (15)
precision="P" (16)
scale="S" (17)
/>
元素的程序集和命名空间属性) *p.price) FROM LineItem li, Product p
WHERE li.productId = p.productId
AND li.customerId = customerId
AND li.orderNumber = orderNumber )"/>
(6) access (optional - defaults to property): the strategy NHibernate should use for accessing the property value.
(7) optimistic-lock (optional - defaults to true): 指定对此属性的更新是否需要获取乐观锁。.
(8) generated (optional - defaults to never): never表示给定的属性值不会在数据库中生成、insert 表示给定的属性值在插入时生成,但在后续更新时不会重新生成。像创建日期这样的东西就属于这一类。请注意,即使版本和时间戳属性可以标记为已生成,此选项也不适用于它们、always 声明属性值在插入和更新时生成.
(9) lazy (optional - defaults to false): 指定此属性是lazy的。最初加载对象时,不会加载lazy属性,除非在特定查询中重写了读取模式。延迟属性的值按lazy-group加载.
(10) lazy-group (optional - defaults to DEFAULT): if the property is lazy, its lazy-loading group. When a lazy property is accessed, the other lazy properties of the lazy group are also loaded with it.
(11) not-null (optional - defaults to false): 数据表该列是否可为null.
(12) unique (optional - defaults to false): 该列是否唯一.
(13) unique-key (optional): 用于 DDL 生成的unique index的逻辑名称.
(14) index (optional): 用于 DDL 生成的索引的逻辑名称。该列将与共享相同索引逻辑名称的其他列一起包含在索引中.
(15) length (optional):指定长度.
(16) precision (optional):指定精度.
(17) scale (optional): 指定刻度.
与另一个持久类的普通关联是使用多对一元素声明的。关系模型是多对一关联
-to-one
name="propertyName" (1)
column="columnName" (2)
class="className" (3)
cascade="all|none|save-update|delete|delete-orphan|all-(4)delete-orphan"
fetch="join|select" (5)
lazy="proxy|no-proxy|false" (6)
update="true|false" (7)
insert="true|false" (7)
property-ref="propertyNameFromAssociatedClass" (8)
access="field|property|nosetter|className" (9)
optimistic-lock="true|false" (10)
not-found="ignore|exception" (11)
entity-name="entityName" (12)
formula="arbitrary SQL expression" (13)
not-null="true|false" (14)
unique="true|false" (15)
unique-key="uniqueKeyName" (16)
index="indexName" (17)
foreign-key="foreignKeyName" (18)
outer-join="auto|true|false" (19)
/>
all-delete-orphan 在关联对象失去宿主(解除父子关系)时,自动删除不属于父对象的子对象, 也支持级联删除和级联保存更新.
delete-orphan 删除所有和当前对象解除关联关系的对象
All 级联删除, 级联更新,但解除父子关系时不会自动删除子对象.
Delete 级联删除, 但不具备级联保存和更新
None 所有操作均不进行级联操作
save-update 级联保存(load以后如果子对象发生了更新,也会级联更新). 在执行save/update/saveOrUpdate时进行关联操作,它不会级联删除
***************
要保存或更新关联对象关系图中的所有对象,您必须
Save()、SaveOrUpdate() 或 Update() 每个单独的对象或
使用 cascade=‘all’ 或 cascade=‘save-update’ 映射关联的对象。
同样,要删除图形中的所有对象,请
delete() 每个单独的对象或
使用 cascade=‘all’、cascade=‘all-delete-orphan’ 或 cascade='delete’映射关联的对象。
建议:
如果子对象的生存期受父对象的生存期限制,则通过指定级联='all’将其设为生命周期对象。否则,请从应用程序代码中显式保存()和删除()。如果你真的想为自己节省一些额外的输入,请使用cascade='save-update’和显式Delete()。
cascade=‘all’ 父对象的操作会关联到子对象,保存/更新/删除父项会导致保存/更新/删除子项。
cascade=‘all-delete-orphan’ 或 cascade='delete-orphan’时如果保存了父项,则所有子项都将传递到 SaveOrUpdate()
如果将父级传递给 Update() 或 SaveOrUpdate(),则所有子级都将传递给 SaveOrUpdate()
如果暂时性子级被永久父级引用,则会将其传递给 SaveOrUpdate()
如果删除了父级,则所有子级都将传递给 Delete()
-to-one
name="propertyName" (1)
class="className" (2)
cascade="all|none|save-update|delete|delete-orphan|all-(3)delete-orphan"
constrained="true|false" (4)
fetch="join|select" (5)
lazy="proxy|no-proxy|false" (6)
property-ref="propertyNameFromAssociatedClass" (7)
access="field|property|nosetter|className" (8)
formula="any SQL expression" (9)
entity-name="entityName" (10)
foreign-key="foreignKeyName" (11)
/>
元素指定.一对一关联有两种类型:
给个例子,假设现在有员工表和人员表两个表,所有人员都必须来自员工表。也就是说人员表的主键上有外键约束,必须来自员工表。
-to-one name="Person" class="Person"/>
-to-one name="Employee" class="Employee" constrained="true"/>
现在,我们必须确保 PERSON 和 EMPLOYEE 表中相关行的主键相等。我们使用一种称为外来的特殊 NHibernate 标识符生成策略:
>
>
>
>Employee>
>
>
...
-to-one name="Employee"
class="Employee"
constrained="true"/>
>
或者,具有唯一约束(从员工到人员)的外键可以表示为::
-to-one name="Person" class="Person" column="PERSON_ID" unique="true"/>
-to-one name="Employee" class="Employee" property-ref="Person"/>
并且可以通过将以下内容添加到人员映射中来进行双向关联。
多态持久性需要声明在持久性超类中声明子类。前面说过的那个灵长类动物区分,就需要
元素实现。
对于(推荐的)每类表层次结构映射策略,使用
声明
-value="discriminatorValue" (2)
extends="superclassName" (3)
...> (4)
... />
... />
...
>
element is nested into its superclass mapping declaration): 将映射类的名称指定为子类的超类.
映射元素上可用的许多属性也可用于
具有相同的用法:proxy, dynamic-update, dynamic-insert, select-before-update, persister, batch-size, lazy, entity-name, abstract。
元素它出现在父映射元素定义与引用原始表的主键的新表的联接的任何位置。它还定义联接表中的外键 。
加入一个订单类含有一个订单详情的集合,那么在定义这个集合表与订单表连接是定义外键的。
-delete="noaction|cascade" (2)
property-ref="propertyName" (3)
not-null="true|false" (4)
update="true|false" (5)
unique="true|false" (6)
foreign-key="foreignKeyName" (7)
/>
元素指定.NHibernate 要求将持久性集合值字段声明为泛型接口类型,例如:
public class Product
{
public virtual ISet> Parts { get; set; } = new HashSet>();
public virtual string SerialNumber { get; set; }
}
实际的接口可能是
System.Collections.Generic.ICollection
, System.Collections.Generic.IList
, System.Collections.Generic.IDictionary
, System.Collections.Generic.ISet
或自定义NHibernate.UserType.IUserCollectionType的实现。
请注意我们如何使用 HashSet
实例初始化实例变量。这是初始化新实例化(非持久性)实例的集合值属性的最佳方法。例如,当您通过调用 Save() 使实例持久化时,NHibernate 实际上会将 HashSet
替换为 NHibernate 自己的ISet
实现的实例。注意此类错误
Cat cat = new DomesticCat();
Cat kitten = new DomesticCat();
...
ISet<Cat> kittens = new HashSet<Cat>();
kittens.Add(kitten);
cat.Kittens = kittens;
session.Save(cat);
//保存成功后cat.Kittens是NHibernate自己实现的ISet集合,已经不是HashSet类型
kittens = cat.Kittens; //Okay, kittens collection is an ISet
//报错NHibernate的ISet无法类型转换成HashSet
HashSet<Cat> hs = (HashSet<Cat>) cat.Kittens;
用于映射集合的 NHibernate 映射元素取决于接口的类型。例如,
元素用于映射 ISet 类型的属性
>
>
>
-null="true"/>
-to-many class="Part"/>
>
>
除了
之外,还有
以及 ,
映射元素元素具有代表性:
element.
element.关于集合映射设置内容很多,可以自行参考这个页面https://nhibernate.info/doc/nhibernate-reference/collections.html
在NHibernate的映射中,关于继承的映射策略有3种方式:
另外还有一种比较特别的多态映射
下面分别来阐述NHibernate继承映射的各种策略要点。
单表继承的方式是,所有的字段都放在一个表中,用一个字段来区分子类。使用配置节点
配置子类。
首先新建一张Animal表如下:
映射文件:Animal.hbm.xml:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Model.AnimalModel" table="Animal" discriminator-value="动物">
<id name="AnimalId" column="AnimalId" type="Int32">
<generator class="native"/>
id>
<discriminator column="AnimalType" type="String" />
<property name="Name" column="Name" type="String"/>
<subclass extends="Model.AnimalModel" name="Model.FrogModel" discriminator-value="蛙">
<property name="Foot" column="Foot" type="String"/>
subclass>
class>
hibernate-mapping>
子类也可以单写映射文件:Fish.hbm.xml
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="false">
<subclass extends="Model.AnimalModel" name="Model.FishModel" discriminator-value="鱼">
<property name="Tail" column="Tail" type="String"/>
subclass>
hibernate-mapping>
Model类:
namespace Model
{
public class AnimalModel
{
public virtual int AnimalId { get; set; }
public virtual string Name { get; set; }
}
public class FishModel : AnimalModel
{
public virtual string Tail { get; set; }
}
public class FrogModel : AnimalModel
{
public virtual string Foot { get; set; }
}
}
执行操作:
static void Main(string[] args)
{
ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory();
using (ISession session = sessionFactory.OpenSession())
{
FishModel Fish = session.Get<FishModel>(1);
Console.WriteLine(Fish.AnimalId + " : " + Fish.Name + " : " + Fish.Tail);
FrogModel Frog = session.Get<FrogModel>(2);
Console.WriteLine(Frog.AnimalId + " : " + Frog.Name + " : " + Frog.Foot);
}
Console.ReadKey();
}
输出结果如下:
我们看看SQL Server Profiler监控到执行的过程:
exec sp_executesql N'SELECT frogmodel0_.AnimalId as AnimalId0_0_, frogmodel0_.Name as Name0_0_, frogmodel0_.Foot as Foot0_0_ FROM Animal frogmodel0_ WHERE frogmodel0_.AnimalId=@p0 and frogmodel0_.AnimalType=''蛙''',N'@p0 int',@p0=2
我们看到NHibernate,会利用我们设置的discriminator-value的值去过滤数据。使用单表继承,特别需要注意的就是,由子类定义的字段,不能有非空约束(not-null)。为什么?因为,如上面的Foot与Tail。青蛙则尾巴字段为空,鱼则肯定腿字段为空。
类表映射,故名思议就是一个子类一个表,其子类表通过外键关联到主表。然后一个子类对应一张表。配置节点为:
首先我们将上面的表结构改为:
映射文件的改动如下,Animal.hbm.xml:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Model.AnimalModel,Model" table="Animal">
<id name="AnimalId" column="AnimalId" type="Int32">
<generator class="native"/>
id>
<property name="Name" column="Name" type="String"/>
<joined-subclass extends="Model.AnimalModel" name="Model.FrogModel" table="Frog">
<key column="Id"/>
<property name="Foot" column="Foot" type="String"/>
joined-subclass>
class>
hibernate-mapping>
Fish.hbm.xml
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="false">
<joined-subclass extends="Model.AnimalModel, Model" name="Model.FishModel, Model" table="Fish">
<key column="Id"/>
<property name="Tail" column="Tail" type="String"/>
joined-subclass>
hibernate-mapping>
Fish和Frog类需要时Animal的子类。
我们来看看SQL Server Profiler监控到执行了什么语句:
exec sp_executesql N'SELECT frogmodel0_.Id as AnimalId0_0_, frogmodel0_1_.Name as Name0_0_, frogmodel0_.Foot as Foot1_0_ FROM Frog frogmodel0_ inner join Animal frogmodel0_1_ on frogmodel0_.Id=frogmodel0_1_.AnimalId WHERE frogmodel0_.Id=@p0',N'@p0 int',@p0=2
一个简单的inner join实现的。可以看到,使用这种方式的表比较多,关系模型实际是一对一关联。
类表映射使用Discriminator
类表映射还可以加上个辨别字段,只是它除了用外键关联之外,还有个辨别字段。配置节点为
。
现在,我们为上面的表结构再添加会一个辨别字段:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Model.AnimalModel,Model" table="Animal" discriminator-value="动物">
<id name="AnimalId" column="AnimalId" type="Int32">
<generator class="native"/>
id>
<discriminator column="Type" type="String" />
<property name="Name" column="Name" type="String"/>
<subclass extends="Model.AnimalModel, Model" name="Model.FrogModel, Model" discriminator-value="蛙">
<join table="Frog">
<key column="Id"/>
<property name="Foot" column="Foot" type="String"/>
join>
subclass>
class>
hibernate-mapping>
Fish.hbm.xml
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="false">
<subclass extends="Model.AnimalModel, Model" name="Model.FishModel, Model" discriminator-value="鱼">
<join table="Fish">
<key column="Id"/>
<property name="Tail" column="Tail" type="String"/>
join>
subclass>
hibernate-mapping>
我们来看看生成的SQL语句:
exec sp_executesql N'SELECT frogmodel0_.AnimalId as AnimalId0_0_, frogmodel0_.Name as Name0_0_, frogmodel0_1_.Foot as Foot1_0_ FROM Animal frogmodel0_ inner join Frog frogmodel0_1_ on frogmodel0_.AnimalId=frogmodel0_1_.Id WHERE frogmodel0_.AnimalId=@p0 and frogmodel0_.Type=''蛙''',N'@p0 int',@p0=2
另外,在映射文件里,可以混合使用单表映射与类表映射,例如
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Model.AnimalModel,Model" table="Animal" discriminator-value="动物">
<id name="AnimalId" column="AnimalId" type="Int32">
<generator class="native"/>
id>
<discriminator column="Type" type="String" />
<property name="Name" column="Name" type="String"/>
<subclass extends="Model.AnimalModel, Model" name="Model.FishModel, Model" discriminator-value="鱼">
<join table="Fish">
<key column="Id"/>
<property name="Tail" column="Tail" type="String"/>
join>
subclass>
<joined-subclass extends="Model.AnimalModel, Model" name="Model.FrogModel, Model" table="Frog">
<key column="Id"/>
<property name="Foot" column="Foot" type="String"/>
joined-subclass>
class>
hibernate-mapping>
这种方法实在太烂。不光表多,重复字段也多,子类表中,每张表为对应类的所有属性(包括从超类继承的属性)定义相应字段。重复列太多,再加上每个类都一张表,估计第一范式都达不到有兴趣自己看吧。
有一个缺点,无法生成有union的SQL语句。有兴趣自己看吧。
在NHibernate中提供了很多查询方式给我们选择,这里列举了3种方式:
这一节我们介绍NHibernate查询语言(HQL,NHibernate Query Language)。
看一个例句:
select c.Firstname from Customer c
如果是SQL语句:Customer是数据库表,Firstname是列;而对于HQL:Customer是一个对象,Firstname是Customer对象的属性。相比之下SQL语句非常灵活,但是没有编译时语法验证支持,但是可以实现跨数据库平台的支持,无需考虑具体数据库SQL方言。
注意:HQL关键字不区分大小写。
顾名思义,同SQL语句类似:
返回表中所有数据。
public IList<Customer> From()
{
//返回所有Customer类的实例
return _session.CreateQuery("from Customer")
.List<Customer>();
}
使用as来赋予表的别名,as可以省略。
public IList<Customer> FromAlias()
{
//返回所有Customer类的实例,Customer赋予了别名customer
return _session.CreateQuery("from Customer as customer")
.List<Customer>();
}
出现多个类,或者分别使用别名,返回笛卡尔积或者称为“交叉”连接。
在结果集中返回指定的对象和属性。
public IList<int> Select()
{
//返回所有Customer的CustomerId
return _session.CreateQuery("select c.CustomerId from Customer c")
.List<int>();
}
用Object[]的数组返回多个对象和/或多个属性,或者使用特殊的elements功能,注意一般要结合group by使用。注意,这里是Object[]的数组,我们可以定义DTO对象集合返回,即使用类型安全的.NET对象。
public IList<object[]> SelectObject()
{
return _session.CreateQuery("select c.Firstname, count(c.Firstname) from Customer c group by c.Firstname")
.List<object[]>();
}
用Object[]的数组返回属性的统计函数的结果,注意统计函数的变量也可以是集合count( elements(c.CustomerId) ) 。注意,这里是Object[]的数组,我们可以定义DTO对象集合返回。
public IList<object[]> AggregateFunction()
{
return _session.CreateQuery("select avg(c.CustomerId),sum(c.CustomerId),count(c) from Customer c")
.List<object[]>();
}
distinct和all关键字的用法和语义与SQL相同。
实例:获取不同Customer的FirstName
public IList<string> Distinct()
{
return _session.CreateQuery("select distinct c.Firstname from Customer c")
.List<string>();
}
where子句让你缩小你要返回的实例的列表范围。
public IList<Customer> Where()
{
return _session.CreateQuery("from Customer c where c.Firstname='YJing'")
.List<Customer>();
}
where子句允许出现的表达式包括了在SQL中的大多数情况:
数学操作符:+, -, *, /
真假比较操作符:=, >=, <=, <>, !=, like
逻辑操作符:and, or, not
字符串连接操作符:||
SQL标量函数:upper(),lower()
没有前缀的( ):表示分组
in, between, is null
位置参数:?
命名参数::name, :start_date, :x1
SQL文字:'foo', 69, '1970-01-01 10:00:01.0'
枚举值或常量:Color.Tabby
按照任何返回的类或者组件的属性排序:asc升序、desc降序。
public IList<Customer> Orderby()
{
return _session.CreateQuery("from Customer c order by c.Firstname asc,c.Lastname desc")
.List<Customer>();
}
按照任何返回的类或者组件的属性进行分组。
public IList<object[]> Groupby()
{
return _session.CreateQuery("select c.Firstname, count(c.Firstname) from Customer c group by c.Firstname")
.List<object[]>();
}
好的,以上基本的查询的确非常简单,我们还是参考一下实例,分析一下我们如何写HQL查询吧!
实例1:按照FirstName查询顾客:
public IList<Customer> GetCustomersByFirstname(string firstname)
{
//写法1
//return _session.CreateQuery("from Customer c where c.Firstname='" + firstname + "'")
// .List();
//写法2:位置型参数
//return _session.CreateQuery("from Customer c where c.Firstname=?")
// .SetString(0, firstname)
// .List();
//写法3:命名型参数(推荐)
return _session.CreateQuery("from Customer c where c.Firstname=:fn")
.SetString("fn", firstname)
.List<Customer>();
}
书写HQL参数有四种写法:
写法1:可能会引起SQL注入,不要使用。
写法2:ADO.NET风格的?参数,NHibernate的参数从0开始计数。
写法3:命名参数用:name的形式在查询字符串中表示,这时IQuery接口把实际参数绑定到命名参数。
使用命名参数有一些好处:命名参数不依赖于它们在查询字符串中出现的顺序;在同一个查询中可以使用多次;它们的可读性好。所以在书写HQL使用参数的时候推荐命名型参数形式。
测试一下这个方法吧:看看数据库中Firstname为“YJingLee”的记录个数是否是1条,并可以判断查询出来的数据的FirstName属性是不是“YJingLee”。
[Test]
public void GetCustomerByFirstnameTest()
{
IList<Customer> customers = _queryHQL.GetCustomersByFirstname("YJingLee");
Assert.AreEqual(1, customers.Count);
foreach (var c in customers)
{
Assert.AreEqual("YJingLee", c.Firstname);
}
}
实例2:获取顾客ID大于CustomerId的顾客:
public IList<Customer> GetCustomersWithCustomerIdGreaterThan(int customerId)
{
return _session.CreateQuery("from Customer c where c.CustomerId > :cid")
.SetInt32("cid", customerId)
.List<Customer>();
}
现有三张表:职员表(employee)、订单表(order)及订单明细表(orderentity)
三张表的主键都是自增类型,职员表与订单表是外键关联,订单表中每行记录都对应一个操作职员,职员表中每行记录都对应多个订单表中的记录,所以职员表与订单表就是主从关系或者父子关系。
订单表与订单明细表也是主从关系,订单表的主键对应订单明细表中的外键。
下面给出三个表的对应实体类:
namespace TestNHibernate.mapping.mysql.poco
{
//订单类
public class Order
{
public virtual Int64 Id{ get; set;}
public virtual DateTime CreateTime{ get; set; }
public virtual char Category { get; set; }
public virtual string Receiver { get; set; }
public virtual Employee Employee { get; set; }
public virtual ISet<OrderDetails> OrderDetails { get; set; }
}
//职员类
public class Employee
{
public virtual Int64 Id { get; set; }
public virtual string Name { get; set; }
public virtual string DepName { get; set; }
//public virtual ISet Orders { get; set; }
}
//订单详情类
public class OrderDetails
{
public virtual Int64 Id { get; set; }
public virtual Int64 ProductId { get; set; }
public virtual int Qty { get; set; }
public virtual decimal Price { get; set; }
public virtual decimal TotalAmount { get; set; }
public virtual int EntrtyId { get; set; }
public virtual Int64? OrderId { get; set; }
}
}
Order.hbm.xml文件
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="TestNHibernate" namespace="TestNHibernate.mapping.mysql.poco">
<class name="Order" table="`order`" >
<id name="Id" column="sysid">
<generator class="identity">generator>
id>
<property name="CreateTime" type="DateTime" column="createtime">property>
<property name="Category" type="char" column="category" >property>
<many-to-one name="Employee" unique="true" column="empid" cascade ="all" />
<property name="Receiver" type="string" column="receiver" length="100">property>
<set name="OrderDetails" table="orderentity" order-by="EntrtyId desc" generic="true" inverse="false" lazy = "false" cascade="save-update" >
<key column="orderid" />
<one-to-many class="OrderDetails" />
set>
class>
hibernate-mapping>
Employee.hbm.xml文件
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="TestNHibernate" namespace="TestNHibernate.mapping.mysql.poco">
<class name="Employee" table="employee" lazy="true">
<id name="Id" column="sysid">
<generator class="identity">generator>
id>
<property name="Name" type="string" column="name" length="50" not-null="true">property>
<property name="DepName" type="string" length="50" column="depname" not-null="true">property>
class>
hibernate-mapping>
OrderDetails.hbm.xml文件
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="TestNHibernate" namespace="TestNHibernate.mapping.mysql.poco">
<class name="OrderDetails" table="`orderentity`" >
<id name="Id" column="sysid">
<generator class="identity">generator>
id>
<property name="ProductId" type="Int64" column="productid" >property>
<property name="Qty" type="int" column="qty" >property>
<property name="Price" type="decimal" column="`price`" >property>
<property name="TotalAmount" type="decimal" column="totalamount" >property>
<property name="EntrtyId" type="int" column="entrtyid" >property>
<property name="OrderId" type="Int64" column="orderid" >property>
class>
hibernate-mapping>
实现同步插入
现在要实现的需求是,增加一个Order类,然后将Employee及OrderDetails属性进行配置,保存Order实体时,同步将保存的订单明细插入数据库,如果职员信息存在则直接外键关联,不存在则插入职员信息.
因为需要关联操作所以需要分别在Order的mapping文件上的
和
元素上设置级联操作的cascade属性,就实现级联操作,由于配置文件中已经设置了,另外在集合配置中有一个inverse属性来设置关联的控制方向,inverse=“false”(默认)父实体负责维护关联关系。inverse=“true”子实体负责维护关联关系。这里需要设置为false,因为父实体order插入后可以维护订单明细表中的外键,这种维护方式是通过插入记录后再通过update方式去修改订单明细中的外键值,所以订单明细中外键值必须可为null,否则出错。如果设置inverse=“true”,将关联关系维护交给订单明细表,则订单明细插入后外键列将为null,因为它不知道订单表主键信息。
Order order=new Order();
order.Category = 'D';
order.CreateTime = DateTime.Now;
order.Receiver = "测试";
order.Employee = new Employee { Name = "收件人", DepName = "营销" };
OrderDetails od=new OrderDetails();
od.EntrtyId = 1;
od.ProductId = 12;
od.Qty = 1;
od.Price = 10m;
od.TotalAmount = 10m;
OrderDetails od1 = new OrderDetails();
od1.EntrtyId = 2;
od1.ProductId = 121;
od1.Qty = 1;
od1.Price = 100m;
od1.TotalAmount = 100m;
ISet<OrderDetails> details=new HashSet<OrderDetails>();
details.Add(od);
details.Add(od1);
order.OrderDetails = details;
EntrityRepositoryImpl<Order> impl = new EntrityRepositoryImpl<Order>(DataBaseCategory.MySql);
impl.Add(order);
查看数据表中,所有信息都被同步插入了。
加入现在在增加一个记录,而订单中的职员信息是配置的已有职员,那么插入后职员信息还会继续新增还是使用已有记录?
Order order=new Order();
order.Category = 'D';
order.CreateTime = DateTime.Now;
order.Receiver = "测试";
//order.Employee = new Employee { Name = "收件人", DepName = "营销" };
EntrityRepositoryImpl<Employee> impls = new EntrityRepositoryImpl<Employee>(DataBaseCategory.MySql);
// impls.SaveOrUpdate(order.Employee);
order.Employee = impls.GetById(18L);
OrderDetails od=new OrderDetails();
od.EntrtyId = 1;
od.ProductId = 12;
od.Qty = 1;
od.Price = 10m;
od.TotalAmount = 10m;
OrderDetails od1 = new OrderDetails();
od1.EntrtyId = 2;
od1.ProductId = 121;
od1.Qty = 1;
od1.Price = 100m;
od1.TotalAmount = 100m;
ISet<OrderDetails> details=new HashSet<OrderDetails>();
details.Add(od);
details.Add(od1);
order.OrderDetails = details;
EntrityRepositoryImpl<Order> impl = new EntrityRepositoryImpl<Order>(DataBaseCategory.MySql);
impl.Add(order);
测试后发现NHibernate会自动维护职员表与订单表中外键关系,只要是职员属性是非transient状态的就会自动维护。
实现关联查询
现在需求是,通过查询订单可以同步将订单所属职员及订单明细数据一同查出来,由于存在select N+1问题(参加7.4章节),我需要使用一条连接语句查询出来。
Func<ISession, IQuery> f = t =>
{
IQuery query = t.CreateQuery("from Order c inner join fetch c.OrderDetails s inner join fetch c.Employee where c.Id=:id");
query.SetInt64("id", 23);
return query;
};
EntrityRepositoryImpl<Order> impls = new EntrityRepositoryImpl<Order>(DataBaseCategory.MySql);
Order o = impls.GetByHQL(f);
注意语句中使用了fetch关键词,直接将关联的订单明细及职员信息查询出来了。这里还需要强调一点,如果在Order的配置文件中在在
和
元素上设置 lazy = "false"和fetch=“join”,在使用Query中是无效的,只有在fetch策略用于get/load一个对象时有效。
是映射文件或配置文件命名错误,eg:News_T.hbm.xml和NHibernate.cfg.xml就是正确的,而New_T.xml和NHibernate.xml就是错误的。
是配置文件里面缺少 语句。
xml文件属性生成操作一定要设置为“嵌入的资源”
如果你只需要一个只读集合,你可以尝试以下方法:
<property name="BankHolidays" type="DateTime[]" formula="(SELECT Date FROM BankHolidays)" update="false" insert="false" />
当从父类中保存数据时,因为nhibernate先插入父表数据,然后插入外键为null的子表数据,然后在生成更新子表的update语句。所以外键必须能够为null,否则将报错。
当父端inverse="true"时将只生成插入语句,不会生成更新外键的update语句,所以必须inverse=“false”
数据库中外键允许为null,持久化类中属性要可为null。如int ? fid;
NHibernate框架使用是否得当将直接影响到我们的程序的效率,其中的两个概念:
用懒加载时,由于关联对象要到需要的时候才查询,所以sql会被拆分成两次查询,这就产生了select N+1问题。
下面就让我们看一下lazy与fetch两个属性组合使用的效果。使用两个实体WallParam(参数)与WallParamGroup(参数组),其中关系为多对一。
CASE 1:将lazy设为proxy
< many-to-one name = " paramGroup " column = " group_id " not-null = " true " class = " Model.WallParamGroup,Model " lazy = " proxy " />
当查询WallParam信息时,lazy配置起作用了,执行的sql如下(没有同时查出WallParamGroup, 该信息被延后了):
然而当执行查询并同时获取关联的WallParamGroup信息时,nhibernate先后执行两句sql来进行查询:
结论:lazy的配置确实延后了关联信息的加载,关联信息只有被用的时候才会去数据库查询。因此查询语句也自然是拆分的sql。
CASE 2:此时已经将lazy设为false
< many-to-one name = " paramGroup " column = " group_id " not-null = " true " class = " Model.WallParamGroup,Model " lazy = " false " />
当查询WallParam信息时,WallParamGroup的信息会同时加载出来。但是令人意外的是,执行的sql并不像我们认为的是一句关联查询。而是任然执行了两个拆分的sql,如下:
结论:lazy能延后关联对象的数据加载,却不能决定关联数据的获取方式(关联查询/拆分查询)。因此仅仅使用lazy属性并不能解决Select N+1问题。
CASE 3:保持lazy设为false,同时将fetch设置为join(fetch默认是“select”)
< many-to-one name = " paramGroup " column = " group_id " not-null = " true " class = " Model.WallParamGroup,Model " lazy = " false " fetch = " join " />
当查询WallParam信息时,WallParamGroup的信息会同时加载出来。并且从生成的sql语句看,确实使用了关联查询,一句sql就取出了所有信息:
结论:设置fetch=“join”改变了查询行为,使得关联对象能够通过join查询得到,从而解决了Select N+1问题。
CASE 4:那么fetch设置为join时,将lazy设为proxy又会有什么结果呢
< many-to-one name = " paramGroup " column = " group_id " not-null = " true " class = " Model.WallParamGroup,Model " lazy = " false " fetch = " join " />
当查询WallParam信息时,WallParamGroup的信息会同时加载出来(这是因为fetch指定了关联查询,所以关联的WallParamGroup 信息已经被查询出来,导致lazy失效)。并且从生成的sql语句看,确实使用了关联查询,一句sql就取出了所有信息:
结论:一旦设置fetch=“join”改变了查询行为(使用了关联查询),lazy懒加载就失去作用了(因为关联数据已经通过join查询查出)。
总结:
lazy 参数值常见有 false 和 true,Hibernate3 映射文件中默认lazy = true ;
fetch 指定了关联对象抓取的方式,参数值常见是select和join,默认是select, select方式先查询主对象,再根据关联外键,每一个对象发一个select查询,获取关联的对象,形成了n+1次查询;而join方式,是left outer join查询,主对象和关联对象用一句外键关联的sql同时查询出来,不会形成多次查询。
在映射文件中,不同的组合会使用不同的查询:
lazy=“true” fetch = “select” ,使用延迟策略,开始只查询出主对象,关联对象不会查询,只有当用到的时候才会发出sql语句去查询 ;
lazy=“false” fetch = “select” ,没有用延迟策略,同时查询出主对象和关联对象,产生1+n条sql.
lazy="true"或lazy=“false” fetch = “join”,延迟都不会作用,因为采用的是外连接查询,同时把主对象和关联对象都查询出来了.
另外,在hql查询中,配置文件中设置的join方式是不起作用的,而在其他查询方式如get、criteria等是有效的,使用 select方式;除非在hql中指定join fetch某个关联对象。fetch策略用于get/load一个对象时,如何获取非lazy的对象/集合。 这些参数在Query中无效。