Posted on 2006-11-05 22:01
阿武 阅读(3718)
评论(30) 编辑 收藏 网摘 所属分类:
C#ASP.NETO/R Mapping
在某一天忽然觉得用 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 实现无限级分类从性能上并非最好的选择,但它毕竟是简单的,而且数据表中也没更多的数据冗余。为了撰写这个例子花费了我一个礼拜天的休息时间(腹搞都打了好几天),没有功劳也有苦劳,大家多多支持哈 :)
Feedback
支持一下!!
我也想过这个问题,只是没有去实践。。
呵呵,毕竟这是NHIBERNATE必须要解决的。因为无限级分类在企业应用太普遍了。
我先看看
@Zhongkeruanjian
@拉拉
谢谢支持!
@Tseng
按照 NH 的缓存机制它应该会对第一次查询出来的数据进行缓存,但我从日志文件中却每次请求都可以查看到许多的 SQL 语句,如果能把这部分处理好,我相信性能上也会有很大的提升
子引用本来就是O/R M具备的功能。
如果采用延时加载,那每次加载子时才会操作数据库。
执行许多SQL语句是必然,因为本身就是一个归递操作。
@henry
是的,就是因为这个特性所以说它天生就具有了处理这样问题的能力。
至于延时加载,虽然在这个例子里面如果设置为 flase 效果会更好,但在大多数应用里延迟加载还是必须的,刚才也测试了将 IQuery.SetCacheable(true);
但效果并不理想
Spring.Net的延迟加载可以用吗?除非你将方法加上事务,但是连接又不释放,真愁人。
@西门子乌
上面提到的延迟加载指的是 NHibernate 的,我想你指的延迟加载在 Spring.Net 中应该是延迟初始化,两者是不同的概念。在 NHibernate 使用延迟加载就不应该在第一次查询后关闭 Session ,这会导致下次异常的产生,你可以看到我在代码中是使用了 IHttpModule 来存储 Session, 详细信息可以参考园子里 DD 的 NHibernate的Session管理(http://www.cnblogs.com/renrenqq/archive/2006/08/04/467688.html">http://www.cnblogs.com/renrenqq/archive/2006/08/04/467688.html)
你的DEMO,我说一下:
1 最好用Sql Server做数据库,不然还要装MYSQL,毕竟一般人电脑上没有这东西。
2 工程项目中的DLL引用,你最好做一个DLLS文件夹,把所有涉及到的外部的DLL放进去,这样不会出现引用丢失了,不然你的DEMO下载编译,到处都是引用丢失的错。
呵,还没看你的源代码,把你的DEMO运行起来(换SQL SERVER,重新加DLL引用)就花了我个把小时,不值得啊。
以后多多探讨NHIBERNATE!!
@Zhongkeruanjian
其实一开始我也是使用 MSSQL 做为数据库,因为用 CodeSmith 生成实体类似乎连接不了 MYSQL, 也许是我还没找到方法,不过我觉得就建一个数据库来说应该还是很简单的,所以就没把 MSSQL 的数据库脚本也帖上来。至于你说的引用 DLL 问题我也觉得是好的,下次我会照着这种方式来做,没有做到真正站在用户的角度思考问题啊 :)
不过运行 DEMO 需要个把小时,我想你是被什么问题卡住了吧,如果是的话可以帖出来一起讨论,不过就只是建个数据表,然后改下 hibernate.cfg.xml 文件中的配置信息,还是蛮简单的,至少在完成这个 DEMO 之前我也是这样改过来的
其实让读者自己动手做一些处理也不是什么坏事,实践才是最好的老师嘛 :)
不讨论这个了,呵,你现在在实际的项目用到NHIBERNATE没?
是的,我也是最近才使用的,不过就我们公司做的产品在效率上要求还是很宽松的, NH 如果使用恰当的话我觉得在效率上影响还是不会太大,而且换来的是一个松偶合可维护的系统,我觉得我也是很值的. 像以前使用 ADO.NET 就不得不手写大量 SQL 语句和存储过程,调试起来十分麻烦不说,在数据表中添加一个字段一般也会牵动到上面所有层或者需要修改大量的SQL,这简直就是恶梦, 像我们公司开发的东西一般开始需求就很不明确,因为需要看市场情况一步步而定,所以需求变更就是家常便饭的事,在这个时候我觉得使用 NH 很合适
"ADO.NET 就不得不手写大量 SQL 语句和存储过程"
这个不是问题,现在自动代码生成器能做大部分的工作。
NH主要是能做到领域建模,从而使软件可复用和易扩展。
你现在项目里Domain Object的继承用不用?
对了,你有MSN没有,我的是zhongkeruanjian%hotmail.com
以后有机会可以多讨论讨论NH ,现在用这个的太少了。
我想你误解了我的意思,对于"ADO.NET 就不得不手写大量 SQL 语句和存储过程"我想说明的是"大量 SQL 语句"而不是"手写“,因为大量存储过程的使用势必会造成将业务逻辑参差进 SQL 中,而且大量的 SQL 是不好维护的,这点深有体会,以前最怕的不是需求变更,而是由于变更所带来的后果是修改数据库。
对于你说的 Domain Object ,这个概念对我来说还是新的,网上查了一下有这样的解释“Entity和Domain Object除了有Setter,Getter属性外,还有仅仅属于自己的一些专有(special)方法,如CRUD及其他专有方法,和有一些Services接口,并不涉及Domain Object与Domain Object之间关系的一些方法。”这就有点类似于Castle 的 ActiveRecord了,至于继承我一般是在需要使用识别器的时候会用到,这也是多态的一个应用
你可以看看JAVAEYE论坛里的Robbin的关于“Domain Object”的论述。
用NHIBERNATE如果只是用它来做O/R Mapping,就想逃过“变更所带来的后果是修改数据库”的问题,那也是不可能的。如果用NHIBERATE,添加一个属性后照样也要修改数据库。
用NHIBERNATE主要是要你的业务逻辑层与数据访问层能真正解耦。从而让你能在作业务逻辑层时能真正面向对象(领域)编程。而不是面向数据对象编程。
我想我在上面说少了一句话,应该说加上修改数据库的同时也必须维护相应的 SQL 语句和存储过程。
以前读书时间的语文课本都被用来当枕头了,语言表达能力实在有限,而 Zhongkeruanjian 也有点断章取义的意思:),我当然也不会希望通过 O/RM 来实现数据库的零修改,而是 O/RM 让我可以不再惧怕数据库的修改,而且修改本身也不是坏事,说明系统有需要改进的地方。
因为从用上了 O/RM 起我们就可以把数据库设计放到了实现阶段而不参与设计,既然数据库可以放到实际阶段那就表示它小的动作不会影响到整体的设计。总结一下其实就是你最后的一句话了。
现在就去 JAVAEYE 找找看你推荐的文章,非常感谢!
" O/RM 起我们就可以把数据库设计放到了实现阶段而不参与设计"
O/R MAPPERING ,比如Nhibernate,它根本就没有数据库设计,它只有DOMAIN OBJECT及Object关联的设计。
也就是说:要从数据库驱动开发变成领域驱动开发。
nhibernate根本是个不能用的东西。请各位试验一下,做个多层分类的例子,然后打开nhibernate的showsql,看看它用什么sql语句就知道性能怎么样了。
cnblogs 为什么不能展开节点.我看不到详细内容
@子归
这不是cnblogs的问题,点击展开的时候有脚本错误,应该是编辑器生成的HTML中的问题,记得以前不会这样的,我会找个时间修复一下
@子归
不好意思,近来比较比较忙,所以文章里代码折叠的问题还没来得及修复,因为代码比较多的话用那编辑器编辑速度很慢,而且浏览器经常没响应,也是现在才发现提供的源码里面原因没有包含数据库脚本,真是大意了,现在重新将数据库脚本帖上,这次不使用代码折叠应该没问题了吧 :)
太感谢了.太及时了..只有谢谢2个字能表达我的心情
真是太感谢了.太及时了..只有谢谢二字能表达我的心情.