Richie

Sometimes at night when I look up at the stars, and see the whole sky just laid out there, don't you think I ain't remembering it all. I still got dreams like anybody else, and ever so often, I am thinking about how things might of been. And then, all of a sudden, I'm forty, fifty, sixty years old, you know?

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(8NULL,
    CREATE_TIME 
nvarchar(6NULL
    下面是自定义日期映射类型的实现:
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(04+ "-" + val1.ToString().Substring(42+ "-" + val1.ToString().Substring(62);
        
if (val2 != null && val2.ToString().Length == 6)
            time 
= val2.ToString().Substring(02+ ":" + val2.ToString().Substring(22+ ":" + val2.ToString().Substring(42);
        
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 == nullreturn 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,其他映射细节的实现已经不包含在映射配置文件中。

posted on 2007-04-12 16:42  riccc  阅读(7770)  评论(11编辑  收藏

导航