NHibernate考察系列 04 枚举 自定义类型 组件类型

    1. one-to-many, many-to-one
    知道了many-to-many用法之后,many-to-one、one-to-many就很简单。因此参考这个系列前面几篇,可以很轻松的实现Vendor、Company、Plant、Item这几个对象。
    Company与Vendor之间建立了一个一对多的关系,下面是Company类中Plants集合属性的定义
public   virtual  ISet < Plant >  Plants
{
    
get  {  return  _plant; }
    
set  { _plant  =  value; }
}
private  ISet < Plant >  _plant;
    下面是这个属性的映射配置
< set  name ="Plants"  table ="TBLPLANT"  lazy ="true" >
  
< key  column ="COMPANY_ID" />
  
< one-to-many  class ="Plant"  not-found ="ignore"   />
</ set >
    下面是Plant类中Company属性的定义与配置
public   virtual  Company Company
{
    
get  {  return  _company; }
    
set  { _company  =  value; }
}
private  Company _company;

< many-to-one  name ="Company"  class ="Company"  not-found ="exception"  lazy ="proxy"  column ="COMPANY_ID"   />

    2. 枚举类型
    NHibernate直接支持枚举类型的映射,这种支持方式,在数据库中保存的是枚举的整数值。在TBLPLANTITEM中有三个字段对应的对象属性使用枚举类型:ITEM_CATEGORY、PURCHASE_CATEGORY、STOCK_OPTION,我把STOCK_OPTION字段设置成整数类型,用来测试NHB对枚举映射的直接支持方式,而其它两个设成了字符串类型,用于保存枚举的字符串描述。
    在数据库保存枚举的字符串描述需要使用自定义映射类型,将在下面一节中讲述,本节看一下直接对枚举类型进行映射。
    StockOptionEnum枚举的定义:
public   enum  StockOptionEnum
{
    None
= 0 ,
    ERP
= 1 ,
    Hub
= 2
}
    PlantItem类中StockOption属性的定义:
public   virtual  StockOptionEnum StockOption
{
    
get  {  return  _stockOption; }
    
set  { _stockOption  =  value; }
}
private  StockOptionEnum _stockOption;
    属性的配置节点:
< property  name ="StockOption" >
  
< column  name ="STOCK_OPTION"  sql-type ="int"  not-null ="false"   />
</ property >
     NHibernate默认支持的枚举映射用起来很简单,这可能是能够将枚举值强制转化成整数这样一个值类型的原因。这种方式,枚举属性保存在数据库中的是整数值。

    3. 自定义类型、自定义映射类型IUserType
    首先在概念方面看一下。当你觉得某个属性需要满足的业务逻辑比较复杂,用.Net标准的数据类型无法满足你的需求时,你会选择为这个属性单独定义一个类,作为这个属性的类型,可以称这个为自定义类型/细粒度对象。某些情况下,将属性进行持久化映射时,并不是简单、直接的进行存取,也就是说你可能无法通过NHibernate提供的标准映射方法在属性和持久化媒介之间进行映射。这种情况下NHibernate提供一个机制,让你自己可以完全的控制映射行为,这就是为你的属性实现一个NHibernate.UserTypes.IUserType类,我称这个为自定义映射类型。自定义映射类型这个名称比较合适,因为从名称你就可以猜测到它的主要职责/作用就是完成属性与持久化媒介之间的映射。
    我们先看一下怎样通过自定义映射类型保存枚举的字符串描述值。下面是ItemCategoryEnum、PurchaseCategoryEnum两个枚举的定义:
public   enum  ItemCategoryEnum
{
    P
= 1 ,   // Product
    M = 2 // material
}

public   enum  PurchaseCategoryEnum
{
    PO
= 1 ,
    JIT
= 2
}
    下面是自定义映射类型实现保存枚举的字符串描述。为了简化,我定义了一个抽象类实现NHibernate.UserTypes.IUserType,然后各个枚举的自定义映射类型就很容易实现了:
#region  EnumType
///   <summary>
///  自定义枚举映射类型
///   </summary>
public   abstract   class  MyEnumType : IUserType
{
    
private  Type _type;
    
private   int  _length;

    
private  MyEnumType()
    {
    }

    
public  MyEnumType(Type type,  int  length)
    {
        
this ._type  =  type;
        
this ._length  =  length;
    }

    
///   <summary>
    
///  自定义类型的对象实例是否会发生改变
    
///   </summary>
     public   bool  IsMutable
    {
        
get  {  return   false ; }
    }

    
public  Type ReturnedType
    {
        
get  {  return  _type; }
    }

    
///   <summary>
    
/// 属性对应的数据库字段类型
    
///   </summary>
     public  SqlType[] SqlTypes
    {
        
get  {  return   new  SqlType[] {  new  SqlType(DbType.String,  this ._length) }; }
    }

    
///   <summary>
    
///  如果IsMutable为true,此处应当实现对象实例的DeepCopy
    
///   </summary>
     public   object  DeepCopy( object  value)
    {
        
return  value;
    }

    
public   new   bool  Equals( object  x,  object  y)
    {
        
return  x.ToString().Trim()  ==  y.ToString().Trim();
    }

    
public   int  GetHashCode( object  x)
    {
        
return  x.ToString().GetHashCode();
    }

    
///   <summary>
    
///  从缓存中取对象
    
///   </summary>
     public   object  Assemble( object  cached,  object  owner)
    {
        
return  DeepCopy(cached);
    }

    
///   <summary>
    
///  将对象放入缓存前的处理
    
///   </summary>
     public   object  Disassemble( object  value)
    {
        
return  DeepCopy(value);
    }

    
///   <summary>
    
///  读取数据库字段值(DataReader),转换成实体属性
    
///   </summary>
     public   object  NullSafeGet(IDataReader rs,  string [] names,  object  owner)
    {
        
object  name  =  NHibernate.NHibernateUtil.String.NullSafeGet(rs, names[ 0 ]);
        
if  (name  ==   null )
            
throw   new  Exception(_type.Name  +   "  can not be null " );
        
return  Enum.Parse(_type, name.ToString(),  true );
    }

    
///   <summary>
    
///  为属性的存取设置DbCommand参数
    
///  这里我们要保存枚举的字符串描述,因此将value直接转换成字符串
    
///   </summary>
     public   void  NullSafeSet(IDbCommand cmd,  object  value,  int  index)
    {
        
if (value == null )
            
throw   new  Exception(_type.Name  +   "  can not be null " );
        NHibernate.NHibernateUtil.String.NullSafeSet(cmd, value.ToString(), index);
    }

    
public   object  Replace( object  original,  object  target,  object  owner)
    {
        
return  original;
    }
}

public   class  PurchaseCategoryType : MyEnumType
{
    
public  PurchaseCategoryType()
        : 
base ( typeof (PurchaseCategoryEnum),  5 )
    {
    }
}

public   class  ItemCategoryType : MyEnumType
{
    
public  ItemCategoryType()
        : 
base ( typeof (ItemCategoryEnum),  3 )
    {
    }
}
#endregion
    下面是PlantItem类中PurchaseCategoryType和ItemCategoryType属性的定义:
public   virtual  ItemCategoryEnum ItemCategory
{
    
get  {  return  _itemCategory; }
    
set  { _itemCategory  =  value; }
}
private  ItemCategoryEnum _itemCategory;

public   virtual  PurchaseCategoryEnum PurchaseCategory
{
    
get  {  return  _purchaseCategory; }
    
set  { _purchaseCategory  =  value; }
}
private  PurchaseCategoryEnum _purchaseCategory;
    这两个属性的映射配置:
< property  name ="ItemCategory"  type ="NH12.MyExample.Domain.ItemCategoryType, Domain" >
  
< column  name ="ITEM_CATEGORY"  sql-type ="nvarchar"  length ="3"  not-null ="false"   />
</ property >

< property  name ="PurchaseCategory"  type ="NH12.MyExample.Domain.PurchaseCategoryType, Domain" >
  
< column  name ="PURCHASE_CATEGORY"  sql-type ="nvarchar"  length ="5"  not-null ="false"   />
</ property >
    从上面可以看出,实体的属性使用枚举类型(或者其它的自定义类型),然后为枚举类型定义一个映射类型(上面的 PurchaseCategoryType和 ItemCategoryType),IUserType接口定义的主要职责,是如何将数据库取出来的值转换成枚举类型(或者自定义类型)( NullSafeGet方法)、如何将枚举类型(或者自定义类型)转换成数据库操作的DbParameter值( NullSafeSet方法),其它一些是支持NHibernate的OR机制必须的功能,例如了解属性类型( Type ReturnedType)、数据库字段的类型( SqlType[] SqlTypes)、对缓存机制的支持( Assemble、 Disassemble方法)等等。

    上面的示例演示如何通过自定义映射类型按照你的需要对属性进行持久化存取,实现NHibernate.UserTypes.IUserType也可以将一个属性保存到数据库的多个字段。下面演示将一个DateTime的属性按照yyyyMMdd、HHmmss这样的格式,以字符串的方式保存到数据库的两个字段中。
    先用下面的语句为TBLPLANTITEM表添加两个字段:
ALTER   TABLE  dbo.TBLPLANTITEM  ADD
    CREATE_DATE 
nvarchar ( 8 NULL ,
    CREATE_TIME 
nvarchar ( 6 NULL
    下面是自定义日期映射类型的实现:
public   class  MyDateTimeType : IUserType
{
    
public  MyDateTimeType()
    {
    }

    
public   bool  IsMutable
    {
        
get  {  return   false ; }
    }

    
public  Type ReturnedType
    {
        
get  {  return   typeof (DateTime); }
    }

    
public  SqlType[] SqlTypes
    {
        
get
        {
            
return   new  SqlType[] {  new  SqlType(DbType.String, 8),  new  SqlType(DbType.String, 6) };
        }
    }

    
public   object  DeepCopy( object  value)
    {
        
return  value;
    }

    
public   new   bool  Equals( object  x,  object  y)
    {
        
return  x ==  y;
    }

    
public   int  GetHashCode( object  x)
    {
        
return  x.GetHashCode();
    }

    
public   object  Assemble( object  cached,  object  owner)
    {
        
return  DeepCopy(cached);
    }

    
public   object  Disassemble( object  value)
    {
        
return  DeepCopy(value);
    }

    
public   object  NullSafeGet(IDataReader rs,  string [] names,  object  owner)
    {
        
object  val1  =  NHibernate.NHibernateUtil.String.NullSafeGet(rs, names[ 0 ]);
        
object  val2  =  NHibernate.NHibernateUtil.String.NullSafeGet(rs, names[ 1 ]);
        
string  date  =   " 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 );
        
return  DateTime.Parse(date  +   "   "   +  time);
    }

    
public   void  NullSafeSet(IDbCommand cmd,  object  value,  int  index)
    {
        
string  date  =   " 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 );
    }

    
public   object  Replace( object  original,  object  target,  object  owner)
    {
        
return  original;
    }
}
    下面是属性的定义:
public  DateTime CreateTime
{
    
get  {  return  _createTime; }
    
set  { _createTime  =  value; }
}
private  DateTime _createTime;
    下面是映射配置:
< property  name ="CreateTime"  type ="NH12.MyExample.Domain.MyDateTimeType, Domain" >
  
< column  name ="CREATE_DATE"  sql-type ="nvarchar(6)" />
  
< column  name ="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属性:
public   class  PlantItemID
{
    
private   string  _plantID;
    
private   string  _itemID;

    
public  PlantItemID()
    {
    }

    
public  PlantItemID( string  plantID,  string  itemID)
    {
        _plantID 
=  plantID;
        _itemID 
=  itemID;
    }

    
public   virtual   string  PlantID
    {
        
get  {  return  _plantID; }
        
set  { _plantID  =  value; }
    }

    
public   virtual   string  ItemID
    {
        
get  {  return  _itemID; }
        
set  { _itemID  =  value; }
    }

    
#region  override
    
public   override   bool  Equals( object  obj)
    {
        PlantItemID o 
=  obj  as  PlantItemID;
        
if  (o  ==   null )
            
return   false ;
        
return   this .PlantID  ==  o.PlantID  &&   this .ItemID  ==  o.ItemID;
    }

    
public   override   int  GetHashCode()
    {
        
return   this .PlantID.GetHashCode()  +   this .ItemID.GetHashCode();
    }

    
public   override   string  ToString()
    {
        
return   this .PlantID  +   "   "   +   this .ItemID;
    }
    
#endregion
}
    属性和映射配置文件如下:   
public   virtual  PlantItemID ID
{
    
get  {  return  _id; }
    
set  { _id  =  value; }
}
private  PlantItemID _id;

< composite-id  class ="NH12.MyExample.Domain.PlantItemID, Domain"  name ="ID" >
  
< key-property  column ="PLANT_ID"  name ="PlantID"  type ="String"  length ="5"   />
  
< key-property  column ="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这几个类和相关的映射配置文件了,下面看一下如何使用:
ISessionFactory sessionFactory  =   new  Configuration().Configure().BuildSessionFactory();
ISession session 
=   null ;
ITransaction tran 
=   null ;
try
{
    session 
=  sessionFactory.OpenSession();
    tran 
=  session.BeginTransaction();

    Company company 
=   new  Company( " 1000 " " test company 1 " "" new  HashedSet < Plant > ());
    session.Save(company);
    Plant plant1 
=   new  Plant( " 1101 " " test plant 1 " , company);
    session.Save(plant1);
    Plant plant2 
=   new  Plant( " 1102 " " test plant 2 " , company);
    session.Save(plant2);
    Item item1 
=   new  Item( " FK1.1023.78AF " " 2.5# LCD " " PCS " new   decimal ( 85.7 ));
    session.Save(item1);
    
// 创建PlantItem对象
    PlantItem plantitem1  =   new  PlantItem( new  PlantItemID( " 1101 " " FK1.1023.78AF " ),
            " PCS1 " , ItemCategoryEnum.P, PurchaseCategoryEnum.JIT, StockOptionEnum.ERP);
    session.Save(plantitem1);
    PlantItem plantitem2 
=   new  PlantItem( new  PlantItemID( " 1102 " " FK1.1023.78AF " ),
            "
PCS2 " , ItemCategoryEnum.M, PurchaseCategoryEnum.PO, StockOptionEnum.Hub);
    session.Save(plantitem2);

    
// 获取PlantItem对象
    
// PlantItem pi = session.Get<PlantItem>(new PlantItemID("1101", "FK1.1023.78AF"));

    tran.Commit();
}
catch
{
    tran.Rollback();
}
finally
{
    session.Close();
}
sessionFactory.Close();
    与数据库交互的SQL语句没有什么特别的地方,例如添加和获取PlantItem对象的SQL如下:
exec  sp_executesql N '
    INSERT INTO TBLPLANTITEM 
           (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
' @p0 nvarchar(4),@p1 nvarchar(1),@p2 nvarchar(3),@p3 int,@p4 nvarchar(8),
        @p5 nvarchar(6),@p6 nvarchar(4),@p7 nvarchar(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 '
exec  sp_executesql N '
    SELECT plantitem0_.PLANT_ID as PLANT1_2_0_, plantitem0_.ITEM_ID as ITEM2_2_0_, plantitem0_.UNIT as UNIT2_0_, 
        plantitem0_.ITEM_CATEGORY as ITEM4_2_0_, plantitem0_.PURCHASE_CATEGORY as PURCHASE5_2_0_, 
        plantitem0_.STOCK_OPTION as STOCK6_2_0_ FROM TBLPLANTITEM plantitem0_ 
    WHERE plantitem0_.PLANT_ID=@p0 and plantitem0_.ITEM_ID=@p1
'
    N
' @p0 nvarchar(4),@p1 nvarchar(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类定义:
public   class  PlantItem
{
    
private  Plant _plant;
    
private  Item _item;
    
private   string  _unit;

    
public  PlantItem(Plant plant, Item item,
               string
 unit, ItemCategoryEnum itemCategory, PurchaseCategoryEnum purchaseCategory, StockOptionEnum stockOption)
    {
        _plant 
=  plant;
        _item 
=  item;
        _unit 
=  unit;
        _itemCategory 
=  itemCategory;
        _purchaseCategory 
=  purchaseCategory;
        _stockOption 
=  stockOption;
    }

    
public  PlantItem()
    {
    }

    
public   virtual  Plant Plant
    {
        
get  {  return  _plant; }
        
set  { _plant  =  value; }
    }

    
public   virtual  Item Item
    {
        
get  {  return  _item; }
        
set  { _item  =  value; }
    }

    
public   override   string  Unit
    {
        
get  {  return  _unit; }
        
set  { _unit  =  value; }
    }

    
public   virtual  ItemCategoryEnum ItemCategory
    {
        
get  {  return  _itemCategory; }
        
set  { _itemCategory  =  value; }
    }
    
private  ItemCategoryEnum _itemCategory;

    
public   virtual  PurchaseCategoryEnum PurchaseCategory
    {
        
get  {  return  _purchaseCategory; }
        
set  { _purchaseCategory  =  value; }
    }
    
private  PurchaseCategoryEnum _purchaseCategory;

    
public   virtual  StockOptionEnum StockOption
    {
        
get  {  return  _stockOption; }
        
set  { _stockOption  =  value; }
    }
    
private  StockOptionEnum _stockOption;

    
#region  override
    
public   override   bool  Equals( object  obj)
    {
        
if  ( this   ==  obj)  return   true ;
        
if  (obj  ==   null   ||  obj.GetType()  !=   this .GetType())
            
return   false ;
        PlantItem plantItem 
=  obj  as  PlantItem;
        
return  plantItem  !=   null   &&  plantItem.Plant  ==  _plant  &&  plantItem.Item  ==  _item;
    }

    
public   override   int  GetHashCode()
    {
        
return  _plant.GetHashCode()  +  _item.GetHashCode();
    }

    
public   override   string  ToString()
    {
        
return  _plant.ToString()  +   "   "   +  _item.ToString();
    }
    
#endregion
}
    下面是映射配置文件:
< hibernate-mapping  xmlns ="urn:nhibernate-mapping-2.2"  namespace ="NH12.MyExample.Domain"  assembly ="Domain" >
  
< class  name ="PlantItem"  table ="TBLPLANTITEM" >
    
< composite-id >
      
< key-many-to-one  name ="Plant"  class ="Plant"  column ="PLANT_ID"  lazy ="proxy" />
      
< key-many-to-one  name ="Item"  class ="Item"  column ="ITEM_ID"  lazy ="proxy" />
    
</ composite-id >

    
< property  name ="Unit" >
      
< column  name ="UNIT"  length ="10"  sql-type ="nvarchar"  not-null ="false"   />
    
</ property >

    
< property  name ="ItemCategory"  type ="NH12.MyExample.Domain.ItemCategoryType, Domain" >
      
< column  name ="ITEM_CATEGORY"  sql-type ="nvarchar"  length ="3"  not-null ="false"   />
    
</ property >

    
< property  name ="PurchaseCategory"  type ="NH12.MyExample.Domain.PurchaseCategoryType, Domain" >
      
< column  name ="PURCHASE_CATEGORY"  sql-type ="nvarchar"  length ="5"  not-null ="false"   />
    
</ property >

    
< property  name ="StockOption" >
      
< column  name ="STOCK_OPTION"  sql-type ="int"  not-null ="false"   />
    
</ property >
  
</ class >
</ hibernate-mapping >
    实体类的实现和配置都比较简单,下面看一下怎样使用。
ISessionFactory sessionFactory  =   new  Configuration().Configure().BuildSessionFactory();
ISession session 
=   null ;
ITransaction tran 
=   null ;
try
{
    session 
=  sessionFactory.OpenSession();
    tran 
=  session.BeginTransaction();

    Company company 
=   new  Company( " 1000 " " test company 1 " "" new  HashedSet < Plant > ());
    session.Save(company);
    Plant plant1 
=   new  Plant( " 1101 " " test plant 1 " , company);
    session.Save(plant1);
    Plant plant2 
=   new  Plant( " 1102 " " test plant 2 " , company);
    session.Save(plant2);
    Item item1 
=   new  Item( " FK1.1023.78AF " " 2.5# LCD " " PCS " new   decimal ( 85.7 ));
    session.Save(item1);
    
// 创建PlantItem对象
    PlantItem plantitem1  =
         new
 PlantItem(plant1, item1,  " PCS1 " , ItemCategoryEnum.P, PurchaseCategoryEnum.JIT, StockOptionEnum.ERP);
    session.Save(plantitem1);
    PlantItem plantitem2 
=
         new
 PlantItem(plant2, item1,  " PCS2 " , ItemCategoryEnum.M, PurchaseCategoryEnum.PO, StockOptionEnum.Hub);
    session.Save(plantitem2);

    tran.Commit();
}
catch
{
    tran.Rollback();
}
finally
{
    session.Close();
}
sessionFactory.Close();
    下面是获取PlantItem对象的代码:
Plant plant  =  session.Get < Plant > ( " 1101 " );
Item item 
=  session.Get < Item > ( " FK1.1023.78AF " );
PlantItem pi 
=   new  PlantItem();
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
    上面举的组件类型非常简单,当组件类型变得复杂,映射关系比较多,或者是组件类型中有属性需要使用自定义映射来完成时,可以使用一个自定义组合映射类型进行封装。下面是一个示例:
public   class UserNameType : ICompositeUserType
{
    
public  PlantItemIDType()
    {
    }

    
public   bool  IsMutable
    {
        
get  {  return   true ; }
    }

    
///   <summary>
    
///  对应的组件类型有哪些属性
    
///   </summary>
     public   string [] PropertyNames
    {
        
get  {  return   new   string [] {  " FirstName " " LastName "  }; }
    }

    
///   <summary>
    
///  对应的组件类型各属性的NHibernate.Type.IType
    
///   </summary>
     public  IType[] PropertyTypes
    {
        
get  {  return   new  IType[] { NHibernate.NHibernateUtil.String, NHibernate.NHibernateUtil.String }; }
    }

    
///   <summary>
    
///  对应的组件类型Class Type
    
///   </summary>
     public  Type ReturnedClass
    {
        
get  {  return   typeof (UserName); }
    }

    
public   object  DeepCopy( object  value)
    {
        UserName obj 
=  value  as UserName;
        
if  (obj  ==   null )
            
return   null ;
        
return   new UserName(obj.FirstName, obj.LastName);
    }

    
public   object  Assemble( object  cached, ISessionImplementor session,  object  owner)
    {
        
return   this .DeepCopy(cached);
    }

    
public   object  Disassemble( object  value, ISessionImplementor session)
    {
        
return   this .DeepCopy(value);
    }

    
public   new   bool  Equals( object  x,  object  y)
    {
        UserName objx 
=  x  as UserName;
        UserName objy 
=  y  as UserName;
        
if  (objx  ==   null   &&  objy  ==   null )
            
return   true ;
        
if  (objx  ==   null   ||  objy  ==   null )
            
return   false ;
        
return  objx.Equals(objy);
    }

    
public   int  GetHashCode( object  x)
    {
        UserName obj 
=  x  as UserName;
        
if  (obj  ==   null )
            
return   0 ;
        
return  obj.GetHashCode();
    }

    
public   object  Replace( object  original,  object  target, ISessionImplementor session,  object  owner)
    {
        
return   this .DeepCopy(original);
    }

    
///   <summary>
    
///  从组件类型的实例中读取属性值
    
///   </summary>
     public   object  GetPropertyValue( object  component,  int  property)
    {
        UserName obj 
=  component  as UserName;
        
if  (obj  ==   null return   null ;
        
if  (property  ==   0 )
            
return  obj.FirstName;
        
if  (property  ==   1 )
            
return  obj.LastName;
        
return   null ;
    }

    
///   <summary>
    
///  为组件类型的实例设置值
    
///   </summary>
     public   void  SetPropertyValue( object  component,  int  property,  object  value)
    {
        UserName obj 
=  component  as UserName;
        
if  (obj  ==   null )
            
return ;
        
if  (property  ==   0 )
            obj.FirstName
=  value  as   string ;
        
else   if  (property  ==   1 )
            obj.LastName
=  value  as   string ;
    }

    
///   <summary>
    
///  从DataReader读取组件类型需要的字段值,生成组件类型
    
///   </summary>
     public   object  NullSafeGet(IDataReader dr,  string [] names, ISessionImplementor session,  object  owner)
    {
        
string  val1  =  NHibernate.NHibernateUtil.String.NullSafeGet(dr, names[ 0 ])  as   string ;
        
string  val2  =  NHibernate.NHibernateUtil.String.NullSafeGet(dr, names[ 1 ])  as   string ;
        
return   new UserName(val1, val2);
    }

    
///   <summary>
    
///  为组件类型生成DbParameter参数值
    
///   </summary>
     public   void  NullSafeSet(IDbCommand cmd,  object  value,  int  index, ISessionImplementor session)
    {
        UserName obj 
=  value  as 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 );
        }
    }
}
    配置示例:
< property  name ="ID"  type ="NH12.MyExample.Domain.UserNameType, Domain" >
  
< column  name ="FIRST_NAME" />
  
< column  name ="LAST_NAME" />
</ property >
    映射细节已经在UserNameType中实现了,因此配置中只需要指定属性名字和对应的自定义组件映射类型,并告诉NHibernate框架各个字段名称就OK,其他映射细节的实现已经不包含在映射配置文件中。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值