在上一篇文章中,我们利用Entity framework来针对UnitOfWork,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的事务的功能。针对其他类型的事务需求可以重新写此段代码。 |
|
|
|
我们列出上一个例子我们的问题和现在这个架构的解决方案:
问题:
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,并且对读取,分页读取,批量插入、删除的支持。
接口定义如下:
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.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类的定义。
在介绍这个类之前我们先引入一个工厂对象UnitOfWorkFactory和工厂接口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
代码实现如下:
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的核心代码。
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继承和扩展。
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来实现。
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的代码为:
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:
/****** 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.
下面一章就现在这个例子我们会引入分区表的问题来比较大数据库的性能问题。