EF5中使用UnitOfWork
前言
每次提交数据库都会打开一个连接,造成结果是:多个连接无法共用一个数据库级别的事务,也就无法保证数据的原子性、一致性。
解决办法是:在ObjectContext的CRUD操作基础上再包装一层,提供统一的入口,让服务层调用。
同一个UnitOfWork实例对象下所有的ObjectContext都共同一个数据库上下文对象(ps:EF用的是ObjectContext),也就是共用一个事物。
提交数据库时,只要有一个操作失败,那么所有的操作都被视为失败。
代码托管:https://github.com/catbiscuit/MT
实现过程
实体层
数据访问层
T_Log表示例
IDAL层
直接继承IRepository<T>泛型接口,其中定义了一些基础的方法。
namespace MT.Business.IDAL.SystemManage { public interface IT_LogDAL : IRepository<T_Log> { } }
DAL层
直接继承Repository<T>类,和IT_LogDAL接口。
Repository类,需要提供实体类和数据库上下文。因为需要创建一个统一管理数据库操作的对象。
namespace MT.Business.DAL.SystemManage { public class T_LogDAL : Repository<T_Log, MTEntities>, IT_LogDAL { public T_LogDAL(IDatabaseFactory<MTEntities> iDatabaseFactory) : base(iDatabaseFactory) { } } }
IBLL层
定义基本的数据操作方法。
namespace MT.Business.IBLL.SystemManage { public interface IT_LogBLL { int Insert(T_Log entity); int Delete(T_Log entity); int Update(); int Insert(List<T_Log> lstEntity); T_Log GetModelByCondition(Expression<Func<T_Log, bool>> condition); } }
BLL
对IT_LogBLL接口中方法的实现。
namespace MT.Business.BLL.SystemManage { public class T_LogBLL : IT_LogBLL { private IUnitOfWork<MTEntities> _iUnitOfWork; private IT_LogDAL _iT_LogDAL; public T_LogBLL(IUnitOfWork<MTEntities> iUnitOfWork , IT_LogDAL iT_LogDAL) { this._iUnitOfWork = iUnitOfWork; this._iT_LogDAL = iT_LogDAL; } public int Insert(T_Log entity) { _iT_LogDAL.Insert(entity); return _iUnitOfWork.Commit(); } public int Delete(T_Log entity) { _iT_LogDAL.Delete(entity); return _iUnitOfWork.Commit(); } public int Update() { return _iUnitOfWork.Commit(); } public int Insert(List<T_Log> lstEntity) { foreach (var item in lstEntity) { _iT_LogDAL.Insert(item); } return _iUnitOfWork.Commit(); } public T_Log GetModelByCondition(Expression<Func<T_Log, bool>> condition) { return _iT_LogDAL.GetModelByCondition(condition); } } }
注意
第一:类中定义了一个IUnitOfWork泛型对象,这个就是单元事务的管理对象。
DAL层只是将对数据的操作,添加到一个数据缓存的集合中。
当数据操作的步骤完成时,才调用IUnitOfWork泛型对象进行数据操作的提交。
这样确保了原子性和一致性。
第二:Update操作,BLL并没有将数据的操作添加到数据缓存的集合中。
此处,可以通过单步调试,当执行到UnitOfWork类中时,
监视DbContext对象的值,这个对象会监视数据库上下文中数据的状态。
这个地方,就要求Update操作的对象只能是从数据库获取得来。
使用lambda表达式获取实体。
示例操作:
Model model = DAL.GetModel(2);
model.Name = "修改Name";
BLL.Update();
此时BLL层执行update方法,实际上只是执行了单元事务的提交,也就完成了对该条记录的修改。
但是,如果是实例化的对象是无法修改数据。
Model model = new Model();
model.ID = 2;
model.Name = "修改Name";
BLL.Update();
这样执行更新操作,是无法修改数据的
总结
思路
DAL层,只是将数据的操作(新增修改删除)附加到数据库上下文中。
UnitOfWork,才是将数据进行提交的地方。
所以,BLL层的方法才是实现对数据的完成操作。
使用工作单元的思路
a.DatabaseFactory类的使用。确保一次业务中每一个线程使用的数据库上下文对象是同一个。
Web中每一次HTTP请求,都会开启一个新的线程。
我们要保证在这个线程中,所有的数据库操作都提交到了同一个数据库上下文对象中。
在一次业务操作时,所使用的数据库上下文对象都由DatabaseFactory实例化。
b.DAL层中的Repository类。将一次业务操作中的数据库实体增删改实体附加到从DatabaseFactory中获取到的数据库上下文中。
Repository类的构造函数,就要求传递数DatabaseFactory的对象。
Repository类中使用的数据库上下文对象,从DatabaseFactory获取。
c.BLL层中的UnitOfWork类。将一次业务操作中的数据库上下文操作提交到数据库中。
UnitOfWork类中使用的数据库上下文对象,从DatabaseFactory获取。
调用各个表DAL层对象,将增删改的操作附加到从DatabaseFactory类获取到的数据库上下文对象中。
最终调用UnitOfWork类的Commit();方法,将所有的数据操作提交到数据库中去。
关键点
a.同一个业务操作时,不同表操作时使用的数据库上下文必须是同一个,保证数据提交的完整性。
b.将数据库附加放在DAL层,数据的提交放在BLL层。
关于每一次HTTP请求中数据库上下文对象的获取问题
问题
在网上看到了,每一次HTTP请求都会开启一个新的线程,这句话不理解是什么意思,就直接照着这个思路来。
但是,总感觉有什么问题,于是今天做了测试。
在HomeController控制器中,
在Index方法中,获取实体,修改属性值,但是不调用BLL层,也就是仅附加不提交。
在Add方法中,获取实体,修改属性值,之后调用BLL层,附加且提交。
经过测试,发现了问题
执行了Index方法,F_isValid++; ,数据库中记录的值并不会改变。
但是当执行了Add方法后,F_isValid++;,数据库中记录的值会发生改变,但是F_isValid自增了两次。
有问题,然后继续测试。执行了三次Index方法,再执行一次Add方法,数据库中记录的值自增了四次。
调试发现,自从程序运行后,DatabaseFactory类中的private T _dbcontext; 一直存在。也就是整个程序周期内,这个示例是共享的。
于是有两个问题。
(1)在一个HTTP周期内,使用的数据库上下文对象应该是同一个。
(2)在一个HTTP周期结束后,使用的数据库上下文对象应该被销毁(回收)。
public class HomeController: Controller { private readonly IT_LogBLL _iT_LogBLL; public HomeController(IT_LogBLL iT_LogBLL) { this._iT_LogBLL = iT_LogBLL; } public ActionResult Index() { T_Log model2 = new T_Log(); model2 = _iT_LogBLL.GetModelByCondition(x => x.F_isValid > 0); model2.F_isValid++; //_iT_LogBLL.Update(); return View(); } public ActionResult Add() { T_Log model2 = new T_Log(); model2 = _iT_LogBLL.GetModelByCondition(x => x.F_isValid > 0); model2.F_isValid++; _iT_LogBLL.Update(); return View(); } }
解决
(1)在一个HTTP周期内,使用的数据库上下文对象应该是同一个。---Autofac配置格式
想起Autofac中,有实例生命周期的设置。
地址:https://www.cnblogs.com/masonblog/p/9584299.html
又寻找到配置格式:
builder.RegisterType<OpenIdRelyingPartyService>().As<IOpenIdRelyingPartyService>().InstancePerHttpRequest();
基于线程或者Context上下文的请求,返回一个单例实例,在Controller的一个View页面执行时包含了整个Context上下文处理.
地址:https://www.cnblogs.com/xiaoweinet/archive/2014/01/09/3512255.html
(2)在一个HTTP周期结束后,使用的数据库上下文对象应该被销毁(回收)。---IDisposable回收
参照网上大神的设置,对DatabaseFactory 资源的回收。
地址:https://www.cnblogs.com/tiancai/p/6612444.html
测试
(1)、InstancePerDependency
builder.RegisterGeneric(typeof(DatabaseFactory<>)).As(typeof(IDatabaseFactory<>)).InstancePerDependency();
每次获取DatabaseFactory类都是新的实体,
数据无法添加。
(2)、InstancePerLifetimeScope
builder.RegisterGeneric(typeof(DatabaseFactory<>)).As(typeof(IDatabaseFactory<>)).InstancePerLifetimeScope();
每次获取DatabaseFactory类都是新的实体,
数据无法添加。
(3)、InstancePerMatchingLifetimeScope
builder.RegisterGeneric(typeof(DatabaseFactory<>)).As(typeof(IDatabaseFactory<>)).InstancePerMatchingLifetimeScope();
No
scope with a Tag matching '' is visible from the scope in which the
instance was requested. This generally indicates that a component
registered as per-HTTP request is being requested by a SingleInstance()
component (or a similar scenario.) Under the web integration always
request dependencies from the DependencyResolver.Current or
ILifetimeScopeProvider.RequestLifetime, never from the container itself.
报错,程序无法运行。
(4)、InstancePerOwned
builder.RegisterGeneric(typeof(DatabaseFactory<>)).As(typeof(IDatabaseFactory<>)).InstancePerOwned(typeof(DatabaseFactory<>));
No
scope with a Tag matching 'MT.Data.Base.BaseObject.DatabaseFactory`1'
is visible from the scope in which the instance was requested. This
generally indicates that a component registered as per-HTTP request is
being requested by a SingleInstance() component (or a similar scenario.)
Under the web integration always request dependencies from the
DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime,
never from the container itself.
报错,程序无法运行。
(5)、SingleInstance
builder.RegisterGeneric(typeof(DatabaseFactory<>)).As(typeof(IDatabaseFactory<>)).SingleInstance();
单例模式,执行时得到的都是同一个相同的共享的实例。
一个HTTP请求,将实体附加到数据库上下问中,但是不提交。在下一个HTTP请求中,得到的数据库上下文对象与上一次HTTP请求的数据库上下文对象是同一个。
数据会将之前附加未提交的记录一并提交。
数据添加异常,不能这样操作。
(6)、InstancePerHttpRequest
builder.RegisterGeneric(typeof(DatabaseFactory<>)).As(typeof(IDatabaseFactory<>)).InstancePerRequest();
builder.RegisterType<DatabaseFactory<MTEntities>>().As<IDatabaseFactory<MTEntities>>().InstancePerRequest();
在一次HTTP请求中,获取到的数据库上下文中对象是同一个。
数据正常添加。
最终设置格式
修改或添加的文件:AutofacRegister,DatabaseFactory,Disposable,IDatabaseFactory
namespace MT.Application.Web.App_Code { /// <summary> /// Autofac注册 /// </summary> public class AutofacRegister { public static ContainerBuilder Register() { ContainerBuilder builder = null; try { builder = new ContainerBuilder(); //工作单元 builder.RegisterGeneric(typeof(UnitOfWork<>)).As(typeof(IUnitOfWork<>));//单例模式 //数据库工厂 builder.RegisterGeneric(typeof(DatabaseFactory<>)).As(typeof(IDatabaseFactory<>)).InstancePerRequest(); //T_Log类 builder.RegisterType<T_LogDAL>().As<IT_LogDAL>(); builder.RegisterType<T_LogBLL>().As<IT_LogBLL>(); return builder; } catch { builder = new ContainerBuilder(); return builder; } } } } namespace MT.Data.Base.BaseObject { /// <summary> /// 数据库工厂类 /// </summary> public class DatabaseFactory<T> : Disposable, IDatabaseFactory<T> where T : class { private T _dbcontext; /// <summary> /// 获取一个数据库上下文对象 /// 每一次http请求都会开启一个新的线程 /// 保证在一个线程(功能)中,DbContext是唯一 /// </summary> /// <returns></returns> public T Get() { try { if (_dbcontext == null) { Type t = typeof(T); System.Reflection.ConstructorInfo ci = t.GetConstructor(System.Type.EmptyTypes); T to = (T)ci.Invoke(new object[0]); _dbcontext = to; } return _dbcontext; } catch (Exception) { throw; } } protected override void DisposeCore() { if (_dbcontext != null) { ((IDisposable)_dbcontext).Dispose(); } } } } namespace MT.Data.Base.BaseObject { /// <summary> /// 释放资源 /// </summary> public class Disposable { //是否回收完毕 private bool isDisposed; ~Disposable() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { //标志位:未回收,且参数标识为需要回收资源 if (!isDisposed && disposing) { //释放那些实现IDisposable接口的托管对象。此处调用方法进行释放。 DisposeCore(); } isDisposed = true; //释放非托管资源。此处没有需要释放的非托管对象 } protected virtual void DisposeCore() { } } } namespace MT.Data.Base.IBaseInterface { /// <summary> /// 数据库工厂接口 /// </summary> /// <typeparam name="T"></typeparam> public interface IDatabaseFactory<T> : IDisposable where T : class { T Get(); } }