利用UnitOfWork和Repository模式-下(转)
http://www.cnblogs.com/huyq2002/archive/2011/08/18/2144208.html
在上一篇文章中,我们利用Entity framework来针对UnitOfWork,Repository设计模式做了一个非常简单的实现。但是这个实现有很多问题。我们现在逐步解决问题,形成一个具有好的扩展性的方案。
新的类图(不包含两个Repository,我们暂时不考虑Repository的问题):
现在解释一下新的设计中各个类的职责:
名称 |
类型 |
描述 |
IUnitOfWork |
接口 |
UnitOfWork模式的核心接口,客户端代码将与IUnitOfWork打交道,主要调用Commit方法,例如以下代码: public CustomerInfo Insert(CustomerInfo customer) { using (IUnitOfWork unitOfWork = UnitOfWork.Start()) { ICustomerRepository customerRepository =... var result = customerRepository.Save(...); unitOfWork.Commit(); return result; } }
|
IUnitOfWorkFactory |
接口 |
UnitOfWorkFactory类的接口 |
UnitOfWorkFactory |
类 |
IUnitOfWorkFactory接口的具体实现。工厂类,将通过UnitOfWorkImplementor实例化接口IUnitOfWork。
|
UnitOfWorkImplementor |
类 |
IUnitOfWork接口的实现,主要逻辑在此类中。 |
TestEntities |
类 |
Entity framework自动生成的object context类。 |
IGenericTransaction |
接口 |
用于控制事务的接口,与IUnitOfWork本身的逻辑分离 |
GenericTransaction |
类 |
IGenericTransaction接口的具体实现,封装了object context的事务的功能。针对其他类型的事务需求可以重新写此段代码。 |
UnitOfWork |
|
UnitOfWork类是一个静态类。用于UnitOfWorkFactory类在客户端的代理,它基本复制了工厂类里面的逻辑。客户端可以避免与工厂类直接打交道。(这只是设计上的一个考虑,这样使客户端代码非常简洁,不需要与工厂类打交道) |
我们列出上一个例子我们的问题和现在这个架构的解决方案:
问题:
1) (1)UnitOfWork依赖于一个具体的object context对象的实例化
解决方案:引入UnitOfWorkFactory工厂接口和UnitOfWorkFactory工厂,将object context或者session对象的实例化逻辑放在工厂对象里面。工厂类同时负责实例化和管理IUnitOfWork 实例。我们需要工厂类来协助实现控制反转。
(关于控制反转,请参考下面的链接:
http://www.cnblogs.com/huyq2002/archive/2011/05/20/2051782.html )
1) (2)UnitOfWork对事务的实现应该和UnitOfWork的代码应该分离。
解决方案:引入IGenericTransaction接口和GenericTransaction类,专门负责事务的控制,主要是设计上遵循单一职责。
(3)UnitOfWork过于简单,需要扩展去支持更多的数据操作
2) 解决方案:扩充IUnitOfWork接口。
3) (4)UnitOfWork实现和EF的object context耦合太紧
解决方案:可以考虑将ObjectContext和Session提取公共接口,IUnitOfWork只该接口打交道。这个设计暂时不解决这个问题。
也许有细心的读者会问到一个问题,IUnitOfWOrk如何传递到Repository里面去呢?我们会增加一个Repository基类。在这个类的构造函数中引入IUnitOfWOrk,如下:
public Repository(): this (UnitOfWork.UnitOfWorkFactory.GetUnitOfWork()) { this .currentUnitOfWork.IncrementRefCount(); } public Repository(Entities::IUnitOfWork currentUnitOfWork) { this .currentUnitOfWork = currentUnitOfWork; } ~Repository() { this .Dispose( true ); } |
读者可以发现在上面的代码里面我们用了常用的Dispose设计模式。
下面我们就新的架构里面的类型做具体介绍。
IUnitOfWork 接口
上面我们提到的问题之一就是要扩展我们的IUnitOfWork接口,使它支持泛型的domain object,并且对读取,分页读取,批量插入、删除的支持。
接口定义如下:
修改后的接口包含三个部分:
与事务(Transaction)有关的方法:
bool HasOpenTransaction { get; }
bool AutoCloseSession { get; set; }
string ConnectionString { get; }
void Commit(global::System.Data.IsolationLevel isolationLevel = global::System.Data.IsolationLevel.ReadCommitted);
IGenericTransaction BeginTransaction(global::System.Data.IsolationLevel isolationLevel = global::System.Data.IsolationLevel.ReadCommitted);
void IncrementRefCount();
void DecrementRefCount();
我们对其中的主要属性或者方法做出解释:
(1) HasOpenTransaction
此标志位标明当前session的事务是否是活动状态,主要用于下面的BulkInsert方法(仅对Nhibernate有效,EF中不存在session对象),因为批量插入需要当前不存在活动的事务
(2) AutoCloseSession
此标志位标识当前的Session是否需要自动关闭,如果设置为True的适合,我们的UnitOfWork(对于EF)要调用objectContext.Connection.Close和objectContext.Dispose方法,(对于NHibernate)要调用session.Close()和session.Dispose()方法。主要目的就是释放数据库连接。
(3) BeginTransaction
启动新的数据库事务。对于EF和N-hibernate分别调用ObjectContext.Connection.BeginTransaction方法或者session.BeginTransaction方法。
(4) IncrememtRefCount,DecrementRefCount
因为IUnitOfWOrk的实例会被所有参与到这个事务的Repository所共享和引用。当IUnitOfWork设定为AutoCloseSession的时候必须判断当前IUnitOfWoek所有的引用Repository实例是否被正常释放(都调用了Dispose方法),读者可以预见性的判断每个Repository的Dispose的方法一定会调用IUnitOfWork的DecrementRefCount方法,当IUnitOfWork实例里面没有任何活动的repository实例的引用,那么IUnitOfWOrk的生命也就终结了(同时它必须是AutoCloseSession))
IncrememtRefCount会被Repository的Constructor调用。DecrementRefCount会被Repository的Dispose调用。
上面的设计主要用于AutoClose模式,如果客户端调用方式都是手工关闭,那么AutoCloseSession, IncrememtRefCount, DecrementRefCount都可以完全从设计中去掉。
与单个实体对象有关的操作和方法主要有:
TEntity GetByKey<TEntity>(object key);
TEntity Save<TEntity>(TEntity entity) where TEntity : class;
TEntity Update<TEntity>(TEntity entity) where TEntity : class;
void Delete<TEntity>(TEntity entity) where TEntity : class;
void Attach<TEntity>(TEntity entity) where TEntity : class;
void Detach<TEntity>(TEntity entity) where TEntity : class;
bool IsAttached<TEntity>(TEntity entity);
void Refresh<TEntity>(TEntity entity);
与多个实体对象有关的操作和方法主要有:
IQueryable<TEntity> GetAll<TEntity>(int maxNumberOfResults = -1) where TEntity : class;
IQueryable<TEntity>GetByQuery<TEntity>(global::System.Linq.Expressions.Expression<global::System.Func<TEntity, bool>> predicate) where TEntity : class;
void BulkInsert<TEntity>(global::System.Collections.Generic.IEnumerable<TEntity> list) where TEntity : class;
void DeleteAll<TEntity>();
void BulkDelete<TEntity>(string query);
void Clear();
上面的方法都是与EF中的objectContext或者N-hibernate中的session对象的方法大体对应(多个对象操作也是基于单个对象的操作组合),不再解释。读者可以参考Entity Framework中的System.Data.Objects.ObjectContext类的定义。
为了解决上个例子中的IUnitOfWork中的实现对EF中的ObjectContext的紧耦合问题,我们引入一个新的类UnitOfWorkImplementor,它实现了IUnitOfWork接口并且隔离客户端代码(service端的代码)对具体的ObjectContext和session等对象的直接实例化和引用。
下面我们针对entity framework来实现新的类UnitOfWorkImplementor
在介绍这个类之前我们先引入一个工厂对象UnitOfWorkFactory和工厂接口IUnitOfWorkFactory。
IUnitOfWorkFactory
接口定义如下:
UnitOfWorkFactory
代码实现如下:
我们主要分析UnitOfWorkImplementor的核心代码。
变量定义:
private readonly IUnitOfWorkFactory factory = null;
private ModelEntities objectContext = null;
private int refCount = 0;
private bool autoCloseSession = true;
private bool isDisposed = false;
private Collections::Dictionary<global::System.Type, Objects::ObjectQuery> objectSets =
new Collections::Dictionary<global::System.Type, Objects::ObjectQuery>();
其中objectSets主要是对ObjectContext里面的objectSet做了一个字典。
private Objects::ObjectSet<TEntity> GetObjectSet<TEntity>() where TEntity : class
{
if (false == this.objectSets.ContainsKey(typeof(TEntity)))
{
this.objectSets.Add(typeof(TEntity), this.ObjectContext.CreateObjectSet<TEntity>());
}
return this.objectSets[typeof(TEntity)] as Objects::ObjectSet<TEntity>;
}
private IQueryable<TEntity> GetQueryable<TEntity>() where TEntity : class
{
Objects::ObjectQuery<TEntity> query = this.GetObjectSet<TEntity>();
if (null != this.ObjectContext.IncludesList)
{
foreach (Collections::List<string> include in this.ObjectContext.IncludesList.Where(i => i[0] == typeof(TEntity).Name))
{
query = ((Objects::ObjectSet<TEntity>)query).Include(include[1]);
}
}
return query.AsQueryable<TEntity>();
}
上面的代码主要实现可以参考EF中的自动生成的objectContext代码。如果不熟悉EF可以略过,我们在此主要讨论UnitOfWork的架构设计。
我们再看一下批量删除的具体实现,其他的有关Entity操作的代码类似不再列出。
public void BulkDelete<TEntity>(string query)
{
this.ObjectContext.ExecuteStoreCommand(string.Format("DELETE FROM {0} where {1}", typeof(TEntity).Name, query));
}
而且从UnitOfWorkImplementor代码中又看到Dispose模式的运用。
Repository的问题:
我们设计的Repository最好能支持泛型的domain object,将基本实现(CRUD)放在基类中以便具体的repository继承和扩展。
IRepository基本类似于IUnitOfWOrk,在此不做详细说明。
我们看一下Repository类,它是所有的Repository的基类。实现了Repository的绝大部分功能。Repository的功能主要是通过调用UnitOfWork里面的objectContext来实现。
那么如果系统需要增加新的具体Repository,代码将很简单。
public interface ICustomerRepository : IRepository<Customer>
{
Customer Get(global::System.Int32 id);
}
public partial class CustomerRepository : CustomerRepositoryBase
{
#region Constructors
public CustomerRepository()
: base()
{
}
public CustomerRepository(Entities::IUnitOfWork unitOfWork)
: base(unitOfWork)
{
}
#endregion
}
public abstract class CustomerRepositoryBase : Repository<Entities::Customer>, Entities::ICustomerRepository
{
#region Constructors
public CustomerRepositoryBase()
: base()
{
}
public CustomerRepositoryBase(Entities::IUnitOfWork unitOfWork)
: base(unitOfWork)
{
}
#endregion
#region Get Method
public virtual Entities::Customer Get(global::System.Int32 id)
{
return GetByQuery(p => p.Id == id).SingleOrDefault();
}
#endregion
#region CustomMethods
#endregion
}
OrderService的代码为:
我们看看测试结果:
测试用例:
[TestClass()]
public partial class OrderServiceTest
{
/// <summary>
/// Primary Test
/// </summary>
[TestMethod()]
public void CreateNewOrderTest()
{
OrderService target = new OrderService();
OrderModel.DataContracts.OrderInfo order = new DataContracts.OrderInfo();
order.Customer = new DataContracts.CustomerInfo { Name = "customer 123", Address = "N/A" };
order.Price = 111;
order.Quantity = 222;
target.CreateNewOrder(order);
OrderModel.DataContracts.OrderInfo order2 = new DataContracts.OrderInfo();
order2.Customer = new DataContracts.CustomerInfo { Name = "customer 123", Address = "N/A" };
order.Price = 333;
order.Quantity = 444;
target.CreateNewOrder(order2);
}
}
执行结果:
两次Order生成操作只产生一条新的Customer,因为第二次Order生成的时候Customer 姓名已经存在了。
结论:
有读者也许感觉这样太繁琐了,用EF自己的实现不是也很快就搞定了吗,要这么复杂吗?
如果仔细领悟UnitOfWork和Repository模式的精髓,我们可以发现,这个新的架构有以下优点:
(1)代码耦合度小,很容易扩展到N-hibernate
(2)可以扩展到非数据库操作,比如事务中包含删除文件等等,只要增加新的Repository.(回滚就需要另外考虑了)
(3)文章结尾附有整个针对EF4和N-hibernate的完全实现。读者可以进一步扩展,解除对ObjectContext和Session的具体实例的依赖。
(4) 客户端代码只与UnitOfWork,Repository打交道,完全不需要了解具体持久层的实现。Repository只需要暴露应该的方法给客户端。实现业务层和数据层的隔离。
(5) Repository的代码重用性很高,增加新的Repository很简单。
(6) 当前的UnitOfWork和Repository模式没有实现跨数据库的方案。读者可以自行扩充。
(7) 要运行示例代码,需要执行数据库脚本,同时修改OrderModelConnectionSettingsBase中的ConnectionString和hibernate.cfg.xml。
UnitOfWork 在EF4上的实现。
https://files.cnblogs.com/huyq2002/UnitOfWork4EF.zip
DB Script:
UnitOfWork 在N-hibernate上的实现。(未经过测试)
https://files.cnblogs.com/huyq2002/UnitOfWork4NH.zip
P.S. 有时间我可能会出集成性更好的和跨数据库的UnitOfWork,同时我会基于这个版本测试ADO.NET和DataDirect做为底层Data Provider的性能,但是有待于DataDirect给我的回复(DataDirect的技术支持已经确认就有关我的问题将进行回复),确定DataDirect到底是否支持SQL Server上的EF和N-hibernat.
下面一章就现在这个例子我们会引入分区表的问题来比较大数据库的性能问题。