在某一天忽然觉得用 NHibernate 来实现无限级将会是多么简单,简单到你做梦都无法想到,似乎它天生就具备了处理这种情况的超能力。就连数据表的设计也被简化到了极致。
下面我会简单说明一下实现的步骤并给出源码下载,同时它也是 ASP.NET 2.0 + Spring.Net + Nhibernate + MYSQL 的一个实例,之所以使用了几个框架组合和 MYSQL 做为数据库,完全是出于自娱自乐,但我还是更希望大家把重点放在 NHibernate 实现无限级分类上。
第一步:创建数据库
-- 创建数据库
CREATE DATABASE hibernatedemo;
-- 添加表 tb_Classes
DROP TABLE IF EXISTS `hibernatedemo`.`tb_classes`;
CREATE TABLE `hibernatedemo`.`tb_classes` (
`Id` int(10) unsigned NOT NULL DEFAULT '1',
`Name` varchar(45) NOT NULL,
`ParentId` int(10) unsigned DEFAULT NULL,
`SortOrder` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`Id`),
KEY `tb_classes_ibfk_1` (`ParentId`),
CONSTRAINT `tb_classes_ibfk_1` FOREIGN KEY (`ParentId`) REFERENCES `tb_classes` (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
第二步: 创建实体关系映射(实体层)
新建解决方案 NhibernateDemo,添加 ModelObject 项目用于存放实体类和映射文件。
创建 ClassesInfo.hbm.xml 文件 (CodeSmith 生成,需做修改)
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.0">
<class name="Yyw.ModelObject.ClassesInfo, Yyw.ModelObject" table="tb_classes">
<cache usage="read-write"/>
<id name="Id" type="Int32" unsaved-value="null">
<column name="Id" length="4" sql-type="int" not-null="true" unique="true" index="PK_tb_classes"/>
<generator class="Yyw.ModelObject.ClassesIdGenerator, Yyw.ModelObject" />
</id>
<property name="Name" type="String">
<column name="Name" length="45" sql-type="varchar" not-null="false"/>
</property>
<property name="SortOrder" type="Int32">
<column name="`SortOrder`" length="4" sql-type="int" not-null="true"/>
</property>
<many-to-one name="Parent" class="Yyw.ModelObject.ClassesInfo, Yyw.ModelObject" update="true" insert="true" cascade="delete-orphan">
<column name="ParentId" length="4" sql-type="int" not-null="false"/>
</many-to-one>
<bag name="Parenttb_classes" inverse="true" lazy="true" cascade="all-delete-orphan">
<key column="ParentId"/>
<one-to-many class="Yyw.ModelObject.ClassesInfo, Yyw.ModelObject"/>
</bag>
</class>
<query name="Yyw.ModelObject.ClassesInfo.Select.By.ParentId.Is.Null">
<![CDATA[
from ClassesInfo classesInfo where classesInfo.Parent.Id is null
]]>
</query>
</hibernate-mapping>
创建实体类 ClassesInfo.cs (CodeSmith 生成,需做修改)
using System;
using System.Collections;
using System.Web.UI.WebControls;
using System.Collections.Generic;
namespace Yyw.ModelObject
{
ClassesInfo#region ClassesInfo
/**//// <summary>
/// ClassesInfo object for NHibernate mapped table 'tb_classes'.
/// </summary>
public class ClassesInfo : System.IComparable
{
Member Variables#region Member Variables
protected int _id;
protected string _name;
protected int _sortOrder;
protected ClassesInfo _parent;
protected IList<ClassesInfo> _parenttbclasses;
protected static String _sortExpression = "Id";
protected static SortDirection _sortDirection = SortDirection.Ascending;
#endregion
Constructors#region Constructors
public ClassesInfo() { }
public ClassesInfo(string name, int sortOrder, ClassesInfo parent)
{
this._name = name;
this._sortOrder = sortOrder;
this._parent = parent;
}
#endregion
Public Properties#region Public Properties
public virtual int Id
{
get { return _id; }
set { _id = value; }
}
public virtual string Name
{
get { return _name; }
set
{
if ( value != null && value.Length > 45)
throw new ArgumentOutOfRangeException("Invalid value for Name", value, value.ToString());
_name = value;
}
}
public virtual int SortOrder
{
get { return _sortOrder; }
set { _sortOrder = value; }
}
public virtual ClassesInfo Parent
{
get { return _parent; }
set { _parent = value; }
}
public virtual IList<ClassesInfo> Parenttb_classes
{
get { return _parenttbclasses; }
set { _parenttbclasses = value; }
}
public static String SortExpression
{
get { return _sortExpression; }
set { _sortExpression = value; }
}
public static SortDirection SortDirection
{
get { return _sortDirection; }
set { _sortDirection = value; }
}
#endregion
IComparable Methods#region IComparable Methods
public int CompareTo(object obj)
{
if (!(obj is ClassesInfo))
throw new InvalidCastException("This object is not of type ClassesInfo");
int relativeValue;
switch (SortExpression)
{
case "Id":
relativeValue = this.Id.CompareTo(((ClassesInfo)obj).Id);
break;
case "Name":
relativeValue = (this.Name != null) ? this.Name.CompareTo(((ClassesInfo)obj).Name) : -1;
break;
case "SortOrder":
relativeValue = this.SortOrder.CompareTo(((ClassesInfo)obj).SortOrder);
break;
default:
goto case "Id";
}
if (ClassesInfo.SortDirection == SortDirection.Ascending)
relativeValue *= -1;
return relativeValue;
}
#endregion
Equals and GetHashCode#region Equals and GetHashCode
/**//// <summary>
/// Tests whether this and another object are equal in a way that
/// will still pass when proxy objects are being used.
/// </summary>
public override bool Equals(object obj)
{
ClassesInfo other = obj as ClassesInfo;
if (other == null)
return false;
if (Id == default(int) && other.Id == default(int))
return (object)this == other;
else
return Id == other.Id;
}
public override int GetHashCode()
{
if (Id == default(int))
return base.GetHashCode();
string stringRepresentation = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "#" + Id.ToString();
return stringRepresentation.GetHashCode();
}
#endregion
}
#endregion
}
为自定义主键添加生成器 ClassesIdGenerator.cs
using System;
using NHibernate;
using Yyw.DBUtility.NH;
namespace Yyw.ModelObject
{
internal class ClassesIdGenerator : NHibernate.Id.IIdentifierGenerator
{
public object Generate(NHibernate.Engine.ISessionImplementor session, object obj)
{
return IdentifierGeneratorHelper.GetNextNumber<ClassesInfo>(session, "Id");
}
}
}
第二步: 新建 IDAL 、NHibernateDAL、DBUtilety、ClassFactory、IBLL、BLL
项目 IDAL 、NHibernateDAL、DBUtilety 项目的内容可参考 移植 Castle 的 ActiveRecord 直接为 NHibernate 所用,
ClassFactory 项目对 Spring.Net 进行封装,主要负责对象创建工作,至于 Spring.Net IoC 的使用不是本文的重点(而且本文也将略去部分项目的讲解),如果您有兴趣的话可以参考 Spring.Net AOP 学习之旅: 使用 Throws Advice 处理异常,
本文例子中也会采用此方式来记录异常。
接下来我们在 IDL 中创建一个基接口 IDalBase
using System;
using System.Collections.Generic;
/**//// <summary>
/// IBizObjectBase
/// </summary>
namespace Yyw.IDAL
{
/**//// <summary>
/// 单个实体类增删改查功能(CRUD)
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IDalBase<T> where T : class
{
T FindByPrimaryKey(object id);
IList<T> FindAll();
/**//// <summary>
/// Creates (Saves) a new instance to the database.
/// </summary>
/// <param name="instance"></param>
void Create(object instance);
/**//// <summary>
/// Saves the instance to the database
/// </summary>
/// <param name="instance"></param>
void SaveOrUpdate(object instance);
/**//// <summary>
/// Persists the modification on the instance
/// state to the database.
/// </summary>
/// <param name="instance"></param>
void Update(object instance);
/**//// <summary>
/// Deletes the instance from the database.
/// </summary>
/// <param name="instance"></param>
void Delete(object instance);
void DeleteAll();
}
}
然后添加继承自它的接口 IClasses
using System;
using System.Collections.Generic;
using Yyw.ModelObject;
namespace Yyw.IDAL
{
public interface IClasses : IDalBase<ClassesInfo>
{
/**//// <summary>
/// 根据父级查找子级,当父级为 NULL 时查出最顶级分类
/// </summary>
/// <param name="parent">父级分类</param>
/// <returns></returns>
IList<ClassesInfo> FindByParent(ClassesInfo parent);
}
}
同样我们也在 IBLL 中创建接口 IClasses ,当然 namespace 为 Yyw.IBLL
using System;
using System.Collections.Generic;
using Yyw.ModelObject;
namespace Yyw.IBLL
{
public interface IClasses
{
/**//// <summary>
/// 获取最上层的分类
/// </summary>
/// <returns></returns>
IList<ClassesInfo> GetRoots();
/**//// <summary>
/// 获取子节点
/// </summary>
/// <param name="parent"></param>
/// <returns></returns>
IList<ClassesInfo> GetChildren(ClassesInfo parent);
ClassesInfo GetData(int id);
bool Add(ClassesInfo ClassesInfo);
bool Update(ClassesInfo ClassesInfo);
bool Delete(ClassesInfo ClassesInfo);
}
}
接下来要做的就是对接口的实现工作了,此部分看代码自然就能明白。至此我们所有底层的工作都已经完成,剩下要做的就是表示层上的处理了,通过 NHibernate 的 many-to-one 级联操作我们可以不需要书写任何代码即可得到结点的父结点和子结点集合,表示层仅需要几个递归方法便可实现我们的无限级分类。
演示地址:http://www.openopen.cn/nhibernatedemo/Default.aspx
需要注意:
1、需要将 MySql.Data.dll 文件拷贝到 /Web/bin 目录下
2、通过父结点查询子结点时不能使用 ICriterion 查询,
因为 ICriterion criterion = Expression.Eq("Parent", null); 生成的 SQL 条件语句为 parent.Id = '' 而并非 parent.Id is null,
所以我们需要通过 HQL 语句来完成。
完整程序下载
虽然通过 NH 实现无限级分类从性能上并非最好的选择,但它毕竟是简单的,而且数据表中也没更多的数据冗余。为了撰写这个例子花费了我一个礼拜天的休息时间(腹搞都打了好几天),没有功劳也有苦劳,大家多多支持哈 :)