在上一篇文章中,我们利用Entity framework来针对UnitOfWorkRepository设计模式做了一个非常简单的实现。但是这个实现有很多问题。我们现在逐步解决问题,形成一个具有好的扩展性的方案。

 新的类图(不包含两个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实现和EFobject context耦合太紧

解决方案:可以考虑将ObjectContextSession提取公共接口,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,并且对读取,分页读取,批量插入、删除的支持。

 

接口定义如下:  

IUnitOfWork
public interface IUnitOfWork : global::System.IDisposable
{
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();

TEntity GetByKey
<TEntity>(object key) where TEntity : class;

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;

global::System.Collections.Generic.IEnumerable<TEntity> GetByExample<TEntity>(TEntity exampleObject, params string[] excludePropertyList) where TEntity : class;

int Count<TEntity>() where TEntity : class;

int Count<TEntity>(global::System.Linq.Expressions.Expression<global::System.Func<TEntity, bool>> predicate) where TEntity : class;

TEntity Save
<TEntity>(TEntity entity) where TEntity : class;

void BulkInsert<TEntity>(global::System.Collections.Generic.IEnumerable<TEntity> list) where TEntity : class;

TEntity Update
<TEntity>(TEntity entity) where TEntity : class;

void Delete<TEntity>(TEntity entity) where TEntity : class;

void DeleteAll<TEntity>();

void BulkDelete<TEntity>(string query);

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);

void Clear();
}


  修改后的接口包含三个部分:

 与事务(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.CloseobjectContext.Dispose方法,(对于NHibernate)要调用session.Close()session.Dispose()方法。主要目的就是释放数据库连接。

(3) BeginTransaction

    启动新的数据库事务。对于EFN-hibernate分别调用ObjectContext.Connection.BeginTransaction方法或者session.BeginTransaction方法。


(4) IncrememtRefCount,DecrementRefCount

    因为IUnitOfWOrk的实例会被所有参与到这个事务的Repository所共享和引用。当IUnitOfWork设定为AutoCloseSession的时候必须判断当前IUnitOfWoek所有的引用Repository实例是否被正常释放(都调用了Dispose方法),读者可以预见性的判断每个RepositoryDispose的方法一定会调用IUnitOfWorkDecrementRefCount方法,IUnitOfWork实例里面没有任何活动的repository实例的引用,那么IUnitOfWOrk的生命也就终结了(同时它必须是AutoCloseSession))

    IncrememtRefCount会被RepositoryConstructor调用。DecrementRefCount会被RepositoryDispose调用。

上面的设计主要用于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端的代码)对具体的ObjectContextsession等对象的直接实例化和引用。

 下面我们针对entity framework来实现新的类UnitOfWorkImplementor

 在介绍这个类之前我们先引入一个工厂对象UnitOfWorkFactory和工厂接口IUnitOfWorkFactory

 IUnitOfWorkFactory

 接口定义如下: 

 

IUnitOfWorkFactory
public interface IUnitOfWorkFactory
{
ModelEntities CreateObjectContext();
Entities::IUnitOfWork GetUnitOfWork();
Entities::IUnitOfWork Create(
global::System.Data.IsolationLevel isolationLevel);
Entities::IUnitOfWork CreateNew();
bool IsStartedUnitOfWork();
void DisposeCurrentUnitOfWork();
}

 UnitOfWorkFactory

 代码实现如下: 

UnitOfWorkFactory
    using Entities = OrderModel.Entities;
using ServiceModel = System.ServiceModel;

public class UnitOfWorkFactory : IUnitOfWorkFactory
{
#region Private Variables

private const string CurrentUnitOfWorkKey = "CurrentUnitOfWork.Key";
private bool isDisposed = false;

#endregion

#region Constructors & Destructors

internal UnitOfWorkFactory()
{
}

#endregion

#region Private Properties

private Entities::IUnitOfWork CurrentUnitOfWork
{
get
{
if (true == this.IsWebContext)
{
WCFSessionContext current
= ServiceModel::OperationContext.Current.InstanceContext.Extensions.Find<WCFSessionContext>();
if (null != current)
{
return current.UnitOfWork;
}
else
{
return null;
}
}
else
{
return (Entities::IUnitOfWork)global::System.Runtime.Remoting.Messaging.CallContext.GetData(CurrentUnitOfWorkKey);
}
}

set
{
if (true == this.IsWebContext)
{
WCFSessionContext sessionContext
= new WCFSessionContext(value);
ServiceModel::OperationContext.Current.InstanceContext.Extensions.Add(sessionContext);
ServiceModel::OperationContext.Current.InstanceContext.Closing
+= new global::System.EventHandler((sender, e) => this.CloseUnitOfWork());
}
else
{
global::System.Runtime.Remoting.Messaging.CallContext.SetData(CurrentUnitOfWorkKey, value);
}
}
}

private bool IsWebContext
{
get { return ServiceModel::OperationContext.Current != null; }
}

#endregion

#region Public Methods

public Entities::IUnitOfWork Create(global::System.Data.IsolationLevel isolationLevel)
{
this.CurrentUnitOfWork = new UnitOfWorkImplementor(this);
this.CurrentUnitOfWork.AutoCloseSession = false;
return this.CurrentUnitOfWork;
}

public Entities::IUnitOfWork CreateNew()
{
return new UnitOfWorkImplementor(this);
}

public Entities::IUnitOfWork GetUnitOfWork()
{
if (null == this.CurrentUnitOfWork)
{
this.CurrentUnitOfWork = new UnitOfWorkImplementor(this);
}

return this.CurrentUnitOfWork;
}

public void DisposeCurrentUnitOfWork()
{
this.CurrentUnitOfWork = null;
}

public ModelEntities CreateObjectContext()
{
return new ModelEntities();
}

public bool IsStartedUnitOfWork()
{
return null != this.CurrentUnitOfWork;
}

public void Dispose()
{
this.Dispose(false);
}

#endregion

#region Private Methods

private void CloseUnitOfWork()
{
WCFSessionContext sessionContext
= ServiceModel::OperationContext.Current.InstanceContext.Extensions.Find<WCFSessionContext>();

if (null != sessionContext && null != sessionContext.UnitOfWork)
{
if (false == sessionContext.Fault)
{
sessionContext.UnitOfWork.Commit();
}

ServiceModel::OperationContext.Current.InstanceContext.Extensions.Remove(sessionContext);
}
}

private void Dispose(bool finalizing)
{
if (false == this.isDisposed)
{
// Flag as disposed.
this.isDisposed = true;
if (false == finalizing)
{
global::System.GC.SuppressFinalize(this);
}
}
}

#endregion

#region Nested Types

public class WCFSessionContext : ServiceModel::IExtension<ServiceModel::InstanceContext>
{

public string ID { get; private set; }

public Entities::IUnitOfWork UnitOfWork { get; private set; }
public bool Fault { get; set; }

public WCFSessionContext(Entities::IUnitOfWork unitOfWork)
{
this.UnitOfWork = unitOfWork;
this.Fault = false;
this.ID = global::System.DateTime.Now.ToString("yyyyMMMdd-HHmmssffff");
}

#region IExtension<InstanceContext> Members

public void Attach(ServiceModel::InstanceContext owner)
{
}

public void Detach(ServiceModel::InstanceContext owner)
{
}

#endregion
}

#endregion
}

 我们主要分析UnitOfWorkImplementor的核心代码。

UnitOfWorkImplementor
   using System.Linq;
using Collections = System.Collections.Generic;
using Data = System.Data;
using Edm = System.Data.Metadata.Edm;
using Entities = OrderModel.Entities;
using Expressions = System.Linq.Expressions;
using Objects = System.Data.Objects;

public class UnitOfWorkImplementor : Entities::IUnitOfWork
{
#region Private Variables

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>();
#endregion

#region Constructors & Destructor

public UnitOfWorkImplementor(IUnitOfWorkFactory factory)
{
this.factory = factory;
this.objectContext = factory.CreateObjectContext();
}

#endregion

#region Public Properties

public IUnitOfWorkFactory Factory
{
get { return this.factory; }
}

public ModelEntities ObjectContext
{
get
{
if (Data::ConnectionState.Closed == this.objectContext.Connection.State)
{
this.objectContext.Connection.Open();
}

return this.objectContext;
}
}

public string ConnectionString
{
get { return this.ObjectContext.Connection.ConnectionString; }
}

public bool HasOpenTransaction
{
get
{
throw new global::System.InvalidOperationException("EntityFramework does not support 'HasOpenTransaction' property ");
}
}

public bool AutoCloseSession
{
get
{
return this.autoCloseSession;
}

set
{
this.autoCloseSession = value;
if (0 == this.refCount && true == this.autoCloseSession)
{
this.Dispose();
}
}
}

#endregion

#region Public Methods

#region Transaction & Seesion Methods

public Entities::IGenericTransaction BeginTransaction(Data::IsolationLevel isolationLevel = Data::IsolationLevel.ReadCommitted)
{
return new GenericTransaction(this.ObjectContext.Connection.BeginTransaction(isolationLevel), this.ObjectContext);
}

public void Commit(global::System.Data.IsolationLevel isolationLevel = global::System.Data.IsolationLevel.ReadCommitted)
{
Entities::IGenericTransaction transaction
= this.BeginTransaction(isolationLevel);

try
{
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
finally
{
transaction.Dispose();
}
}

public void IncrementRefCount()
{
this.refCount++;
}

public void DecrementRefCount()
{
this.refCount--;
if (0 == this.refCount && this.AutoCloseSession)
{
this.Dispose();
}
}

#endregion

#region Query Methods

public TEntity GetByKey<TEntity>(object key) where TEntity : class
{
var xParam
= Expressions::Expression.Parameter(typeof(TEntity), typeof(TEntity).Name);
Expressions::MemberExpression leftExpr
= Expressions::MemberExpression.Property(xParam, this.GetFirstPrimaryKey(typeof(TEntity).Name));
Expressions::Expression rightExpr
= Expressions::Expression.Constant(key);
Expressions::BinaryExpression binaryExpr
= Expressions::MemberExpression.Equal(leftExpr, rightExpr);
Expressions::Expression
<global::System.Func<TEntity, bool>> lambdaExpr =
Expressions::Expression.Lambda
<global::System.Func<TEntity, bool>>(binaryExpr, new Expressions::ParameterExpression[] { xParam });
Collections::IList
<TEntity> resultCollection = GetByQuery<TEntity>(lambdaExpr).ToList();
return resultCollection.First<TEntity>();
}

public IQueryable<TEntity> GetAll<TEntity>(int maxNumberOfResults = -1) where TEntity : class
{
if (-1 == maxNumberOfResults)
{
return this.GetQueryable<TEntity>();
}

return this.GetQueryable<TEntity>().Take(maxNumberOfResults);
}

public IQueryable<TEntity> GetByQuery<TEntity>(global::System.Linq.Expressions.Expression<global::System.Func<TEntity, bool>> predicate) where TEntity : class
{
return this.GetQueryable<TEntity>().Where(predicate);
}

public Collections::IEnumerable<TEntity> GetByExample<TEntity>(TEntity exampleObject, params string[] excludePropertyList) where TEntity : class
{
IQueryable
<TEntity> queryExample = this.GetQueryable<TEntity>().Cast<TEntity>();

Edm::EntityType entityType
= this.ObjectContext.MetadataWorkspace.GetItemCollection(Edm::DataSpace.CSpace).GetItems<Edm::EntityType>().FirstOrDefault(E => E.Name == typeof(TEntity).Name);

foreach (Edm::EdmProperty property in entityType.Properties)
{
object value = typeof(TEntity).GetProperty(property.Name).GetValue(exampleObject, null);
if (null != value)
{
var xParam
= Expressions::Expression.Parameter(typeof(TEntity), typeof(TEntity).Name);
Expressions::MemberExpression leftExpr
= Expressions::MemberExpression.Property(xParam, property.Name);
Expressions::Expression rightExpr
= Expressions::Expression.Constant(value);
Expressions::BinaryExpression binaryExpr
= Expressions::MemberExpression.Equal(leftExpr, rightExpr);
Expressions::Expression
<global::System.Func<TEntity, bool>> lambdaExpr =
Expressions::Expression.Lambda
<global::System.Func<TEntity, bool>>(binaryExpr, new Expressions::ParameterExpression[] { xParam });
queryExample
= queryExample.Where(lambdaExpr);
}
}

return queryExample.AsEnumerable<TEntity>();
}

public int Count<TEntity>() where TEntity : class
{
return this.GetQueryable<TEntity>().Count();
}

public int Count<TEntity>(global::System.Linq.Expressions.Expression<global::System.Func<TEntity, bool>> predicate) where TEntity : class
{
return this.GetQueryable<TEntity>().Count(predicate);
}

public bool IsAttached<TEntity>(TEntity entity)
{
Objects::ObjectStateEntry stateEntry
= null;
return this.ObjectContext.ObjectStateManager.TryGetObjectStateEntry(entity, out stateEntry);
}

#endregion

#region Commands

public TEntity Save<TEntity>(TEntity entity) where TEntity : class
{
this.GetObjectSet<TEntity>().AddObject(entity);
return entity;
}

public void BulkInsert<TEntity>(Collections::IEnumerable<TEntity> list) where TEntity : class
{
if (true == this.HasOpenTransaction)
{
throw new global::System.Exception("You can not make bulk insert within transaction");
}

if (Data::ConnectionState.Closed == this.ObjectContext.Connection.State)
{
this.ObjectContext.Connection.Open();
}

using (Data::IDbTransaction transaction = this.ObjectContext.Connection.BeginTransaction(Data::IsolationLevel.ReadCommitted))
{
foreach (TEntity entity in list)
{
this.Save<TEntity>(entity);
}

try
{
this.ObjectContext.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}

public TEntity Update<TEntity>(TEntity entity) where TEntity : class
{
if (true == this.IsAttached(entity))
{
return entity;
}

Data::EntityKey entitykey
= this.ObjectContext.CreateEntityKey(typeof(TEntity).Name, entity);
object original = null;
if (this.ObjectContext.TryGetObjectByKey(entitykey, out original))
{
this.ObjectContext.ApplyCurrentValues<TEntity>(entitykey.EntitySetName, entity);
}

return (TEntity)original;
}

public void Delete<TEntity>(TEntity entity) where TEntity : class
{
if (true == this.IsAttached(entity))
{
this.GetObjectSet<TEntity>().DeleteObject(entity);
}
else
{
Data::EntityKey entitykey
= this.ObjectContext.CreateEntityKey(typeof(TEntity).Name, entity);
object original = null;
if (this.ObjectContext.TryGetObjectByKey(entitykey, out original))
{
this.GetObjectSet<TEntity>().DeleteObject((TEntity)original);
}
}
}

public void DeleteAll<TEntity>()
{
this.ObjectContext.ExecuteStoreCommand(string.Format("DELETE FROM {0}", typeof(TEntity).Name));
}

public void BulkDelete<TEntity>(string query)
{
this.ObjectContext.ExecuteStoreCommand(string.Format("DELETE FROM {0} where {1}", typeof(TEntity).Name, query));
}

public void Attach<TEntity>(TEntity entity) where TEntity : class
{
this.GetObjectSet<TEntity>().Attach(entity);
}

public void Detach<TEntity>(TEntity entity) where TEntity : class
{
this.GetObjectSet<TEntity>().Detach(entity);
}

public void Refresh<TEntity>(TEntity entity)
{
this.ObjectContext.Refresh(Objects::RefreshMode.StoreWins, entity);
}

public void Clear()
{
var objectStateEntries
= this.ObjectContext
.ObjectStateManager
.GetObjectStateEntries(Data::EntityState.Added);

foreach (var objectStateEntry in objectStateEntries.Where(E => null != E.Entity))
{
this.ObjectContext.Detach(objectStateEntry.Entity);
}

objectStateEntries
= this.ObjectContext
.ObjectStateManager
.GetObjectStateEntries(Data::EntityState.Modified);

foreach (var objectStateEntry in objectStateEntries.Where(E => null != E.Entity))
{
this.ObjectContext.Detach(objectStateEntry.Entity);
}

objectStateEntries
= this.ObjectContext
.ObjectStateManager
.GetObjectStateEntries(Data::EntityState.Unchanged);

foreach (var objectStateEntry in objectStateEntries.Where(E => null != E.Entity))
{
this.ObjectContext.Detach(objectStateEntry.Entity);
}
}

#endregion

public void Dispose()
{
this.Dispose(false);
}

#endregion

#region Private Methods

private void Close()
{
if (null == this.objectContext)
{
return;
}

if (System.Data.ConnectionState.Open == this.objectContext.Connection.State)
{
this.objectContext.Connection.Close();
}

this.objectContext.Dispose();
this.objectContext = null;
}

private string GetFirstPrimaryKey(string entityName)
{
Edm::EntityType entityType
= this.ObjectContext.MetadataWorkspace.GetItemCollection(Edm::DataSpace.CSpace).GetItems<Edm::EntityType>().FirstOrDefault(E => E.Name == entityName);
if (null != entityType)
{
Edm::EdmMember member
= entityType.KeyMembers.FirstOrDefault();
if (null != member)
{
return member.Name;
}
}

return string.Empty;
}

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>();
}

private void Dispose(bool finalizing)
{
if (false == this.isDisposed)
{
try
{
this.Close();
}
catch (global::System.ObjectDisposedException)
{
}

if (false == finalizing)
{
global::System.GC.SuppressFinalize(this);
}

if (UnitOfWork.Current == this)
{
this.factory.DisposeCurrentUnitOfWork();
}

this.isDisposed = true;
}
}

#endregion
}

  变量定义:

        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
public interface IRepository<TEntity>
{
IUnitOfWork CurrentUnitOfWork {
get; }

IQueryable
<TEntity> GetAll(int maxNumberOfResults = -1);

IQueryable
<TEntity> GetByQuery(global::System.Linq.Expressions.Expression<global::System.Func<TEntity, bool>> predicate);

global::System.Collections.Generic.IEnumerable<TEntity> GetByExample(TEntity exampleObject, params string[] excludePropertyList);

int Count();

int Count(global::System.Linq.Expressions.Expression<global::System.Func<TEntity, bool>> predicate);

TEntity Save(TEntity entity);

void BulkInsert(global::System.Collections.Generic.IEnumerable<TEntity> list);

TEntity Update(TEntity entity);

void Delete(TEntity entity);

void DeleteAll();

void BulkDelete(string query);

void Attach(TEntity entity);

void Detach(TEntity entity);

void Refresh(TEntity entity);

bool IsAttached(TEntity entity);

void Commit();

void Dispose();
}

 IRepository基本类似于IUnitOfWOrk,在此不做详细说明。

 我们看一下Repository类,它是所有的Repository的基类。实现了Repository的绝大部分功能。Repository的功能主要是通过调用UnitOfWork里面的objectContext来实现。 

Repository
   using System.Linq;
using Entities = OrderModel.Entities;

public abstract class Repository<TEntity> : global::System.IDisposable, Entities::IRepository<TEntity> where TEntity : class
{
#region Private Variables

private Entities::IUnitOfWork currentUnitOfWork = null;
private bool disposed = false;

#endregion

#region Constructors & Destructors

public Repository()
:
this(UnitOfWork.UnitOfWorkFactory.GetUnitOfWork())
{
this.currentUnitOfWork.IncrementRefCount();
}

public Repository(Entities::IUnitOfWork currentUnitOfWork)
{
this.currentUnitOfWork = currentUnitOfWork;
}

~Repository()
{
this.Dispose(true);
}

#endregion

#region Public Properties

public Entities::IUnitOfWork CurrentUnitOfWork
{
get { return this.currentUnitOfWork; }
}

#endregion

#region Public Methods

public virtual IQueryable<TEntity> GetAll(int maxNumberOfResults = -1)
{
return this.currentUnitOfWork.GetAll<TEntity>(maxNumberOfResults);
}

public virtual global::System.Linq.IQueryable<TEntity> GetByQuery(global::System.Linq.Expressions.Expression<global::System.Func<TEntity, bool>> predicate)
{
return this.currentUnitOfWork.GetByQuery<TEntity>(predicate);
}

public virtual global::System.Collections.Generic.IEnumerable<TEntity> GetByExample(TEntity exampleObject, params string[] excludePropertyList)
{
return this.currentUnitOfWork.GetByExample<TEntity>(exampleObject, excludePropertyList);
}

public virtual int Count()
{
return this.currentUnitOfWork.Count<TEntity>();
}

public virtual int Count(global::System.Linq.Expressions.Expression<global::System.Func<TEntity, bool>> predicate)
{
return this.currentUnitOfWork.Count<TEntity>(predicate);
}

public virtual TEntity Save(TEntity entity)
{
return this.currentUnitOfWork.Save<TEntity>(entity);
}

public virtual void BulkInsert(global::System.Collections.Generic.IEnumerable<TEntity> list)
{
this.currentUnitOfWork.BulkInsert<TEntity>(list);
}

public virtual TEntity Update(TEntity entity)
{
return this.currentUnitOfWork.Update<TEntity>(entity);
}

public virtual void Delete(TEntity entity)
{
this.currentUnitOfWork.Delete<TEntity>(entity);
}

public void DeleteAll()
{
this.currentUnitOfWork.DeleteAll<TEntity>();
}

public void BulkDelete(string query)
{
this.currentUnitOfWork.BulkDelete<TEntity>(query);
}

public virtual void Attach(TEntity entity)
{
this.currentUnitOfWork.Attach<TEntity>(entity);
}

public virtual void Detach(TEntity entity)
{
this.currentUnitOfWork.Detach<TEntity>(entity);
}

public virtual bool IsAttached(TEntity entity)
{
return this.currentUnitOfWork.IsAttached<TEntity>(entity);
}

public virtual void Refresh(TEntity entity)
{
this.currentUnitOfWork.Refresh<TEntity>(entity);
}

public virtual void Commit()
{
this.currentUnitOfWork.Commit();
}

public virtual void Dispose()
{
this.Dispose(false);
}

#endregion

#region Protected Methods

protected TEntity GetByKey(object id)
{
return this.currentUnitOfWork.GetByKey<TEntity>(id);
}

#endregion

#region Private Methods

private void Dispose(bool finalizing)
{
if (false == this.disposed)
{
if (UnitOfWork.Current == this.currentUnitOfWork)
{
this.currentUnitOfWork.DecrementRefCount();
}

if (false == finalizing)
{
global::System.GC.SuppressFinalize(this);
}

this.disposed = true;
}
}

#endregion
}

 那么如果系统需要增加新的具体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的代码为:

CreateNewOrder
    using System.Linq;
using DataAccess = OrderModel.DataAccess;
using DataContracts = OrderModel.DataContracts;
using Entities = OrderModel.Entities;
using MefHosting = System.ComponentModel.Composition.Hosting;

public partial class OrderService
{
public override void CreateNewOrder(DataContracts.OrderInfo orderInfo)
{
using (Entities::IUnitOfWork unitOfWork = DataAccess::UnitOfWork.Start())
{
Entities::IOrderRepository orderRepository
= this.Container.GetExportedValue<Entities::IOrderRepository>();
Entities::ICustomerRepository customerRepository
= this.Container.GetExportedValue<Entities::ICustomerRepository>();

global::OrderModel.Entities.Order order = DataAccess::OrderTranslator.ToEntity(orderInfo);
// find the customer ,if not exist, then create
var customer = customerRepository.GetByQuery(item => item.Name == orderInfo.Customer.Name).FirstOrDefault();
order.Customer
= customer != null ? customer : DataAccess::CustomerTranslator.ToEntity(orderInfo.Customer);

var result
= DataAccess::OrderTranslator.ToValueObject((Entities::Order)orderRepository.Save(order));
unitOfWork.Commit();
}

}
}

  我们看看测试结果:

   测试用例:

  

[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:

DB Script
/****** Object:  Table [dbo].[Customer]    Script Date: 08/18/2011 15:40:51 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Customer](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[Address] [nvarchar](50) NOT NULL,
CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [Unique_Customer_Id] UNIQUE NONCLUSTERED
(
[Id] ASC
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
)
ON [PRIMARY]
GO
/****** Object: Table [dbo].[Order] Script Date: 08/18/2011 15:40:51 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Order](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Quantity] [int] NOT NULL,
[Price] [decimal](18, 0) NOT NULL,
[Customer_Id] [int] NOT NULL,
CONSTRAINT [PK_Order] PRIMARY KEY CLUSTERED
(
[Id] ASC
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [Unique_Order_Id] UNIQUE NONCLUSTERED
(
[Id] ASC
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
)
ON [PRIMARY]
GO
/****** Object: ForeignKey [Customer_Order] Script Date: 08/18/2011 15:40:51 ******/
ALTER TABLE [dbo].[Order] WITH CHECK ADD CONSTRAINT [Customer_Order] FOREIGN KEY([Customer_Id])
REFERENCES [dbo].[Customer] ([Id])
GO
ALTER TABLE [dbo].[Order] CHECK CONSTRAINT [Customer_Order]
GO

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.

下面一章就现在这个例子我们会引入分区表的问题来比较大数据库的性能问题。

posted on 2011-08-18 14:49  胡以谦  阅读(4072)  评论(11编辑  收藏  举报