关于NHibernate的资料本身就不多,中文的就更少了,好在有一些翻译文章含金量很高,另外NHibernate与Hibernate的使用方式可谓神似,所以也有不少经验可以去参考Hibernate。

本文是实战中的心得,也是NHibernate进阶教程,假设你已经看过NHibernate的文档,但对它还是觉得无法驾驭,那么你可以看看本文,或者你只是想看看其他人在实战中是如何使用它的,你也可以看看。

本文主要会涉及到这些概念,关键字:级联操作 多表查询 复杂查询 值对象

需求简述:

简单地描述一下,有一个批次,一个批次包含多个订单,每个订单又可以有3个任务步骤要处理。

有时候我们需要获取一个批次,然后对这个批次下所有订单进行处理,也可能会涉及到订单的任务(数据库中可能涉及到3张有关联的表);

有时候我们也会获取一个订单,然后看这个订单属于哪个批次,还有就是对这个订单的任务步骤进行操作;

有时候我们会有相对复杂的查询,比如说要显示到第二个任务步骤的订单,并且第一个任务步骤已经完成,然后还需要根据订单中的日期进行过滤;

业务规则:

一个批次可以有多个订单,订单号不能重复;

一个订单有3个任务步骤,而且是三种不同类型的任务步骤;

也就是说如果我们获取了一个批次对象,它可能包含了40个订单,每个订单最多有4种任务类型,但转换成数据库SQL查询语句可能会返回最多160条记录。

关注领域模型,以领域模型为中心

图1-1 领域模型图

上图可以看出三个领域模型之间都存在着密切的关系。

批次类,批次被创建的时候(也就是new实例化的时候),它的创建时间(CreateDate)就是系统默认时间,

然后他有一个名字(Name),并且它依赖了一个集合(订单集合),和一些方法,一些操作订单的方法。

你会发现我这里使用的是Isei类库,大家都知道这个表示集合中的元素不能重复的。

另外一些操作订单的方法里都会有一句“order.PurchaseTime = null;”或者"order.PurchaseTime = this;",

这表示我们在批次中添加订单的同时,让订单对象也关联到批次,让订单对象可以感知到批次的存在,这一点非常重要,否则NHibernate无法执行级联操作。

public class PurchaseTime : DomainBase
{
public PurchaseTime()
{
CreateDate = DateTime.Now;
Status = "";
SalesmanStatus = "";
StorageStatus = "";
} /// </summary>
public virtual string Name
{
get;
set;
} private ICollection<PurchaseOrder> _purchaseOrders = new Iesi.Collections.Generic.HashedSet<PurchaseOrder>();
public virtual ICollection<PurchaseOrder> PurchaseOrders
{
get
{
return _purchaseOrders;
}
set
{
_purchaseOrders = value;
}
} public virtual void ClearOrders()
{
foreach (PurchaseOrder order in this._purchaseOrders)
{
order.PurchaseTime = null;
}
this._purchaseOrders.Clear();
} public virtual void AddOrder(PurchaseOrder order)
{
order.PurchaseTime = this;
this._purchaseOrders.Add(order);
} public virtual void RemoveOrder(PurchaseOrder order)
{
order.PurchaseTime = null;
this._purchaseOrders.Remove(order);
}
}

订单类,这个订单的业务逻辑已经被我砍掉很多了,只保留一些我们要讨论的内容,本来它的内容相当丰富。

可以看到订单依赖一个批次对象,并且可以拥有任务集合。

AppointTask方法里面包含了许多逻辑,首先会调用ContainsTask判断该订单是否已经有这个任务步骤,它只是执行这样一句“_purchaseTasks.Contains(task);”,

意思是看一下该订单的任务集合中是不是有相同的任务对象存在,这里非常有意思,通常比较两个对象是否相等.NET会从内存中去比较他们是不是同一个对象的引用,而使用NHibernate的话我们可能会希望他们的ID属性是不是同一个来判断他们是否相等,所以我们一般会涉及一个所有领域模型的基类(DomainBase),来重写Equals和GetHashCode方法。

但是我们上面的需求提到验证任务类型存不存在,所以我们关心的不是ID相不相等,而是类型(tasktype)相不相等,后面会提到如何重写任务模型中的Equals和GetHashCode方法。

这个领域模型的设计已经给后面的设计打下了重要的基础,你需要思考为什么我们尽量不写存储过程,或者是再查询一次数据库来验证是否存在或者其他什么的。

public class PurchaseOrder : DomainBase
{
/// <summary>
/// 订单号
/// </summary>
public virtual string OrderNumber
{
get;
set;
} /// <summary>
/// 交货日期
/// </summary>
public virtual DateTime? DateOfDelivery
{
get;
set;
} private PurchaseTime _purchaseTime = new PurchaseTime(); public virtual PurchaseTime PurchaseTime
{
get
{
return _purchaseTime;
}
set
{
_purchaseTime = value;
}
} private ICollection<PurchaseTask> _purchaseTasks = new HashedSet<PurchaseTask>();
public virtual ICollection<PurchaseTask> PurchaseTasks
{
get
{
return _purchaseTasks;
}
set
{
_purchaseTasks=value;
}
} /// <summary>
/// 指派任务
/// </summary>
/// <param name="task"></param>
public virtual void AppointTask(PurchaseTask task)
{
if (ContainsTask(task))
{
if (TaskProgress == )
{
RemoveTask(task);
}
else
{
throw new Exception("任务已经开始,无法重新指派!");
}
}
if (task.Principal != )
{
task.PurchaseOrder = this;
_purchaseTasks.Add(task);
}
} public virtual void ClearTask()
{
foreach (PurchaseTask item in _purchaseTasks)
{
item.PurchaseOrder = null;
}
_purchaseTasks.Clear();
} public virtual void RemoveTask(PurchaseTask task)
{
task.PurchaseOrder = null;
_purchaseTasks.Remove(task);
} public virtual bool ContainsTask(PurchaseTask task)
{
return _purchaseTasks.Contains(task);
} /// <summary>
/// 任务进度
/// 0未开始
/// 1完成
/// 2完成
/// 3完成
/// </summary>
public virtual decimal? TaskProgress
{
get;
set;
} }

任务类,在这个类的最后我们已经看到override重写比较对象相等的Equals和GetHashCode方法了,当我们调用Contains方法来判断任务集合中是否已经包含这个任务时,将会自动调用Equals、GetHashCode方法。

public class PurchaseTask : DomainBase
{
private PurchaseOrder _purchaseOrder = new PurchaseOrder(); /// <summary>
/// PurchaseOrder
/// </summary>
public virtual PurchaseOrder PurchaseOrder
{
get { return _purchaseOrder; }
set { _purchaseOrder=value; }
}
/// <summary>
/// 任务类型
/// 类型1 类型2 类型3
/// </summary>
public virtual decimal? TaskType
{
get;
set;
}
/// <summary>
/// 任务负责人ID
/// </summary>
public virtual decimal? Principal
{
get;
set;
} /// <summary>
/// 是否处理
/// </summary>
/// <returns></returns>
public virtual bool IfHandle()
{
if (this.PurchaseOrder.TaskProgress ==GetNeedHandleProgress(this.TaskType))
{
return true;
}
return false;
} /// <summary>
/// 获取需要处理的进度步骤
/// </summary>
/// <param name="tasktype"></param>
/// <returns></returns>
public virtual decimal? GetNeedHandleProgress(decimal? tasktype)
{
switch (tasktype.ToString().ToLower())
{
case "":
return ;
case "":
return ;
case "":
return ;
default:
throw new Exception("任务类型错误!无法定位需要处理的进度。");
}
} public override bool Equals(object obj)
{
PurchaseTask task = obj as PurchaseTask;
if (task == null)
return false;
return TaskType.Equals(task.TaskType);
} public override int GetHashCode()
{
return TaskType.GetHashCode();
}
}

总结:

我们介绍了领域模型的设计,你会发现本文提到的模型它们之间都是一对多关系,模型包含了许多业务,看起来非常复杂,其实,这个复杂的模型并不是一开始就建成的,开始时它非常简单,也许最简单的时候只有一个UML类图的草稿只包含了对象名称,然后逐步细化。

使用类似于Hibernate的O/R Mapping工具打好扎实的基础非常重要,可以使你在设计领域模型时更加得心应手。基础知识包括UML建模、面向对象分析、面向对象设计,这些知识可以参考《软件工程》中面向对象的那一部分进行学习。

这一节就到这里,在下一节中将会看到映射文件(hbm.xml)的编写,与一些级联的操作(级联保存、多表查询)的应用。

NHibernate实战详解(一)领域模型设计的更多相关文章

  1. NHibernate实战详解(二)映射配置与应用

    关于NHibernate的资料本身就不多,中文的就更少了,好在有一些翻译文章含金量很高,另外NHibernate与Hibernate的使用方式可谓神似,所以也有不少经验可以去参考Hibernate. ...

  2. Android安卓书籍推荐《Android驱动开发与移植实战详解》下载

    百度云下载地址:点我 Android凭借其开源性.优异的用户体验和极为方便的开发方式,赢得了广大用户和开发者的青睐,目前已经发展成为市场占有率很高的智能手机操作系统. <Android驱动开发与 ...

  3. 《Android NFC 开发实战详解 》简介+源码+样章+勘误ING

    <Android NFC 开发实战详解>简介+源码+样章+勘误ING SkySeraph Mar. 14th  2014 Email:skyseraph00@163.com 更多精彩请直接 ...

  4. 011-Scala中的apply实战详解

    011-Scala中的apply实战详解 object中的apply方法 class中的apply方法 使用方法 apply方法可以应用在类或者Object对象中 class类 必须要创建实例化的类对 ...

  5. 010-Scala单例对象、伴生对象实战详解

    010-Scala单例对象.伴生对象实战详解 Scala单例对象详解 函数的最后一行是返回值 子项目 Scala伴生对象代码实战 object对象的私有成员可以直接被class伴生类访问,但是不可以被 ...

  6. 008-Scala主构造器、私有构造器、构造器重载实战详解

    008-Scala主构造器.私有构造器.构造器重载实战详解 Scala主构造器实战 无参数的主构造器 分析 1.name 需要赋初值,一般通过占位符来代表空值 2.private 声明私有的age 生 ...

  7. 009-Scala的内部类实战详解

    009-Scala的内部类实战详解 Scala内部类详解 与java的区别 java的内部类是从属于外部类的 Scala的内部类是从属于对象的 内部类在调用方法的时候传递的内部类只能是由自己本身 欢迎 ...

  8. 007-Scala类的属性和对象私有字段实战详解

    007-Scala类的属性和对象私有字段实战详解 Scala类的使用实战 变量里的类必须赋初值 def函数时如果没参数可不带括号 2.不需要加Public声明 getter与setter实战 gett ...

  9. 005-Scala数组操作实战详解

    005-Scala数组操作实战详解 Worksheet的使用 交互式命令执行平台 记得每次要保存才会出相应的结果 数组的基本操作 数组的下标是从0开始和Tuple不同 缓冲数组ArrayBuffer( ...

随机推荐

  1. 从头开始写框架(一):浅谈JS模块化发展

    博客申请下来已经过去一个月了,一直不知道写点什么,毕竟我的文笔不是很好orz. 不过既然申请下来了,不写点什么总是觉得很可惜.正好最近在自己写框架,就把自己的进程和一些心得体会分享出来吧. 写在前面: ...

  2. 绿书模拟day10 单词前缀

    [题目描述]一组单词是安全的,当且仅当不存在一个单词是另一个单词的前缀,这样才能保证数据不容易被误解,现在你手上有一个单词集合s,你需要计算有多少个自己是安全的.注意空集永远是安全的.[输入格式]第一 ...

  3. JS/HTML 保存图片到本地:HTML <a> download 属性

    JS如何保存图片到本地呢?自己百度一下吧! 这里想要说的是,可以利用 HTML 的 <a> 标签 来是实现保存图片到本地的功能,参考代码如下: <a href="http: ...

  4. cocoapods pod install 安装报错 is not used in any concrete target

    低版本的cocoa pods在编写Podfile文件时这样写就可以了 platform :ios, '8.0'pod 'AFNetworking' 高版本的cocoa pods在编写Podfile文件 ...

  5. mysql 修改表结构

    alter table 表名 modify column 字段名 varchar(数量); 将varchar(50)改为255 alter table 表名 modify column 字段名 var ...

  6. Javascript高级程序设计——基本包装类型

    既然js中的基本类型没有属性和方法那么为什么对字符串进行subString()方法可以呢?基本类型不应该没有方法的吗? 这就是基本包装类型啦! ECMAScript提供了三个特殊的引用类型,Boole ...

  7. python 编码 UnicodeDecodeError

    将一个py脚本从Centos转到win运行,出错如下: UnicodeDecodeError: 'gbk' codec can't decode byte 0xff in position 0: il ...

  8. 纯CSS多级菜单

    主要代码部分: /*新增的二级菜单部分*/ .menu ul ul { visibility:hidden;/*开始时是隐藏的*/ position:absolute; left:0px; top:3 ...

  9. IntelliJ Idea 修改编码格式

    Setting→Editor→File Encodings→设置“Project Encoding”为UTF-8,如图:

  10. HDU 4941 Magical Forest(map映射+二分查找)杭电多校训练赛第七场1007

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4941 解题报告:给你一个n*m的矩阵,矩阵的一些方格中有水果,每个水果有一个能量值,现在有三种操作,第 ...