NHibernate中如何处理自定义枚举和组件类型的配置与映射?
- 内容介绍
- 文章标签
- 相关推荐
本文共计4768个文字,预计阅读时间需要20分钟。
1. 理解了one-to-many和many-to-one关系后,学习many-to-many用法变得简单。因此,通过参考本系列前几篇内容,可以轻松实现Vendor、Company、Plant和Item这几个对象的关联。
+ Company与Vendor之间建立关联:
1. one-to-many, many-to-one知道了many-to-many用法之后,many-to-one、one-to-many就很简单。因此参考这个系列前面几篇,可以很轻松的实现Vendor、Company、Plant、Item这几个对象。
Company与Vendor之间建立了一个一对多的关系,下面是Company类中Plants集合属性的定义
publicvirtualISet<Plant>Plants
{
get{return_plant;}
set{_plant=value;}
}
privateISet<Plant>_plant; 下面是这个属性的映射配置
<setname="Plants"table="TBLPLANT"lazy="true">
<keycolumn="COMPANY_ID"/>
<one-to-manyclass="Plant"not-found="ignore"/>
</set> 下面是Plant类中Company属性的定义与配置
publicvirtualCompanyCompany
{
get{return_company;}
set{_company=value;}
}
privateCompany_company;
<many-to-onename="Company"class="Company"not-found="exception"lazy="proxy"column="COMPANY_ID"/>
2. 枚举类型
NHibernate直接支持枚举类型的映射,这种支持方式,在数据库中保存的是枚举的整数值。在TBLPLANTITEM中有三个字段对应的对象属性使用枚举类型:ITEM_CATEGORY、PURCHASE_CATEGORY、STOCK_OPTION,我把STOCK_OPTION字段设置成整数类型,用来测试NHB对枚举映射的直接支持方式,而其它两个设成了字符串类型,用于保存枚举的字符串描述。
在数据库保存枚举的字符串描述需要使用自定义映射类型,将在下面一节中讲述,本节看一下直接对枚举类型进行映射。
StockOptionEnum枚举的定义:
publicenumStockOptionEnum
{
None=0,
ERP=1,
Hub=2
} PlantItem类中StockOption属性的定义:
publicvirtualStockOptionEnumStockOption
{
get{return_stockOption;}
set{_stockOption=value;}
}
privateStockOptionEnum_stockOption; 属性的配置节点:
<propertyname="StockOption">
<columnname="STOCK_OPTION"sql-type="int"not-null="false"/>
</property> NHibernate默认支持的枚举映射用起来很简单,这可能是能够将枚举值强制转化成整数这样一个值类型的原因。这种方式,枚举属性保存在数据库中的是整数值。
3. 自定义类型、自定义映射类型IUserType
首先在概念方面看一下。当你觉得某个属性需要满足的业务逻辑比较复杂,用.Net标准的数据类型无法满足你的需求时,你会选择为这个属性单独定义一个类,作为这个属性的类型,可以称这个为自定义类型/细粒度对象。某些情况下,将属性进行持久化映射时,并不是简单、直接的进行存取,也就是说你可能无法通过NHibernate提供的标准映射方法在属性和持久化媒介之间进行映射。这种情况下NHibernate提供一个机制,让你自己可以完全的控制映射行为,这就是为你的属性实现一个NHibernate.UserTypes.IUserType类,我称这个为自定义映射类型。自定义映射类型这个名称比较合适,因为从名称你就可以猜测到它的主要职责/作用就是完成属性与持久化媒介之间的映射。
我们先看一下怎样通过自定义映射类型保存枚举的字符串描述值。下面是ItemCategoryEnum、PurchaseCategoryEnum两个枚举的定义:
publicenumItemCategoryEnum
{
P=1,//Product
M=2,//material
}
publicenumPurchaseCategoryEnum
{
PO=1,
JIT=2
} 下面是自定义映射类型实现保存枚举的字符串描述。为了简化,我定义了一个抽象类实现NHibernate.UserTypes.IUserType,然后各个枚举的自定义映射类型就很容易实现了:
#regionEnumType
///<summary>
///自定义枚举映射类型
///</summary>
publicabstractclassMyEnumType:IUserType
{
privateType_type;
privateint_length;
privateMyEnumType()
{
}
publicMyEnumType(Typetype,intlength)
{
this._type=type;
this._length=length;
}
///<summary>
///自定义类型的对象实例是否会发生改变
///</summary>
publicboolIsMutable
{
get{returnfalse;}
}
publicTypeReturnedType
{
get{return_type;}
}
///<summary>
/// 属性对应的数据库字段类型
///</summary>
publicSqlType[]SqlTypes
{
get{returnnewSqlType[]{newSqlType(DbType.String,this._length)};}
}
///<summary>
///如果IsMutable为true,此处应当实现对象实例的DeepCopy
///</summary>
publicobjectDeepCopy(objectvalue)
{
returnvalue;
}
publicnewboolEquals(objectx,objecty)
{
returnx.ToString().Trim()==y.ToString().Trim();
}
publicintGetHashCode(objectx)
{
returnx.ToString().GetHashCode();
}
///<summary>
///从缓存中取对象
///</summary>
publicobjectAssemble(objectcached,objectowner)
{
returnDeepCopy(cached);
}
///<summary>
///将对象放入缓存前的处理
///</summary>
publicobjectDisassemble(objectvalue)
{
returnDeepCopy(value);
}
///<summary>
///读取数据库字段值(DataReader),转换成实体属性
///</summary>
publicobjectNullSafeGet(IDataReaderrs,string[]names,objectowner)
{
objectname=NHibernate.NHibernateUtil.String.NullSafeGet(rs,names[0]);
if(name==null)
thrownewException(_type.Name+"cannotbenull");
returnEnum.Parse(_type,name.ToString(),true);
}
///<summary>
///为属性的存取设置DbCommand参数
///这里我们要保存枚举的字符串描述,因此将value直接转换成字符串
///</summary>
publicvoidNullSafeSet(IDbCommandcmd,objectvalue,intindex)
{
if(value==null)
thrownewException(_type.Name+"cannotbenull");
NHibernate.NHibernateUtil.String.NullSafeSet(cmd,value.ToString(),index);
}
publicobjectReplace(objectoriginal,objecttarget,objectowner)
{
returnoriginal;
}
}
publicclassPurchaseCategoryType:MyEnumType
{
publicPurchaseCategoryType()
:base(typeof(PurchaseCategoryEnum),5)
{
}
}
publicclassItemCategoryType:MyEnumType
{
publicItemCategoryType()
:base(typeof(ItemCategoryEnum),3)
{
}
}
#endregion 下面是PlantItem类中PurchaseCategoryType和ItemCategoryType属性的定义:
publicvirtualItemCategoryEnumItemCategory
{
get{return_itemCategory;}
set{_itemCategory=value;}
}
privateItemCategoryEnum_itemCategory;
publicvirtualPurchaseCategoryEnumPurchaseCategory
{
get{return_purchaseCategory;}
set{_purchaseCategory=value;}
}
privatePurchaseCategoryEnum_purchaseCategory; 这两个属性的映射配置:
<propertyname="ItemCategory"type="NH12.MyExample.Domain.ItemCategoryType,Domain">
<columnname="ITEM_CATEGORY"sql-type="nvarchar"length="3"not-null="false"/>
</property>
<propertyname="PurchaseCategory"type="NH12.MyExample.Domain.PurchaseCategoryType,Domain">
<columnname="PURCHASE_CATEGORY"sql-type="nvarchar"length="5"not-null="false"/>
</property> 从上面可以看出,实体的属性使用枚举类型(或者其它的自定义类型),然后为枚举类型定义一个映射类型(上面的PurchaseCategoryType和ItemCategoryType),IUserType接口定义的主要职责,是如何将数据库取出来的值转换成枚举类型(或者自定义类型)(NullSafeGet方法)、如何将枚举类型(或者自定义类型)转换成数据库操作的DbParameter值(NullSafeSet方法),其它一些是支持NHibernate的OR机制必须的功能,例如了解属性类型(TypeReturnedType)、数据库字段的类型(SqlType[]SqlTypes)、对缓存机制的支持(Assemble、Disassemble方法)等等。
上面的示例演示如何通过自定义映射类型按照你的需要对属性进行持久化存取,实现NHibernate.UserTypes.IUserType也可以将一个属性保存到数据库的多个字段。下面演示将一个DateTime的属性按照yyyyMMdd、HHmmss这样的格式,以字符串的方式保存到数据库的两个字段中。
先用下面的语句为TBLPLANTITEM表添加两个字段:
ALTERTABLEdbo.TBLPLANTITEMADD
CREATE_DATEnvarchar(8)NULL,
CREATE_TIMEnvarchar(6)NULL 下面是自定义日期映射类型的实现:
publicclassMyDateTimeType:IUserType
{
publicMyDateTimeType()
{
}
publicboolIsMutable
{
get{returnfalse;}
}
publicTypeReturnedType
{
get{returntypeof(DateTime);}
}
publicSqlType[]SqlTypes
{
get
{
returnnewSqlType[]{newSqlType(DbType.String, 8),newSqlType(DbType.String, 6)};
}
}
publicobjectDeepCopy(objectvalue)
{
returnvalue;
}
publicnewboolEquals(objectx,objecty)
{
returnx ==y;
}
publicintGetHashCode(objectx)
{
returnx.GetHashCode();
}
publicobjectAssemble(objectcached,objectowner)
{
returnDeepCopy(cached);
}
publicobjectDisassemble(objectvalue)
{
returnDeepCopy(value);
}
publicobjectNullSafeGet(IDataReaderrs,string[]names,objectowner)
{
objectval1=NHibernate.NHibernateUtil.String.NullSafeGet(rs,names[0]);
objectval2=NHibernate.NHibernateUtil.String.NullSafeGet(rs,names[1]);
stringdate="1900-01-01",time="00:00:00";
if(val1!=null&&val1.ToString().Trim().Length==8)
date=val1.ToString().Substring(0,4)+"-"+val1.ToString().Substring(4,2)+"-"+val1.ToString().Substring(6,2);
if(val2!=null&&val2.ToString().Length==6)
time=val2.ToString().Substring(0,2)+":"+val2.ToString().Substring(2,2)+":"+val2.ToString().Substring(4,2);
returnDateTime.Parse(date+""+time);
}
publicvoidNullSafeSet(IDbCommandcmd,objectvalue,intindex)
{
stringdate="19000101",time="000000";
if(value!=null)
{
date=System.Convert.ToDateTime(value).ToString("yyyyMMdd");
time=System.Convert.ToDateTime(value).ToString("HHmmss");
}
NHibernate.NHibernateUtil.String.NullSafeSet(cmd,date,index);
NHibernate.NHibernateUtil.String.NullSafeSet(cmd,time,index+1);
}
publicobjectReplace(objectoriginal,objecttarget,objectowner)
{
returnoriginal;
}
} 下面是属性的定义:
publicDateTimeCreateTime
{
get{return_createTime;}
set{_createTime=value;}
}
privateDateTime_createTime; 下面是映射配置:
<propertyname="CreateTime"type="NH12.MyExample.Domain.MyDateTimeType,Domain">
<columnname="CREATE_DATE"sql-type="nvarchar(6)"/>
<columnname="CREATE_TIME"sql-type="nvarchar(6)"/>
</property>
4. 组件类型component 组合主键composite-id
首先先理解OO里关联、聚合、组合三个概念。
在NHibernate中,one-to-one、many-to-many等这几种映射关系,基本上都用于实现聚合、组合这两种对象关系。他们使用上的形式是,定义A和B两个类,分别为A和B独立的编写配置文件完成映射配置,A跟B通常存储在不同的表中,通过使用字段关联实现这种关系。
NHibernate中的组件(component)/组合(composite),最通常的情况是实现某种形式的组合关系。在趋向于细粒度对象的设计中会有不少这样的情况,本来用几个属性表示也可以满足要求,但是因为存在一些特定的规则、业务等逻辑关系,希望将这几个属性用一个类做一次封装。举个用户名的例子,在数据库的用户表中,我使用FirstName、LastName两个字段保存用户名;在对象模型中,我定义一个UserName的类,包含FirstName、LastName、FullName等。我们关注两个方面,首先UserName和User对象是存储在同一个表中的,我们完全没有必要去建立一个one-to-one的映射关系;其次它跟前面自定义映射类型中的场景也是有区别的,自定义映射类型中的例子将数据库的一个或多个字段映射到一个属性上,而这里UserName的例子,需要将数据库的多个字段映射到多个属性。从实现层面来看,UserName完全是一个独立的类,需要完成自己的映射;从概念层次看,它跟User对象是组合关系,数据保存在共同的地方。
我举的例子很简单,但是能够看明白组件类型的使用。TBLPLANTITEM表用两个字段PLANT_ID、ITEM_ID作为主键,我们可以定义一个组件类PlantItemID,作为PlantItem对象的ID属性:
publicclassPlantItemID
{
privatestring_plantID;
privatestring_itemID;
publicPlantItemID()
{
}
publicPlantItemID(stringplantID,stringitemID)
{
_plantID=plantID;
_itemID=itemID;
}
publicvirtualstringPlantID
{
get{return_plantID;}
set{_plantID=value;}
}
publicvirtualstringItemID
{
get{return_itemID;}
set{_itemID=value;}
}
#regionoverride
publicoverrideboolEquals(objectobj)
{
PlantItemIDo=objasPlantItemID;
if(o==null)
returnfalse;
returnthis.PlantID==o.PlantID&&this.ItemID==o.ItemID;
}
publicoverrideintGetHashCode()
{
returnthis.PlantID.GetHashCode()+this.ItemID.GetHashCode();
}
publicoverridestringToString()
{
returnthis.PlantID+""+this.ItemID;
}
#endregion
} 属性和映射配置文件如下:
publicvirtualPlantItemIDID
{
get{return_id;}
set{_id=value;}
}
privatePlantItemID_id;
<composite-idclass="NH12.MyExample.Domain.PlantItemID,Domain"name="ID">
<key-propertycolumn="PLANT_ID"name="PlantID"type="String"length="5"/>
<key-propertycolumn="ITEM_ID"name="ItemID"type="String"length="5"/>
</composite-id> 这是很简单的例子,我用一个组件类型实现组合主键(composite-id),对于组件类型仅用于普通属性的时候,使用component映射元素,在component元素下面除了property之外,也可以使用many-to-one、set等元素,因此可以构造很丰富的组建类型出来。对于普通的组件类型,不需要重载Equals、GetHashCode这两个方法,但对于组合主键一定要重写,这是因为NHibernate要使用这些方法判断两个对象的主键是否一样,确定两个对象是否相等。
到目前我们已经可以写出完整的Company、Plant、Item、PlantItem这几个类和相关的映射配置文件了,下面看一下如何使用:
ISessionFactorysessionFactory=newConfiguration().Configure().BuildSessionFactory();
ISessionsession=null;
ITransactiontran=null;
try
{
session=sessionFactory.OpenSession();
tran=session.BeginTransaction();
Companycompany=newCompany("1000","testcompany1","",newHashedSet<Plant>());
session.Save(company);
Plantplant1=newPlant("1101","testplant1",company);
session.Save(plant1);
Plantplant2=newPlant("1102","testplant2",company);
session.Save(plant2);
Itemitem1=newItem("FK1.1023.78AF","2.5#LCD","PCS",newdecimal(85.7));
session.Save(item1);
//创建PlantItem对象
PlantItemplantitem1=newPlantItem(newPlantItemID("1101","FK1.1023.78AF"),
"PCS1",ItemCategoryEnum.P,PurchaseCategoryEnum.JIT,StockOptionEnum.ERP);
session.Save(plantitem1);
PlantItemplantitem2=newPlantItem(newPlantItemID("1102","FK1.1023.78AF"),
"PCS2",ItemCategoryEnum.M,PurchaseCategoryEnum.PO,StockOptionEnum.Hub);
session.Save(plantitem2);
//获取PlantItem对象
//PlantItempi=session.Get<PlantItem>(newPlantItemID("1101","FK1.1023.78AF"));
tran.Commit();
}
catch
{
tran.Rollback();
}
finally
{
session.Close();
}
sessionFactory.Close(); 与数据库交互的SQL语句没有什么特别的地方,例如添加和获取PlantItem对象的SQL如下:
execsp_executesqlN'
INSERTINTOTBLPLANTITEM
(UNIT,ITEM_CATEGORY,PURCHASE_CATEGORY,STOCK_OPTION,CREATE_DATE,CREATE_TIME,PLANT_ID,ITEM_ID)
VALUES(@p0,@p1,@p2,@p3,@p4,@p5,@p6,@p7)',
N'@p0nvarchar(4),@p1nvarchar(1),@p2nvarchar(3),@p3int,@p4nvarchar(8),
@p5nvarchar(6),@p6nvarchar(4),@p7nvarchar(13)',
@p0=N'PCS1',@p1=N'P',@p2=N'JIT',@p3=1,@p4=N'00010101',
@p5=N'000000',@p6=N'1101',@p7=N'FK1.1023.78AF'
execsp_executesqlN'
SELECTplantitem0_.PLANT_IDasPLANT1_2_0_,plantitem0_.ITEM_IDasITEM2_2_0_,plantitem0_.UNITasUNIT2_0_,
plantitem0_.ITEM_CATEGORYasITEM4_2_0_,plantitem0_.PURCHASE_CATEGORYasPURCHASE5_2_0_,
plantitem0_.STOCK_OPTIONasSTOCK6_2_0_FROMTBLPLANTITEMplantitem0_
WHEREplantitem0_.PLANT_ID=@p0andplantitem0_.ITEM_ID=@p1',
N'@p0nvarchar(4),@p1nvarchar(13)',@p0=N'1101',@p1=N'FK1.1023.78AF'
上面演示了使用组件类型实现组合主键。如果采用poor model,用transaction script实现业务逻辑,这样的方式也足够用。缺点是如果想从PlantItem对象获取其它的一些资料,例如Plant对象的PlantName、Item对象的ItemDescription,还必须使用session调用Get()方法。这对于domain model中的逻辑没什么问题,但如果使用Castle MonoRail这样MVC的web框架(假定你是在模板视图中直接使用poor的domain model),在模板视图中处理起来就很不方便。
下面看一下组合主键的另外一种实现方式。我们让PlantItem对象聚合一个Plant,一个Item对象,在组合主键中指定这种关联关系。
下面是完整的PlantItem类定义:
publicclassPlantItem
{
privatePlant_plant;
privateItem_item;
privatestring_unit;
publicPlantItem(Plantplant,Itemitem,
stringunit,ItemCategoryEnumitemCategory,PurchaseCategoryEnumpurchaseCategory,StockOptionEnumstockOption)
{
_plant=plant;
_item=item;
_unit=unit;
_itemCategory=itemCategory;
_purchaseCategory=purchaseCategory;
_stockOption=stockOption;
}
publicPlantItem()
{
}
publicvirtualPlantPlant
{
get{return_plant;}
set{_plant=value;}
}
publicvirtualItemItem
{
get{return_item;}
set{_item=value;}
}
publicoverridestringUnit
{
get{return_unit;}
set{_unit=value;}
}
publicvirtualItemCategoryEnumItemCategory
{
get{return_itemCategory;}
set{_itemCategory=value;}
}
privateItemCategoryEnum_itemCategory;
publicvirtualPurchaseCategoryEnumPurchaseCategory
{
get{return_purchaseCategory;}
set{_purchaseCategory=value;}
}
privatePurchaseCategoryEnum_purchaseCategory;
publicvirtualStockOptionEnumStockOption
{
get{return_stockOption;}
set{_stockOption=value;}
}
privateStockOptionEnum_stockOption;
#regionoverride
publicoverrideboolEquals(objectobj)
{
if(this==obj)returntrue;
if(obj==null||obj.GetType()!=this.GetType())
returnfalse;
PlantItemplantItem=objasPlantItem;
returnplantItem!=null&&plantItem.Plant==_plant&&plantItem.Item==_item;
}
publicoverrideintGetHashCode()
{
return_plant.GetHashCode()+_item.GetHashCode();
}
publicoverridestringToString()
{
return_plant.ToString()+""+_item.ToString();
}
#endregion
} 下面是映射配置文件:
<hibernate-mappingxmlns="urn:nhibernate-mapping-2.2"namespace="NH12.MyExample.Domain"assembly="Domain">
<classname="PlantItem"table="TBLPLANTITEM">
<composite-id>
<key-many-to-onename="Plant"class="Plant"column="PLANT_ID"lazy="proxy"/>
<key-many-to-onename="Item"class="Item"column="ITEM_ID"lazy="proxy"/>
</composite-id>
<propertyname="Unit">
<columnname="UNIT"length="10"sql-type="nvarchar"not-null="false"/>
</property>
<propertyname="ItemCategory"type="NH12.MyExample.Domain.ItemCategoryType,Domain">
<columnname="ITEM_CATEGORY"sql-type="nvarchar"length="3"not-null="false"/>
</property>
<propertyname="PurchaseCategory"type="NH12.MyExample.Domain.PurchaseCategoryType,Domain">
<columnname="PURCHASE_CATEGORY"sql-type="nvarchar"length="5"not-null="false"/>
</property>
<propertyname="StockOption">
<columnname="STOCK_OPTION"sql-type="int"not-null="false"/>
</property>
</class>
</hibernate-mapping> 实体类的实现和配置都比较简单,下面看一下怎样使用。
ISessionFactorysessionFactory=newConfiguration().Configure().BuildSessionFactory();
ISessionsession=null;
ITransactiontran=null;
try
{
session=sessionFactory.OpenSession();
tran=session.BeginTransaction();
Companycompany=newCompany("1000","testcompany1","",newHashedSet<Plant>());
session.Save(company);
Plantplant1=newPlant("1101","testplant1",company);
session.Save(plant1);
Plantplant2=newPlant("1102","testplant2",company);
session.Save(plant2);
Itemitem1=newItem("FK1.1023.78AF","2.5#LCD","PCS",newdecimal(85.7));
session.Save(item1);
//创建PlantItem对象
PlantItemplantitem1=
newPlantItem(plant1,item1,"PCS1",ItemCategoryEnum.P,PurchaseCategoryEnum.JIT,StockOptionEnum.ERP);
session.Save(plantitem1);
PlantItemplantitem2=
newPlantItem(plant2,item1,"PCS2",ItemCategoryEnum.M,PurchaseCategoryEnum.PO,StockOptionEnum.Hub);
session.Save(plantitem2);
tran.Commit();
}
catch
{
tran.Rollback();
}
finally
{
session.Close();
}
sessionFactory.Close(); 下面是获取PlantItem对象的代码:
Plantplant=session.Get<Plant>("1101");
Itemitem=session.Get<Item>("FK1.1023.78AF");
PlantItempi=newPlantItem();
pi.Plant=plant;
pi.Item=item;
pi=session.Get<PlantItem>(pi); 在调用session.Get(object id)方法获取PlantItem对象时,我们传入的参数id对象必须要有两个属性:Plant和Item,这是因为在映射配置文件的composite-id中我们指定了这两个属性,NHibernate从id.Plant中取PlantID,从id.Item中取ItemID,根据这两个值去查询PlantItem对象。
这样的方式在获取PlantItem对象时做法上有点麻烦,并且导致了lasy属性失效,从而每次加载PlantItem对象时必须加载相关的Plant和Item对象。适当的作一些封装,可以使外部在获取PlantItem对象时代码简化一些,但数据的加载是不可避免的。除非特定的场合下你完全确定不需要使用Plant和Item对象,这样你可以new一个空的Plant和Item对象,并设置好PlantID和ItemID值,然后再调用session.Get(object id)方法获取PlantItem,这样NHibernate是不会再加载Plant和Item对象的。
因为PlantItem对象已经聚合了一个Plant和一个Item对象,这样在Castle MonoRail的web框架下使用起来会比较方便。
综合上面两种实现组合主键的方式来看,实现或者使用层面总有一些不和谐,或者是感觉不伦不类的地方。NHibernate Best Practice中第二条的主要意思,就是提倡每个对象使用一个与业务无关的ID作为对象ID,这样使用NHB,在类的设计和使用上的确会显得自然很多。例如上面PlantItem类的例子中,添加一个无意义的ID字段,NHB的delete、update,以及缓存机制等,会基于这个ID进行;获取PlantItem对象,使用一个HQL,给出PlantID、ItemID参数就可以。
目前为止,个人的设计思想是尽量采取业务相关的主键,如果一定要使用一个语义上的ID,也不要用它来做关联。就是说对整个Domain而言,把无意义的ID当作不存在,它完全只是NHibernate专用的一个属性。
5. 自定义组合映射类型ICompositeUserType
上面举的组件类型非常简单,当组件类型变得复杂,映射关系比较多,或者是组件类型中有属性需要使用自定义映射来完成时,可以使用一个自定义组合映射类型进行封装。下面是一个示例:
publicclass UserNameType:ICompositeUserType
{
publicPlantItemIDType()
{
}
publicboolIsMutable
{
get{returntrue;}
}
///<summary>
///对应的组件类型有哪些属性
///</summary>
publicstring[]PropertyNames
{
get{returnnewstring[]{"FirstName","LastName"};}
}
///<summary>
///对应的组件类型各属性的NHibernate.Type.IType
///</summary>
publicIType[]PropertyTypes
{
get{returnnewIType[]{NHibernate.NHibernateUtil.String,NHibernate.NHibernateUtil.String};}
}
///<summary>
///对应的组件类型ClassType
///</summary>
publicTypeReturnedClass
{
get{returntypeof(UserName);}
}
publicobjectDeepCopy(objectvalue)
{
UserName obj=valueas UserName;
if(obj==null)
returnnull;
returnnew UserName(obj.FirstName,obj.LastName);
}
publicobjectAssemble(objectcached,ISessionImplementorsession,objectowner)
{
returnthis.DeepCopy(cached);
}
publicobjectDisassemble(objectvalue,ISessionImplementorsession)
{
returnthis.DeepCopy(value);
}
publicnewboolEquals(objectx,objecty)
{
UserName objx=xas UserName;
UserName objy=yas UserName;
if(objx==null&&objy==null)
returntrue;
if(objx==null||objy==null)
returnfalse;
returnobjx.Equals(objy);
}
publicintGetHashCode(objectx)
{
UserName obj=xas UserName;
if(obj==null)
return0;
returnobj.GetHashCode();
}
publicobjectReplace(objectoriginal,objecttarget,ISessionImplementorsession,objectowner)
{
returnthis.DeepCopy(original);
}
///<summary>
///从组件类型的实例中读取属性值
///</summary>
publicobjectGetPropertyValue(objectcomponent,intproperty)
{
UserName obj=componentas UserName;
if(obj==null)returnnull;
if(property==0)
returnobj.FirstName;
if(property==1)
returnobj.LastName;
returnnull;
}
///<summary>
///为组件类型的实例设置值
///</summary>
publicvoidSetPropertyValue(objectcomponent,intproperty,objectvalue)
{
UserName obj=componentas UserName;
if(obj==null)
return;
if(property==0)
obj.FirstName =valueasstring;
elseif(property==1)
obj.LastName =valueasstring;
}
///<summary>
///从DataReader读取组件类型需要的字段值,生成组件类型
///</summary>
publicobjectNullSafeGet(IDataReaderdr,string[]names,ISessionImplementorsession,objectowner)
{
stringval1=NHibernate.NHibernateUtil.String.NullSafeGet(dr,names[0])asstring;
stringval2=NHibernate.NHibernateUtil.String.NullSafeGet(dr,names[1])asstring;
returnnew UserName(val1,val2);
}
///<summary>
///为组件类型生成DbParameter参数值
///</summary>
publicvoidNullSafeSet(IDbCommandcmd,objectvalue,intindex,ISessionImplementorsession)
{
UserName obj=valueas UserName;
if(obj!=null)
{
NHibernate.NHibernateUtil.String.NullSafeSet(cmd,obj.FirstName,index);
NHibernate.NHibernateUtil.String.NullSafeSet(cmd,obj.LastName,index+1);
}
else
{
NHibernate.NHibernateUtil.String.NullSafeSet(cmd,DBNull.Value,index);
NHibernate.NHibernateUtil.String.NullSafeSet(cmd,DBNull.Value,index+1);
}
}
} 配置示例:
<propertyname="ID"type="NH12.MyExample.Domain.UserNameType,Domain">
<columnname="FIRST_NAME"/>
<columnname="LAST_NAME"/>
</property> 映射细节已经在UserNameType中实现了,因此配置中只需要指定属性名字和对应的自定义组件映射类型,并告诉NHibernate框架各个字段名称就OK,其他映射细节的实现已经不包含在映射配置文件中。
本文共计4768个文字,预计阅读时间需要20分钟。
1. 理解了one-to-many和many-to-one关系后,学习many-to-many用法变得简单。因此,通过参考本系列前几篇内容,可以轻松实现Vendor、Company、Plant和Item这几个对象的关联。
+ Company与Vendor之间建立关联:
1. one-to-many, many-to-one知道了many-to-many用法之后,many-to-one、one-to-many就很简单。因此参考这个系列前面几篇,可以很轻松的实现Vendor、Company、Plant、Item这几个对象。
Company与Vendor之间建立了一个一对多的关系,下面是Company类中Plants集合属性的定义
publicvirtualISet<Plant>Plants
{
get{return_plant;}
set{_plant=value;}
}
privateISet<Plant>_plant; 下面是这个属性的映射配置
<setname="Plants"table="TBLPLANT"lazy="true">
<keycolumn="COMPANY_ID"/>
<one-to-manyclass="Plant"not-found="ignore"/>
</set> 下面是Plant类中Company属性的定义与配置
publicvirtualCompanyCompany
{
get{return_company;}
set{_company=value;}
}
privateCompany_company;
<many-to-onename="Company"class="Company"not-found="exception"lazy="proxy"column="COMPANY_ID"/>
2. 枚举类型
NHibernate直接支持枚举类型的映射,这种支持方式,在数据库中保存的是枚举的整数值。在TBLPLANTITEM中有三个字段对应的对象属性使用枚举类型:ITEM_CATEGORY、PURCHASE_CATEGORY、STOCK_OPTION,我把STOCK_OPTION字段设置成整数类型,用来测试NHB对枚举映射的直接支持方式,而其它两个设成了字符串类型,用于保存枚举的字符串描述。
在数据库保存枚举的字符串描述需要使用自定义映射类型,将在下面一节中讲述,本节看一下直接对枚举类型进行映射。
StockOptionEnum枚举的定义:
publicenumStockOptionEnum
{
None=0,
ERP=1,
Hub=2
} PlantItem类中StockOption属性的定义:
publicvirtualStockOptionEnumStockOption
{
get{return_stockOption;}
set{_stockOption=value;}
}
privateStockOptionEnum_stockOption; 属性的配置节点:
<propertyname="StockOption">
<columnname="STOCK_OPTION"sql-type="int"not-null="false"/>
</property> NHibernate默认支持的枚举映射用起来很简单,这可能是能够将枚举值强制转化成整数这样一个值类型的原因。这种方式,枚举属性保存在数据库中的是整数值。
3. 自定义类型、自定义映射类型IUserType
首先在概念方面看一下。当你觉得某个属性需要满足的业务逻辑比较复杂,用.Net标准的数据类型无法满足你的需求时,你会选择为这个属性单独定义一个类,作为这个属性的类型,可以称这个为自定义类型/细粒度对象。某些情况下,将属性进行持久化映射时,并不是简单、直接的进行存取,也就是说你可能无法通过NHibernate提供的标准映射方法在属性和持久化媒介之间进行映射。这种情况下NHibernate提供一个机制,让你自己可以完全的控制映射行为,这就是为你的属性实现一个NHibernate.UserTypes.IUserType类,我称这个为自定义映射类型。自定义映射类型这个名称比较合适,因为从名称你就可以猜测到它的主要职责/作用就是完成属性与持久化媒介之间的映射。
我们先看一下怎样通过自定义映射类型保存枚举的字符串描述值。下面是ItemCategoryEnum、PurchaseCategoryEnum两个枚举的定义:
publicenumItemCategoryEnum
{
P=1,//Product
M=2,//material
}
publicenumPurchaseCategoryEnum
{
PO=1,
JIT=2
} 下面是自定义映射类型实现保存枚举的字符串描述。为了简化,我定义了一个抽象类实现NHibernate.UserTypes.IUserType,然后各个枚举的自定义映射类型就很容易实现了:
#regionEnumType
///<summary>
///自定义枚举映射类型
///</summary>
publicabstractclassMyEnumType:IUserType
{
privateType_type;
privateint_length;
privateMyEnumType()
{
}
publicMyEnumType(Typetype,intlength)
{
this._type=type;
this._length=length;
}
///<summary>
///自定义类型的对象实例是否会发生改变
///</summary>
publicboolIsMutable
{
get{returnfalse;}
}
publicTypeReturnedType
{
get{return_type;}
}
///<summary>
/// 属性对应的数据库字段类型
///</summary>
publicSqlType[]SqlTypes
{
get{returnnewSqlType[]{newSqlType(DbType.String,this._length)};}
}
///<summary>
///如果IsMutable为true,此处应当实现对象实例的DeepCopy
///</summary>
publicobjectDeepCopy(objectvalue)
{
returnvalue;
}
publicnewboolEquals(objectx,objecty)
{
returnx.ToString().Trim()==y.ToString().Trim();
}
publicintGetHashCode(objectx)
{
returnx.ToString().GetHashCode();
}
///<summary>
///从缓存中取对象
///</summary>
publicobjectAssemble(objectcached,objectowner)
{
returnDeepCopy(cached);
}
///<summary>
///将对象放入缓存前的处理
///</summary>
publicobjectDisassemble(objectvalue)
{
returnDeepCopy(value);
}
///<summary>
///读取数据库字段值(DataReader),转换成实体属性
///</summary>
publicobjectNullSafeGet(IDataReaderrs,string[]names,objectowner)
{
objectname=NHibernate.NHibernateUtil.String.NullSafeGet(rs,names[0]);
if(name==null)
thrownewException(_type.Name+"cannotbenull");
returnEnum.Parse(_type,name.ToString(),true);
}
///<summary>
///为属性的存取设置DbCommand参数
///这里我们要保存枚举的字符串描述,因此将value直接转换成字符串
///</summary>
publicvoidNullSafeSet(IDbCommandcmd,objectvalue,intindex)
{
if(value==null)
thrownewException(_type.Name+"cannotbenull");
NHibernate.NHibernateUtil.String.NullSafeSet(cmd,value.ToString(),index);
}
publicobjectReplace(objectoriginal,objecttarget,objectowner)
{
returnoriginal;
}
}
publicclassPurchaseCategoryType:MyEnumType
{
publicPurchaseCategoryType()
:base(typeof(PurchaseCategoryEnum),5)
{
}
}
publicclassItemCategoryType:MyEnumType
{
publicItemCategoryType()
:base(typeof(ItemCategoryEnum),3)
{
}
}
#endregion 下面是PlantItem类中PurchaseCategoryType和ItemCategoryType属性的定义:
publicvirtualItemCategoryEnumItemCategory
{
get{return_itemCategory;}
set{_itemCategory=value;}
}
privateItemCategoryEnum_itemCategory;
publicvirtualPurchaseCategoryEnumPurchaseCategory
{
get{return_purchaseCategory;}
set{_purchaseCategory=value;}
}
privatePurchaseCategoryEnum_purchaseCategory; 这两个属性的映射配置:
<propertyname="ItemCategory"type="NH12.MyExample.Domain.ItemCategoryType,Domain">
<columnname="ITEM_CATEGORY"sql-type="nvarchar"length="3"not-null="false"/>
</property>
<propertyname="PurchaseCategory"type="NH12.MyExample.Domain.PurchaseCategoryType,Domain">
<columnname="PURCHASE_CATEGORY"sql-type="nvarchar"length="5"not-null="false"/>
</property> 从上面可以看出,实体的属性使用枚举类型(或者其它的自定义类型),然后为枚举类型定义一个映射类型(上面的PurchaseCategoryType和ItemCategoryType),IUserType接口定义的主要职责,是如何将数据库取出来的值转换成枚举类型(或者自定义类型)(NullSafeGet方法)、如何将枚举类型(或者自定义类型)转换成数据库操作的DbParameter值(NullSafeSet方法),其它一些是支持NHibernate的OR机制必须的功能,例如了解属性类型(TypeReturnedType)、数据库字段的类型(SqlType[]SqlTypes)、对缓存机制的支持(Assemble、Disassemble方法)等等。
上面的示例演示如何通过自定义映射类型按照你的需要对属性进行持久化存取,实现NHibernate.UserTypes.IUserType也可以将一个属性保存到数据库的多个字段。下面演示将一个DateTime的属性按照yyyyMMdd、HHmmss这样的格式,以字符串的方式保存到数据库的两个字段中。
先用下面的语句为TBLPLANTITEM表添加两个字段:
ALTERTABLEdbo.TBLPLANTITEMADD
CREATE_DATEnvarchar(8)NULL,
CREATE_TIMEnvarchar(6)NULL 下面是自定义日期映射类型的实现:
publicclassMyDateTimeType:IUserType
{
publicMyDateTimeType()
{
}
publicboolIsMutable
{
get{returnfalse;}
}
publicTypeReturnedType
{
get{returntypeof(DateTime);}
}
publicSqlType[]SqlTypes
{
get
{
returnnewSqlType[]{newSqlType(DbType.String, 8),newSqlType(DbType.String, 6)};
}
}
publicobjectDeepCopy(objectvalue)
{
returnvalue;
}
publicnewboolEquals(objectx,objecty)
{
returnx ==y;
}
publicintGetHashCode(objectx)
{
returnx.GetHashCode();
}
publicobjectAssemble(objectcached,objectowner)
{
returnDeepCopy(cached);
}
publicobjectDisassemble(objectvalue)
{
returnDeepCopy(value);
}
publicobjectNullSafeGet(IDataReaderrs,string[]names,objectowner)
{
objectval1=NHibernate.NHibernateUtil.String.NullSafeGet(rs,names[0]);
objectval2=NHibernate.NHibernateUtil.String.NullSafeGet(rs,names[1]);
stringdate="1900-01-01",time="00:00:00";
if(val1!=null&&val1.ToString().Trim().Length==8)
date=val1.ToString().Substring(0,4)+"-"+val1.ToString().Substring(4,2)+"-"+val1.ToString().Substring(6,2);
if(val2!=null&&val2.ToString().Length==6)
time=val2.ToString().Substring(0,2)+":"+val2.ToString().Substring(2,2)+":"+val2.ToString().Substring(4,2);
returnDateTime.Parse(date+""+time);
}
publicvoidNullSafeSet(IDbCommandcmd,objectvalue,intindex)
{
stringdate="19000101",time="000000";
if(value!=null)
{
date=System.Convert.ToDateTime(value).ToString("yyyyMMdd");
time=System.Convert.ToDateTime(value).ToString("HHmmss");
}
NHibernate.NHibernateUtil.String.NullSafeSet(cmd,date,index);
NHibernate.NHibernateUtil.String.NullSafeSet(cmd,time,index+1);
}
publicobjectReplace(objectoriginal,objecttarget,objectowner)
{
returnoriginal;
}
} 下面是属性的定义:
publicDateTimeCreateTime
{
get{return_createTime;}
set{_createTime=value;}
}
privateDateTime_createTime; 下面是映射配置:
<propertyname="CreateTime"type="NH12.MyExample.Domain.MyDateTimeType,Domain">
<columnname="CREATE_DATE"sql-type="nvarchar(6)"/>
<columnname="CREATE_TIME"sql-type="nvarchar(6)"/>
</property>
4. 组件类型component 组合主键composite-id
首先先理解OO里关联、聚合、组合三个概念。
在NHibernate中,one-to-one、many-to-many等这几种映射关系,基本上都用于实现聚合、组合这两种对象关系。他们使用上的形式是,定义A和B两个类,分别为A和B独立的编写配置文件完成映射配置,A跟B通常存储在不同的表中,通过使用字段关联实现这种关系。
NHibernate中的组件(component)/组合(composite),最通常的情况是实现某种形式的组合关系。在趋向于细粒度对象的设计中会有不少这样的情况,本来用几个属性表示也可以满足要求,但是因为存在一些特定的规则、业务等逻辑关系,希望将这几个属性用一个类做一次封装。举个用户名的例子,在数据库的用户表中,我使用FirstName、LastName两个字段保存用户名;在对象模型中,我定义一个UserName的类,包含FirstName、LastName、FullName等。我们关注两个方面,首先UserName和User对象是存储在同一个表中的,我们完全没有必要去建立一个one-to-one的映射关系;其次它跟前面自定义映射类型中的场景也是有区别的,自定义映射类型中的例子将数据库的一个或多个字段映射到一个属性上,而这里UserName的例子,需要将数据库的多个字段映射到多个属性。从实现层面来看,UserName完全是一个独立的类,需要完成自己的映射;从概念层次看,它跟User对象是组合关系,数据保存在共同的地方。
我举的例子很简单,但是能够看明白组件类型的使用。TBLPLANTITEM表用两个字段PLANT_ID、ITEM_ID作为主键,我们可以定义一个组件类PlantItemID,作为PlantItem对象的ID属性:
publicclassPlantItemID
{
privatestring_plantID;
privatestring_itemID;
publicPlantItemID()
{
}
publicPlantItemID(stringplantID,stringitemID)
{
_plantID=plantID;
_itemID=itemID;
}
publicvirtualstringPlantID
{
get{return_plantID;}
set{_plantID=value;}
}
publicvirtualstringItemID
{
get{return_itemID;}
set{_itemID=value;}
}
#regionoverride
publicoverrideboolEquals(objectobj)
{
PlantItemIDo=objasPlantItemID;
if(o==null)
returnfalse;
returnthis.PlantID==o.PlantID&&this.ItemID==o.ItemID;
}
publicoverrideintGetHashCode()
{
returnthis.PlantID.GetHashCode()+this.ItemID.GetHashCode();
}
publicoverridestringToString()
{
returnthis.PlantID+""+this.ItemID;
}
#endregion
} 属性和映射配置文件如下:
publicvirtualPlantItemIDID
{
get{return_id;}
set{_id=value;}
}
privatePlantItemID_id;
<composite-idclass="NH12.MyExample.Domain.PlantItemID,Domain"name="ID">
<key-propertycolumn="PLANT_ID"name="PlantID"type="String"length="5"/>
<key-propertycolumn="ITEM_ID"name="ItemID"type="String"length="5"/>
</composite-id> 这是很简单的例子,我用一个组件类型实现组合主键(composite-id),对于组件类型仅用于普通属性的时候,使用component映射元素,在component元素下面除了property之外,也可以使用many-to-one、set等元素,因此可以构造很丰富的组建类型出来。对于普通的组件类型,不需要重载Equals、GetHashCode这两个方法,但对于组合主键一定要重写,这是因为NHibernate要使用这些方法判断两个对象的主键是否一样,确定两个对象是否相等。
到目前我们已经可以写出完整的Company、Plant、Item、PlantItem这几个类和相关的映射配置文件了,下面看一下如何使用:
ISessionFactorysessionFactory=newConfiguration().Configure().BuildSessionFactory();
ISessionsession=null;
ITransactiontran=null;
try
{
session=sessionFactory.OpenSession();
tran=session.BeginTransaction();
Companycompany=newCompany("1000","testcompany1","",newHashedSet<Plant>());
session.Save(company);
Plantplant1=newPlant("1101","testplant1",company);
session.Save(plant1);
Plantplant2=newPlant("1102","testplant2",company);
session.Save(plant2);
Itemitem1=newItem("FK1.1023.78AF","2.5#LCD","PCS",newdecimal(85.7));
session.Save(item1);
//创建PlantItem对象
PlantItemplantitem1=newPlantItem(newPlantItemID("1101","FK1.1023.78AF"),
"PCS1",ItemCategoryEnum.P,PurchaseCategoryEnum.JIT,StockOptionEnum.ERP);
session.Save(plantitem1);
PlantItemplantitem2=newPlantItem(newPlantItemID("1102","FK1.1023.78AF"),
"PCS2",ItemCategoryEnum.M,PurchaseCategoryEnum.PO,StockOptionEnum.Hub);
session.Save(plantitem2);
//获取PlantItem对象
//PlantItempi=session.Get<PlantItem>(newPlantItemID("1101","FK1.1023.78AF"));
tran.Commit();
}
catch
{
tran.Rollback();
}
finally
{
session.Close();
}
sessionFactory.Close(); 与数据库交互的SQL语句没有什么特别的地方,例如添加和获取PlantItem对象的SQL如下:
execsp_executesqlN'
INSERTINTOTBLPLANTITEM
(UNIT,ITEM_CATEGORY,PURCHASE_CATEGORY,STOCK_OPTION,CREATE_DATE,CREATE_TIME,PLANT_ID,ITEM_ID)
VALUES(@p0,@p1,@p2,@p3,@p4,@p5,@p6,@p7)',
N'@p0nvarchar(4),@p1nvarchar(1),@p2nvarchar(3),@p3int,@p4nvarchar(8),
@p5nvarchar(6),@p6nvarchar(4),@p7nvarchar(13)',
@p0=N'PCS1',@p1=N'P',@p2=N'JIT',@p3=1,@p4=N'00010101',
@p5=N'000000',@p6=N'1101',@p7=N'FK1.1023.78AF'
execsp_executesqlN'
SELECTplantitem0_.PLANT_IDasPLANT1_2_0_,plantitem0_.ITEM_IDasITEM2_2_0_,plantitem0_.UNITasUNIT2_0_,
plantitem0_.ITEM_CATEGORYasITEM4_2_0_,plantitem0_.PURCHASE_CATEGORYasPURCHASE5_2_0_,
plantitem0_.STOCK_OPTIONasSTOCK6_2_0_FROMTBLPLANTITEMplantitem0_
WHEREplantitem0_.PLANT_ID=@p0andplantitem0_.ITEM_ID=@p1',
N'@p0nvarchar(4),@p1nvarchar(13)',@p0=N'1101',@p1=N'FK1.1023.78AF'
上面演示了使用组件类型实现组合主键。如果采用poor model,用transaction script实现业务逻辑,这样的方式也足够用。缺点是如果想从PlantItem对象获取其它的一些资料,例如Plant对象的PlantName、Item对象的ItemDescription,还必须使用session调用Get()方法。这对于domain model中的逻辑没什么问题,但如果使用Castle MonoRail这样MVC的web框架(假定你是在模板视图中直接使用poor的domain model),在模板视图中处理起来就很不方便。
下面看一下组合主键的另外一种实现方式。我们让PlantItem对象聚合一个Plant,一个Item对象,在组合主键中指定这种关联关系。
下面是完整的PlantItem类定义:
publicclassPlantItem
{
privatePlant_plant;
privateItem_item;
privatestring_unit;
publicPlantItem(Plantplant,Itemitem,
stringunit,ItemCategoryEnumitemCategory,PurchaseCategoryEnumpurchaseCategory,StockOptionEnumstockOption)
{
_plant=plant;
_item=item;
_unit=unit;
_itemCategory=itemCategory;
_purchaseCategory=purchaseCategory;
_stockOption=stockOption;
}
publicPlantItem()
{
}
publicvirtualPlantPlant
{
get{return_plant;}
set{_plant=value;}
}
publicvirtualItemItem
{
get{return_item;}
set{_item=value;}
}
publicoverridestringUnit
{
get{return_unit;}
set{_unit=value;}
}
publicvirtualItemCategoryEnumItemCategory
{
get{return_itemCategory;}
set{_itemCategory=value;}
}
privateItemCategoryEnum_itemCategory;
publicvirtualPurchaseCategoryEnumPurchaseCategory
{
get{return_purchaseCategory;}
set{_purchaseCategory=value;}
}
privatePurchaseCategoryEnum_purchaseCategory;
publicvirtualStockOptionEnumStockOption
{
get{return_stockOption;}
set{_stockOption=value;}
}
privateStockOptionEnum_stockOption;
#regionoverride
publicoverrideboolEquals(objectobj)
{
if(this==obj)returntrue;
if(obj==null||obj.GetType()!=this.GetType())
returnfalse;
PlantItemplantItem=objasPlantItem;
returnplantItem!=null&&plantItem.Plant==_plant&&plantItem.Item==_item;
}
publicoverrideintGetHashCode()
{
return_plant.GetHashCode()+_item.GetHashCode();
}
publicoverridestringToString()
{
return_plant.ToString()+""+_item.ToString();
}
#endregion
} 下面是映射配置文件:
<hibernate-mappingxmlns="urn:nhibernate-mapping-2.2"namespace="NH12.MyExample.Domain"assembly="Domain">
<classname="PlantItem"table="TBLPLANTITEM">
<composite-id>
<key-many-to-onename="Plant"class="Plant"column="PLANT_ID"lazy="proxy"/>
<key-many-to-onename="Item"class="Item"column="ITEM_ID"lazy="proxy"/>
</composite-id>
<propertyname="Unit">
<columnname="UNIT"length="10"sql-type="nvarchar"not-null="false"/>
</property>
<propertyname="ItemCategory"type="NH12.MyExample.Domain.ItemCategoryType,Domain">
<columnname="ITEM_CATEGORY"sql-type="nvarchar"length="3"not-null="false"/>
</property>
<propertyname="PurchaseCategory"type="NH12.MyExample.Domain.PurchaseCategoryType,Domain">
<columnname="PURCHASE_CATEGORY"sql-type="nvarchar"length="5"not-null="false"/>
</property>
<propertyname="StockOption">
<columnname="STOCK_OPTION"sql-type="int"not-null="false"/>
</property>
</class>
</hibernate-mapping> 实体类的实现和配置都比较简单,下面看一下怎样使用。
ISessionFactorysessionFactory=newConfiguration().Configure().BuildSessionFactory();
ISessionsession=null;
ITransactiontran=null;
try
{
session=sessionFactory.OpenSession();
tran=session.BeginTransaction();
Companycompany=newCompany("1000","testcompany1","",newHashedSet<Plant>());
session.Save(company);
Plantplant1=newPlant("1101","testplant1",company);
session.Save(plant1);
Plantplant2=newPlant("1102","testplant2",company);
session.Save(plant2);
Itemitem1=newItem("FK1.1023.78AF","2.5#LCD","PCS",newdecimal(85.7));
session.Save(item1);
//创建PlantItem对象
PlantItemplantitem1=
newPlantItem(plant1,item1,"PCS1",ItemCategoryEnum.P,PurchaseCategoryEnum.JIT,StockOptionEnum.ERP);
session.Save(plantitem1);
PlantItemplantitem2=
newPlantItem(plant2,item1,"PCS2",ItemCategoryEnum.M,PurchaseCategoryEnum.PO,StockOptionEnum.Hub);
session.Save(plantitem2);
tran.Commit();
}
catch
{
tran.Rollback();
}
finally
{
session.Close();
}
sessionFactory.Close(); 下面是获取PlantItem对象的代码:
Plantplant=session.Get<Plant>("1101");
Itemitem=session.Get<Item>("FK1.1023.78AF");
PlantItempi=newPlantItem();
pi.Plant=plant;
pi.Item=item;
pi=session.Get<PlantItem>(pi); 在调用session.Get(object id)方法获取PlantItem对象时,我们传入的参数id对象必须要有两个属性:Plant和Item,这是因为在映射配置文件的composite-id中我们指定了这两个属性,NHibernate从id.Plant中取PlantID,从id.Item中取ItemID,根据这两个值去查询PlantItem对象。
这样的方式在获取PlantItem对象时做法上有点麻烦,并且导致了lasy属性失效,从而每次加载PlantItem对象时必须加载相关的Plant和Item对象。适当的作一些封装,可以使外部在获取PlantItem对象时代码简化一些,但数据的加载是不可避免的。除非特定的场合下你完全确定不需要使用Plant和Item对象,这样你可以new一个空的Plant和Item对象,并设置好PlantID和ItemID值,然后再调用session.Get(object id)方法获取PlantItem,这样NHibernate是不会再加载Plant和Item对象的。
因为PlantItem对象已经聚合了一个Plant和一个Item对象,这样在Castle MonoRail的web框架下使用起来会比较方便。
综合上面两种实现组合主键的方式来看,实现或者使用层面总有一些不和谐,或者是感觉不伦不类的地方。NHibernate Best Practice中第二条的主要意思,就是提倡每个对象使用一个与业务无关的ID作为对象ID,这样使用NHB,在类的设计和使用上的确会显得自然很多。例如上面PlantItem类的例子中,添加一个无意义的ID字段,NHB的delete、update,以及缓存机制等,会基于这个ID进行;获取PlantItem对象,使用一个HQL,给出PlantID、ItemID参数就可以。
目前为止,个人的设计思想是尽量采取业务相关的主键,如果一定要使用一个语义上的ID,也不要用它来做关联。就是说对整个Domain而言,把无意义的ID当作不存在,它完全只是NHibernate专用的一个属性。
5. 自定义组合映射类型ICompositeUserType
上面举的组件类型非常简单,当组件类型变得复杂,映射关系比较多,或者是组件类型中有属性需要使用自定义映射来完成时,可以使用一个自定义组合映射类型进行封装。下面是一个示例:
publicclass UserNameType:ICompositeUserType
{
publicPlantItemIDType()
{
}
publicboolIsMutable
{
get{returntrue;}
}
///<summary>
///对应的组件类型有哪些属性
///</summary>
publicstring[]PropertyNames
{
get{returnnewstring[]{"FirstName","LastName"};}
}
///<summary>
///对应的组件类型各属性的NHibernate.Type.IType
///</summary>
publicIType[]PropertyTypes
{
get{returnnewIType[]{NHibernate.NHibernateUtil.String,NHibernate.NHibernateUtil.String};}
}
///<summary>
///对应的组件类型ClassType
///</summary>
publicTypeReturnedClass
{
get{returntypeof(UserName);}
}
publicobjectDeepCopy(objectvalue)
{
UserName obj=valueas UserName;
if(obj==null)
returnnull;
returnnew UserName(obj.FirstName,obj.LastName);
}
publicobjectAssemble(objectcached,ISessionImplementorsession,objectowner)
{
returnthis.DeepCopy(cached);
}
publicobjectDisassemble(objectvalue,ISessionImplementorsession)
{
returnthis.DeepCopy(value);
}
publicnewboolEquals(objectx,objecty)
{
UserName objx=xas UserName;
UserName objy=yas UserName;
if(objx==null&&objy==null)
returntrue;
if(objx==null||objy==null)
returnfalse;
returnobjx.Equals(objy);
}
publicintGetHashCode(objectx)
{
UserName obj=xas UserName;
if(obj==null)
return0;
returnobj.GetHashCode();
}
publicobjectReplace(objectoriginal,objecttarget,ISessionImplementorsession,objectowner)
{
returnthis.DeepCopy(original);
}
///<summary>
///从组件类型的实例中读取属性值
///</summary>
publicobjectGetPropertyValue(objectcomponent,intproperty)
{
UserName obj=componentas UserName;
if(obj==null)returnnull;
if(property==0)
returnobj.FirstName;
if(property==1)
returnobj.LastName;
returnnull;
}
///<summary>
///为组件类型的实例设置值
///</summary>
publicvoidSetPropertyValue(objectcomponent,intproperty,objectvalue)
{
UserName obj=componentas UserName;
if(obj==null)
return;
if(property==0)
obj.FirstName =valueasstring;
elseif(property==1)
obj.LastName =valueasstring;
}
///<summary>
///从DataReader读取组件类型需要的字段值,生成组件类型
///</summary>
publicobjectNullSafeGet(IDataReaderdr,string[]names,ISessionImplementorsession,objectowner)
{
stringval1=NHibernate.NHibernateUtil.String.NullSafeGet(dr,names[0])asstring;
stringval2=NHibernate.NHibernateUtil.String.NullSafeGet(dr,names[1])asstring;
returnnew UserName(val1,val2);
}
///<summary>
///为组件类型生成DbParameter参数值
///</summary>
publicvoidNullSafeSet(IDbCommandcmd,objectvalue,intindex,ISessionImplementorsession)
{
UserName obj=valueas UserName;
if(obj!=null)
{
NHibernate.NHibernateUtil.String.NullSafeSet(cmd,obj.FirstName,index);
NHibernate.NHibernateUtil.String.NullSafeSet(cmd,obj.LastName,index+1);
}
else
{
NHibernate.NHibernateUtil.String.NullSafeSet(cmd,DBNull.Value,index);
NHibernate.NHibernateUtil.String.NullSafeSet(cmd,DBNull.Value,index+1);
}
}
} 配置示例:
<propertyname="ID"type="NH12.MyExample.Domain.UserNameType,Domain">
<columnname="FIRST_NAME"/>
<columnname="LAST_NAME"/>
</property> 映射细节已经在UserNameType中实现了,因此配置中只需要指定属性名字和对应的自定义组件映射类型,并告诉NHibernate框架各个字段名称就OK,其他映射细节的实现已经不包含在映射配置文件中。

