在动手写这篇 Post 之前我得先声明一下,本人对 Castle 的了解还不够深入,仅仅在近期在项目中使用过而已,所以我的做法不一定是正确的,也不一定能给大家带来帮助,说不定还会给你带来更多的麻烦。
把 Posts 发到首页上也是希望大家都能说出自己的想法,还有我的处理方式的可行性做一些分析和批评。
从对 Castle 一无所知到有所了解,Terrylee 的 Castle 开发系列(推荐) 给了我很大的帮助,特在此对 Terrylee 表示感谢。
对于我自己而言我并不太喜欢 Castle 封装 NHibernate 的方式, 虽然减少了配置映射文件的复杂性和易出错,但我还是更喜欢把映射文件和实体类分开来,我觉得这更加直观,看起来也不会太凌乱,唯一的缺点就是有时候会顾此失彼,但我认为这是值得的。就好比是 ASP.NET 将界面和代码相分离,但萝卜青菜各有所爱,我们所要做的就是像找女朋友一样选自己喜欢的。
Castle 把 ActiveRecordBase 声明为 abstract 以至于在实体类直接继承并实现一些自操作行为,如 Save()、 Create()、Update()、Delete() 也是我深恶痛绝日,我非常不喜欢把业务操作移殖到实体层去,层与层之间的职任应该划分得很清晰,要不然我看了就觉得不舒服,可能有人会说如果不喜欢我完全可以不去理会它,当它不存在就是了,但它却确确实实地存在着,还是 public ,无论走到那里都能看到自己不喜欢的东西我还能无动于衷吗?而且我们还有同样方便方法来代替,稍后将会讲到,所以我要把 ActiveRecordBase 改造成为 ActiveRecord 并且只留下静态方法,相当于一个 NHibernateHelper。
至于 IOC 容器我更喜欢 Spring.Net,虽然比较复杂,但还是那句话,萝卜青菜各有所爱。
接下来就开始我们的改造工作,首先是 Session 管理的问题,由于现在我没时间研究 Castle 的源码,加上水平有限,决定另寻它路。于是在 DDLLY命名空间 里找到了 NHibernate的Session管理,我承认通过 IHttpModule 来存储 Session 不失为了一个好方法,但它仅解决了客户端请求操作的问题,当请求来自服务器端的时候 HttpContext.Current 将为 NULL 值。我的解决方法是新建一个 SessionObject 类,类中两个属性,一个用于存储 Session 值,另一个用于标记操作完成后是否关闭 Session,当 HttpContext 为空时创建一个新的 SessionObject 实例并将其标记为需要 Close,这样做的缺点是每次操作都必须检查是否需要关闭 Session,但暂时还想不到更好的解决方法就先凑合着使用了。
下面是我改造过后的代码,这个类并不能独立工作,它还依赖于其它类,在后面我会放上完整项目供下载。
namespace Yyw.DBUtility.NH.ActiveRecords
{
using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Expression;
using Yyw.DBUtility.NHibernateSessionStorage;
/**//// <summary>
/// Base class for all ActiveRecord classes. Implements
/// all the functionality to simplify the code on the
/// subclasses.
/// </summary>
[Serializable]
public class ActiveRecord
{
/**//// <summary>
/// Constructs an ActiveRecordBase subclass.
/// </summary>
public ActiveRecord()
{
}
static methods#region static methods
/**//// <summary>
/// Invokes the specified delegate passing a valid
/// NHibernate session. Used for custom NHibernate queries.
/// </summary>
/// <param name="targetType">The target ActiveRecordType</param>
/// <param name="call">The delegate instance</param>
/// <param name="instance">The ActiveRecord instance</param>
/// <returns>Whatever is returned by the delegate invocation</returns>
//public static object Execute(Type targetType, NHibernateDelegate call, object instance)
//{
// if (targetType == null) throw new ArgumentNullException("targetType", "Target type must be informed");
// if (call == null) throw new ArgumentNullException("call", "Delegate must be passed");
// EnsureInitialized(targetType);
// ISession session = _holder.CreateSession( targetType );
// try
// {
// return call(session, instance);
// }
// catch(Exception ex)
// {
// throw new ActiveRecordException("Error performing Execute for " + targetType.Name, ex);
// }
// finally
// {
// _holder.ReleaseSession(session);
// }
//}
/**//// <summary>
/// Finds an object instance by a unique ID
/// </summary>
/// <param name="targetType">The AR subclass type</param>
/// <param name="id">ID value</param>
/// <param name="throwOnNotFound"><c>true</c> if you want to catch an exception
/// if the object is not found</param>
/// <returns></returns>
/// <exception cref="ObjectNotFoundException">if <c>throwOnNotFound</c> is set to
/// <c>true</c> and the row is not found</exception>
public static T FindByPrimaryKey<T>(object id, bool throwOnNotFound) where T : class
{
SessionObject sessionObj = NHibernateDatabaseFactory.CreateSession();
ISession session = sessionObj.Session;
try
{
return session.Load<T>( id );
}
catch(ObjectNotFoundException ex)
{
if (throwOnNotFound)
{
String message = String.Format("Could not find {0} with id {1}", typeof(T).Name, id);
throw new NotFoundException(message, ex);
}
return default(T);
}
catch(Exception ex)
{
throw new ActiveRecordException("Could not perform Load (Find by id) for " + typeof(T).Name, ex);
}
finally
{
if (sessionObj.IsNeedClose)
session.Close();
}
}
/**//// <summary>
/// Finds an object instance by a unique ID
/// </summary>
/// <param name="targetType">The AR subclass type</param>
/// <param name="id">ID value</param>
/// <returns></returns>
public static T FindByPrimaryKey<T>(object id) where T : class
{
return FindByPrimaryKey<T>(id, true);
}
/**//// <summary>
/// Returns all instances found for the specified type.
/// </summary>
/// <param name="targetType"></param>
/// <returns></returns>
public static IList<T> FindAll<T>() where T : class
{
return FindAll<T>((Order[]) null);
}
/**//// <summary>
/// Returns a portion of the query results (sliced)
/// </summary>
public static IList<T> SlicedFindAll<T>(int firstResult, int maxresults, Order[] orders, params ICriterion[] criterias) where T : class
{
SessionObject sessionObj = NHibernateDatabaseFactory.CreateSession();
ISession session = sessionObj.Session;
try
{
ICriteria criteria = session.CreateCriteria(typeof(T));
foreach( ICriterion cond in criterias )
{
criteria.Add( cond );
}
if (orders != null)
{
foreach( Order order in orders )
{
criteria.AddOrder( order );
}
}
criteria.SetFirstResult(firstResult);
criteria.SetMaxResults(maxresults);
return criteria.List<T>();
}
catch(Exception ex)
{
throw new ActiveRecordException("Could not perform SlicedFindAll for " + typeof(T).Name, ex);
}
finally
{
if (sessionObj.IsNeedClose)
session.Close();
}
}
/**//// <summary>
/// Returns a portion of the query results (sliced)
/// </summary>
public static IList<T> SlicedFindAll<T>(int firstResult, int maxresults, params ICriterion[] criterias) where T : class
{
return SlicedFindAll<T>(firstResult, maxresults, null, criterias);
}
/**//// <summary>
/// Returns all instances found for the specified type
/// using sort orders and criterias.
/// </summary>
/// <param name="targetType"></param>
/// <param name="orders"></param>
/// <param name="criterias"></param>
/// <returns></returns>
public static IList<T> FindAll<T>(Order[] orders, params ICriterion[] criterias) where T : class
{
SessionObject sessionObj = NHibernateDatabaseFactory.CreateSession();
ISession session = sessionObj.Session;
try
{
ICriteria criteria = session.CreateCriteria(typeof(T));
foreach( ICriterion cond in criterias )
{
criteria.Add( cond );
}
if (orders != null)
{
foreach( Order order in orders )
{
criteria.AddOrder( order );
}
}
return criteria.List<T>();
}
catch(Exception ex)
{
throw new ActiveRecordException("Could not perform FindAll for " + typeof(T).Name, ex);
}
finally
{
if (sessionObj.IsNeedClose)
session.Close();
}
}
/**//// <summary>
/// Returns all instances found for the specified type
/// using criterias.
/// </summary>
/// <param name="targetType"></param>
/// <param name="criterias"></param>
/// <returns></returns>
public static IList<T> FindAll<T>(params ICriterion[] criterias) where T : class
{
return FindAll<T>(null, criterias);
}
/**//// <summary>
/// Finds records based on a property value
/// </summary>
/// <remarks>
/// Contributed by someone on the forum
/// http://forum.castleproject.org/posts/list/300.page
/// </remarks>
/// <param name="targetType">The target type</param>
/// <param name="property">A property name (not a column name)</param>
/// <param name="value">The value to be equals to</param>
/// <returns></returns>
public static IList<T> FindAllByProperty<T>(String property, object value) where T : class
{
return FindAll<T>(Expression.Eq(property, value));
}
/**//// <summary>
/// Finds records based on a property value
/// </summary>
/// <param name="targetType">The target type</param>
/// <param name="orderByColumn">The column name to be ordered ASC</param>
/// <param name="property">A property name (not a column name)</param>
/// <param name="value">The value to be equals to</param>
/// <returns></returns>
public static IList<T> FindAllByProperty<T>(String orderByColumn, String property, object value) where T : class
{
return FindAll<T>(new Order[] { Order.Asc(orderByColumn) }, Expression.Eq(property, value));
}
public static IList<T> FindAllByQueryName<T>(string queryName) where T : class
{
return FindAllByQueryName<T>(queryName, null);
}
public static IList<T> FindAllByQueryName<T>(string queryName, QueryParameter[] parameters) where T : class
{
SessionObject sessionObj = NHibernateDatabaseFactory.CreateSession();
ISession session = sessionObj.Session;
try
{
ICriteria criteria = session.CreateCriteria(typeof(T));
IQuery query = session.GetNamedQuery(queryName);
if (parameters != null)
{
foreach (QueryParameter parameter in parameters)
{
if (!QueryParameter.IsAvail(parameter))
continue;
if (parameter.Value is ICollection)
{
if (parameter.Type == null)
{
query.SetParameterList(parameter.Name, (ICollection)parameter.Value);
}
else
{
query.SetParameterList(parameter.Name, (ICollection)parameter.Value, parameter.Type);
}
}
else
{
if (parameter.Type == null)
{
query.SetParameter(parameter.Name, parameter.Value);
}
else
{
query.SetParameter(parameter.Name, parameter.Value, parameter.Type);
}
}
}
}
return query.List<T>();
}
catch (Exception ex)
{
throw new ActiveRecordException("Could not perform FindAllByQueryName for " + typeof(T).Name, ex);
}
finally
{
if (sessionObj.IsNeedClose)
session.Close();
}
}
/**//// <summary>
/// Searches and returns the first row.
/// </summary>
/// <param name="targetType">The target type</param>
/// <param name="criterias">The criteria expression</param>
/// <returns>A <c>targetType</c> instance or <c>null</c></returns>
public static T FindFirst<T>(params ICriterion[] criterias) where T : class
{
return FindFirst<T>(null, criterias);
}
/**//// <summary>
/// Searches and returns the first row.
/// </summary>
/// <param name="targetType">The target type</param>
/// <param name="orders">The sort order - used to determine which record is the first one</param>
/// <param name="criterias">The criteria expression</param>
/// <returns>A <c>targetType</c> instance or <c>null</c></returns>
public static T FindFirst<T>(Order[] orders, params ICriterion[] criterias) where T : class
{
IList<T> result = SlicedFindAll<T>(0, 1, orders, criterias);
return (result != null && result.Count > 0 ? result[0] : default(T));
}
/**//// <summary>
/// Searches and returns the a row. If more than one is found,
/// throws <see cref="ActiveRecordException"/>
/// </summary>
/// <param name="targetType">The target type</param>
/// <param name="criterias">The criteria expression</param>
/// <returns>A <c>targetType</c> instance or <c>null</c></returns>
public static T FindOne<T>(params ICriterion[] criterias) where T : class
{
IList<T> result = SlicedFindAll<T>(0, 2, criterias);
if (result.Count > 1)
{
throw new ActiveRecordException(typeof(T).Name + ".FindOne returned " + result.Count + " rows. Expecting one or none");
}
return (result == null || result.Count == 0) ? default(T) : result[0];
}
/**//// <summary>
/// 通过聚合函数查找
/// </summary>
/// <typeparam name="T">实体类类型</typeparam>
/// <typeparam name="M">返回值类型</typeparam>
/// <param name="aggregate"></param>
/// <param name="propertyName">属性名</param>
/// <returns></returns>
public static M FindByAggregate<T, M>(AggregateEnum aggregate, string propertyName) where T : class
{
Type type = typeof(T);
SessionObject sessionObj = NHibernateDatabaseFactory.CreateSession();
ISession session = sessionObj.Session;
string modelClass = typeof(T).Name;
string query = string.Empty;
if (aggregate == AggregateEnum.CountDistinct)
{
query = string.Format("select Count(distinct model.{0}) from {1} as model", propertyName, modelClass);
}
else
{
query = string.Format("select {0}(model.{1}) from {2} as model", aggregate, propertyName, modelClass);
}
try
{
IQuery q = session.CreateQuery(query);
return (M)q.UniqueResult();
}
catch (Exception ex)
{
throw new ActiveRecordException("Could not perform FindByAggregate for " + typeof(T).Name, ex);
}
finally
{
if (sessionObj.IsNeedClose)
session.Close();
}
}
//public static object ExecuteQuery(IActiveRecordQuery q)
//{
// Type targetType = q.TargetType;
// EnsureInitialized(targetType);
// ISession session = _holder.CreateSession( targetType );
// try
// {
// return q.Execute(session);
// }
// catch (Exception ex)
// {
// throw new ActiveRecordException("Could not perform Execute for " + targetType.Name, ex);
// }
// finally
// {
// _holder.ReleaseSession(session);
// }
//}
public static void DeleteAll<T>() where T : class
{
SessionObject sessionObj = NHibernateDatabaseFactory.CreateSession();
ISession session = sessionObj.Session;
try
{
session.Delete( String.Format("from {0}", typeof(T).Name) );
session.Flush();
}
catch(Exception ex)
{
throw new ActiveRecordException("Could not perform DeleteAll for " + typeof(T).Name, ex);
}
finally
{
if (sessionObj.IsNeedClose)
session.Close();
}
}
public static void DeleteAll<T>(string where) where T : class
{
SessionObject sessionObj = NHibernateDatabaseFactory.CreateSession();
ISession session = sessionObj.Session;
try
{
session.Delete(String.Format("from {0} where {1}", typeof(T).Name, where));
session.Flush();
}
catch(Exception ex)
{
throw new ActiveRecordException("Could not perform DeleteAll for " + typeof(T).Name, ex);
}
finally
{
if (sessionObj.IsNeedClose)
session.Close();
}
}
//public static int DeleteAll(Type targetType, IEnumerable pkValues)
//{
// if (pkValues == null)
// return 0;
// int c = 0;
// foreach (int pk in pkValues)
// {
// Object obj = FindByPrimaryKey(targetType, pk, false);
// if (obj != null)
// {
// ActiveRecord arBase = obj as ActiveRecord;
// if (arBase != null)
// arBase.Delete(); // in order to allow override of the virtual "Delete()" method
// else
// ActiveRecord.Delete(obj);
// c++;
// }
// }
// return c;
//}
/**//// <summary>
/// Creates (Saves) a new instance to the database.
/// </summary>
/// <param name="instance"></param>
public static void Create(object instance)
{
if (instance == null) throw new ArgumentNullException("instance");
SessionObject sessionObj = NHibernateDatabaseFactory.CreateSession();
ISession session = sessionObj.Session;
try
{
session.Save(instance);
session.Flush();
}
catch (Exception ex)
{
throw new ActiveRecordException("Could not perform Save for " + instance.GetType().Name, ex);
}
finally
{
if (sessionObj.IsNeedClose)
session.Close();
}
}
/**//// <summary>
/// Saves the instance to the database
/// </summary>
/// <param name="instance"></param>
public static void SaveOrUpdate(object instance)
{
if (instance == null) throw new ArgumentNullException("instance");
SessionObject sessionObj = NHibernateDatabaseFactory.CreateSession();
ISession session = sessionObj.Session;
try
{
session.SaveOrUpdate(instance);
session.Flush();
}
catch(Exception ex)
{
throw new ActiveRecordException("Could not perform Save for " + instance.GetType().Name, ex);
}
finally
{
if (sessionObj.IsNeedClose)
session.Close();
}
}
/**//// <summary>
/// Persists the modification on the instance
/// state to the database.
/// </summary>
/// <param name="instance"></param>
public static void Update(object instance)
{
if (instance == null) throw new ArgumentNullException("instance");
SessionObject sessionObj = NHibernateDatabaseFactory.CreateSession();
ISession session = sessionObj.Session;
try
{
session.Update(instance);
session.Flush();
}
catch(Exception ex)
{
throw new ActiveRecordException("Could not perform Save for " + instance.GetType().Name, ex);
}
finally
{
if (sessionObj.IsNeedClose)
session.Close();
}
}
/**//// <summary>
/// Deletes the instance from the database.
/// </summary>
/// <param name="instance"></param>
public static void Delete(object instance)
{
if (instance == null) throw new ArgumentNullException("instance");
SessionObject sessionObj = NHibernateDatabaseFactory.CreateSession();
ISession session = sessionObj.Session;
try
{
session.Delete(instance);
session.Flush();
}
catch(Exception ex)
{
throw new ActiveRecordException("Could not perform Delete for " + instance.GetType().Name, ex);
}
finally
{
if (sessionObj.IsNeedClose)
session.Close();
}
}
#endregion
}
}
为了解决 ActiveRecordBase 带来的便利性的问题,但又不破解层的职责分配问题,我们同样需要一个基类。一般的三层架构我都会以如下形式分配,
从项目名称上基本就可以了解到是干什么用的了,现在我们在 IDAL 中添加一个基接口 IDalBase<T> ,其中 T 为对应的实体类类型,接口中包含了一些常用的增删改查(GRUD)操作,如果你的项目没有接口层刚此步省略。
using System;
using System.Collections.Generic;
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();
}
}
接着是在 DAL 层中创建一个实现了 IDalBase<T> 接口的基类 DalObjectBase<T>,不仅要感叹 .NET 2.0 来的真是时候,如果没有泛型,这一切我们将很难实现。
using System;
using System.Collections.Generic;
using Yyw.DBUtility.NH.ActiveRecords;
namespace Yyw.NHibernateDAL
{
/**//// <summary>
/// 单个实体类增删改查功能(CRUD)
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class DalObjectBase<T> : Yyw.IDAL.IDalBase<T> where T : class
{
Constructors#region Constructors
public DalObjectBase()
{
}
#endregion
Methods#region Methods
public virtual T FindByPrimaryKey(object id)
{
return ActiveRecord.FindByPrimaryKey<T>(id);
}
public virtual IList<T> FindAll()
{
return ActiveRecord.FindAll<T>();
}
public virtual void Create(object instance)
{
ActiveRecord.Create(instance);
}
public virtual void SaveOrUpdate(object instance)
{
ActiveRecord.SaveOrUpdate(instance);
}
public virtual void Update(object instance)
{
ActiveRecord.Update(instance);
}
public virtual void Delete(object instance)
{
ActiveRecord.Delete(instance);
}
public virtual void DeleteAll()
{
ActiveRecord.DeleteAll<T>();
}
#endregion
}
}
项目下载
在希望能给大家带来帮助的同时也希望能收到更多指导性的意见,共同交流,共同进步。
明天可以睡懒觉了,祝大家周末愉快 :)