NHibernate
NHibernate是一个面向.NET环境的对象/关系数据库映射工具。对象/关系数据库映射(object/relational mapping,ORM)这个术语表示一种技术,用来把对象模型表示的对象映射到基于SQL的关系模型数据结构中去。
对象和关系数据库之间的映射是用一个XML文档(XML document)来定义的。这个映射文档被设计为易读的,并且可以手工修改。映射语言是以.NET为中心的,意味着映射是按照持久化类的定义来创建的,而非表的定义。在Hibernate中XML映射文档可以手动定义,也有一些工具来辅助生成,包括Xdoclet、Middlegen和AndroMDA,但是在NHibernate文档中并没有上述的辅助工具,不过可以采用MyGeneration这样的代码生成工具来生成XML配置文档。
FluentNHibernate
连贯NHibernate(FluentNHibernate)是通过编程方式而不是使用XML配置创建NHiberne映射的API。它的目标是在项目中运用NHibernate时减少所遇到的困难,提供更好的可读性、试性 和编译时的安全性。
有以下特性:
- ■ 自动映射(Automapping)
- ■ 流映射(Fluent Mapping)
- ■ 约定(Conventions)
- ■ 配置(Configuration)
- ■ 持久化测试(Persistence Testing)
S#arp架构
Sharp Architecture 是一个用来构建易于维护的 Web应用框架,基于 ASP.NET MVC 和NHibernate.
无论使用任何框架,其主要优点是减少冗余的代码和提高最终项目的质量。框架使开发人员,有更多的时间
把注意力放在自己的领域和用户体验上。
有以下特性:
- ■ 基于领域设计驱动(Domain Driven Design)
- ■ 模块的松耦合
- ■ 预配置的基础设施
- ■ 开源项目
框架分层:
- ■ 表示层(Presentation)
- ■ 领域实体层(Domain)
- ■ 服务层(Tasks)
- ■ 基础设施层(Infrastructure)
- ■ 测试层(Specs/Tests)
想法
NHibernate ORM框架一定会记录实体的Mapping信息,跟数据库进行通信。所以查找S#ARP框架是否有封装好的Mapping信息的获取方法,没有就在从NHibernate API 里面找,如果找到了实体的Mapping信息后,就可以知道了表的数据结构信息。然后通过自定义描述特性(Attribute),为领域实体的类和属性上加上描述特性,通过反射获取上面的特性信息作为表和字段的描述。
实践 - 准备工作
定义描述特性:
实体描述特性抽象类:
/// <summary>
/// 抽象 - 实体描述
/// </summary>
public abstract class BaseEntityDescAttribute : Attribute
{
/// <summary>
/// 描述
/// </summary>
public readonly string Description;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="description">描述</param>
protected BaseEntityDescAttribute(string description)
{
Description = description;
}
}
实体描述:
/// <summary>
/// 实体描述特性
/// </summary>
[AttributeUsage(AttributeTargets.Class,AllowMultiple = false,Inherited = true)]
public class EntityDescAttribute : BaseEntityDescAttribute
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="description">描述</param>
public EntityDescAttribute(string description)
: base(description){
}
}
实体属性描述:
/// <summary>
/// 实体属性描述
/// </summary>
[AttributeUsage(AttributeTargets.Property,AllowMultiple = false)]
public class EntityPropertyDescAttribute : BaseEntityDescAttribute
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="description">实体描述</param>
public EntityPropertyDescAttribute(string description)
: base(description)
{
}
}
例子:User实体
Mapping信息如下:
public sealed class UserMap : ClassMap<User>
{
public UserMap()
{
Table(MapConst.Mapname + "Users");
Id(m => m.Id).Column("UserId").GeneratedBy.HiLo(
MapConst.Mapname + "hibernate_unique_key",
"next_hi",
"0",
p =>p.AddParam("where",
string.Format(
"table_name = '{0}'",
MapConst.Mapname + "Users")));
Map(m => m.UserName).Length(100).Column("UserName");
Map(m => m.UserNo).Length(100);
Map(m => m.ImgHead).Length(200);
Map(m => m.SmallImgHead).Length(200);
Map(m => m.StandardImgHead).Length(200);
Map(m => m.Domain).Length(15);
Map(m => m.Email).Length(30);
Map(m => m.Card).Length(20);
Map(m => m.UserSex).CustomType<UserSex>();
Map(m => m.UserState).CustomType<UserState>();
Map(m => m.Phone).Length(15);
Map(m => m.Paw);
Map(m => m.RealName).Length(25);
Map(m => m.EmailValidate);
Map(m => m.QQ).Length(30);
Map(m => m.CreateDate);
Map(m => m.LastUpdate);
Map(m => m.LoginDate);
Map(m => m.LastLoginDate);
Map(m => m.LoginIp).Length(15);
Map(m => m.LastLoginIp).Length(15);
Map(m => m.DesPassword);
}
}
实体信息如下:
/// <summary>
/// 用户
/// </summary>
[EntityDesc("用户,用于记录平台用户信息")]
public class User : Entity
{
/// <summary>
/// 默认构造函数
/// </summary>
public User()
{
}
/// <summary>
/// 用户名
/// </summary>
[DomainSignature]
[Length(255, Message = "用户名不能超出255个字符")]
[NotNullNotEmpty]
[EntityPropertyDesc("用户名")]
public virtual string UserName { get; set; }
/// <summary>
/// 用户编码
/// </summary>
[EntityPropertyDesc("用户编码")]
public virtual string UserNo { get; set; }
/// <summary>
/// 密码
/// </summary>
[Length(255, Message = "用户名不能超出255个字符")]
[NotNullNotEmpty]
[EntityPropertyDesc("密码")]
public virtual string Paw { get; set; }
/// <summary>
/// 用户头像(原图)
/// </summary>
[EntityPropertyDesc("用户头像(原图)")]
public virtual string ImgHead { get; set; }
/// <summary>
/// 用户头像(小图) 50 X 50
/// </summary>
[EntityPropertyDesc("用户头像(小图) 50 X 50")]
public virtual string SmallImgHead { get; set; }
/// <summary>
/// 用户头像(标准图) 150 X 150
/// </summary>
[EntityPropertyDesc("用户头像(标准图) 150 X 150")]
public virtual string StandardImgHead { get; set; }
/// <summary>
/// 域名地址
/// </summary>
[EntityPropertyDesc("域名地址")]
public virtual string Domain { get; set; }
/// <summary>
/// 邮件
/// </summary>
[EntityPropertyDesc("邮件")]
public virtual string Email { get; set; }
/// <summary>
/// 身份证
/// </summary>
[EntityPropertyDesc("身份证")]
public virtual string Card { get; set; }
/// <summary>
/// 性别
/// </summary>
[EntityPropertyDesc("性别")]
public virtual UserSex? UserSex { get; set; }
/// <summary>
/// 用户状态
/// </summary>
[EntityPropertyDesc("用户状态")]
public virtual UserState UserState { get; set; }
/// <summary>
/// 用户手机号码
/// </summary>
[Length(20, Message = "手机号码不能超出22个字符")]
[EntityPropertyDesc("用户手机号码")]
public virtual string Phone { get; set; }
/// <summary>
/// 明文密码
/// </summary>
[EntityPropertyDesc("明文密码")]
public virtual string DesPassword { get; set; }
/// <summary>
/// 真实名称
/// </summary>
[EntityPropertyDesc("真实名称")]
public virtual string RealName { get; set; }
/// <summary>
/// 是否通过邮箱验证
/// </summary>
[EntityPropertyDesc("是否通过邮箱验证")]
public virtual bool? EmailValidate { get; set; }
/// <summary>
/// QQ
/// </summary>
[EntityPropertyDesc("QQ")]
[Length(20, Message = "QQ不能超出22个字符")]
public virtual string QQ { get; set; }
/// <summary>
/// 创建时间
/// </summary>
[EntityPropertyDesc("创建时间")]
public virtual DateTime CreateDate { get; set; }
/// <summary>
/// 最后修改时间
/// </summary>
[EntityPropertyDesc("最后修改时间")]
public virtual DateTime? LastUpdate { get; set; }
/// <summary>
/// 当前登录时间
/// </summary>
[EntityPropertyDesc("当前登录时间")]
public virtual DateTime? LoginDate { get; set; }
/// <summary>
/// 上次登录时间
/// </summary>
[EntityPropertyDesc("上次登录时间")]
public virtual DateTime? LastLoginDate { get; set; }
/// <summary>
/// 当前登录IP
/// </summary>
[EntityPropertyDesc("当前登录IP")]
public virtual string LoginIp { get; set; }
/// <summary>
/// 最后登录IP
/// </summary>
[EntityPropertyDesc("最后登录IP")]
public virtual string LastLoginIp { get; set; }
/// <summary>
/// 用户收货地址
/// </summary>
[EntityPropertyDesc("用户收货地址")]
public virtual IList<UserAddress> UserAddress { get; set; }
/// <summary>
/// 用户订单
/// </summary>
[EntityPropertyDesc("用户订单")]
public virtual IList<ProductOrder> ProductOrders { get; set; }
}
实践 - 生成数据库字典
使用单元测试生成数据库字典
单元测试代码如下:
/// <summary>
/// 文件输出目录
/// </summary>
private const string DB_FOLDER_PATH = "../../../../db/";
/// <summary>
/// 领域对象DLL名
/// </summary>
private const string DomainDll = "Cotide.Domain";
/// <summary>
/// NHibernate Configuration
/// </summary>
private static NHibernate.Cfg.Configuration _nhConfig;
[Test]
public void WriteDomainDbInfo()
{
// 获取NHibernate 获取领域实体元数据集合
// 通过NHibernateSession静态类 -> 获取默认工厂 -> 获取所有领域实体元数据
var allClassMetadata = NHibernateSession.
GetDefaultSessionFactory().GetAllClassMetadata();
// 输出内容变量
var str = new StringBuilder();
// 遍历元数据
foreach (var entry in allClassMetadata)
{
// 获取当前元数据
var entity = ((SingleTableEntityPersister)entry.Value);
// 获取实体映射的表名
var tableName = entity.TableName;
// 领域实体名称
var entityName = entity.EntityName;
// 领域实体Mapping对象
var userClassMap = _nhConfig.GetClassMapping(entityName);
// 领域实体主键名
var primaryKey = userClassMap.Table.PrimaryKey.Columns.FirstOrDefault().Text;
// 领域实体下的属性列表
var column = userClassMap.Table.ColumnIterator;
// 输出表头
str.Append(string.Format("<H1>{0}({1})</H1>", tableName, entityName));
// 获取实体描述
// 使用反射dll -> 获取EntityDescAttribute特性描述内容
var type = Type.GetType(entityName + "," + DomainDll);
if (type != null)
{
var attr = type.GetCustomAttributes(typeof(EntityDescAttribute), true);
if (attr.Length > 0)
{
str.Append(string.Format("<P>表描述:{0}</P>",
((EntityDescAttribute)attr[0]).Description));
}
}
// 输出表格表头
str.Append("<table width=\"100%\" " +
"border=\"1\" " +
"cellspacing=\"0\" " +
"bordercolor=\"#333\" >");
str.Append("<tr style='background-color:#3AF;'>" +
"<td>序号</td>" +
"<td>数据库字段</td>" +
"<td>类型</td>" +
"<td>描述</td>" +
"<td>唯一约束</td>" +
"<td>允许为NULL</td>" +
"<td>是否主键</td>" +
"</tr>");
// 遍历领域实体下的属性
// 顺序索引
var index = 0;
foreach (var obj in column)
{
// 属性描述
var attrDesc = "";
// 获取NHibernate Column对象
var item = (Column)obj.Value.ColumnIterator.FirstOrDefault();
if (item == null)
continue;
// 判断如果为领域实体唯一标识则不获取属性描述
if (primaryKey == item.Text)
{
//唯一标识
attrDesc = "唯一标识";
}
else
{
// 如果超出索引的 -> 判断为非数据库字段,为领域实体属性
// -> 跳出本次处理
if(index >= entity.PropertyNames.Count())
{
continue;
}
// 获取属性上的EntityPropertyDescAttribute特性描述内容
var memberItem = entity.PropertyNames[index];
if(type!=null)
{
var property = type.GetProperty(memberItem);
var memberAttr = property.GetCustomAttributes(
typeof(EntityPropertyDescAttribute), true);
if (memberAttr.Length > 0)
{
attrDesc = ((EntityPropertyDescAttribute)
memberAttr[0]).Description;
}
}
++index;
}
// 输出内容
str.Append(string.Format("<tr>" +
"<td>{0}</td>" +
"<td>{1}</td>" +
"<td>{2}</td>" +
"<td>{3}</td>" +
"<td>{4}</td>" +
"<td>{5}</td>" +
"<td>{6}</td>" +
"</tr>",
index + 1,
item.Text, GetType(item.Value.Type),
attrDesc,
item.IsUnique ? "Yes" : "No",
item.IsNullable ? "Yes" : "No",
(primaryKey == obj.Name) ? "Yes" : "No"));
}
str.Append("</table>");
}
// 输出数据库字典文件
// 文件名
const string outputFileName = "数据库字典.doc";
// 输出文件夹路径
string outputFilePath = Path.Combine(DB_FOLDER_PATH, outputFileName);
// 输出操作
File.WriteAllText(outputFilePath, str.ToString(), Encoding.GetEncoding("utf-8"));
}
#region Helper
/// <summary>
/// 获取数据库类型
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private static string GetType(NHibernate.Type.IType type)
{
if (type is NHibernate.Type.Int32Type)
{
return "int";
}
if (type is NHibernate.Type.StringType)
{
var sqlType = ((NHibernate.Type.StringType)type).SqlType;
return string.Format("nvarchar({0})", GetLength(sqlType));
}
if (type is NHibernate.Type.BooleanType)
{
return "bit";
}
if (type is NHibernate.Type.DateTimeType)
{
return "datetime";
}
if (type is NHibernate.Type.DecimalType)
{
return "decimal";
}
if (type is NHibernate.Type.DoubleType)
{
return "double";
}
if (type is NHibernate.Type.Int64Type)
{
return "bitint";
}
if (type is NHibernate.Type.CharType)
{
return "char";
}
if (type is NHibernate.Type.TimestampType)
{
return "timestamp";
}
if (type is NHibernate.Type.Int16Type)
{
return "tinyint";
}
if (type is NHibernate.Type.XmlDocType)
{
return "xml";
}
if (type is NHibernate.Type.BinaryType)
{
var sqlType = ((NHibernate.Type.StringType)type).SqlType;
return string.Format("varbinary({0})", sqlType.Length > 4000 ? "max" : sqlType.Length.ToString());
}
if (type is NHibernate.Type.PersistentEnumType)
{
var enumType = (NHibernate.Type.PersistentEnumType)type;
return enumType.SqlType.DbType.ToString() == "Int32" ? "int" : "nvarchar(255)";
}
if (type is NHibernate.Type.EntityType)
{
// 暂时使用这种方式,以后重构
return "int";
}
return "未知";
}
/// <summary>
/// 获取SQL长度
/// </summary>
/// <param name="sqlType">SQL类型</param>
/// <returns></returns>
private static string GetLength(SqlType sqlType)
{
return sqlType.Length > 4000 ? "max" : sqlType.Length <= 0 ? "255" : sqlType.Length.ToString();
}
#endregion
NHibernate.Type.IType 对应的SQL类型 因为没有文档,所以都是自己去摸索,所以这部分
代码可能以后需要重构,但对于常用的类型都已经包括在里面了。
结果
在输出目录下生成数据库字典:
字典内容如下:
参考资料
- ■ NHibernate官方(http://nhforge.org/)
- ■ FluentnHibernate官方(http://www.fluentnhibernate.org/)
- ■ Sharp Architecture(https://github.com/sharparchitecture)
总结
DBA负责数据库的管理的工作的时候,肯定需要数据库字典。
如果每次手动维护数据库字典,多痛苦的事情。
为了偷懒,所以花了几天时间去了解框架和一些类库的API,虽然过程是痛苦的,
但做出来一点东西的时候挺满足的。
“ 当面对框架的时候,如果带着设计的思想去阅读它,你就会发现里面其实很多东西都很巧妙 ”