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();
}
}
EF5中使用UnitOfWork的更多相关文章
- EF中使用UnitOfWork
前言 关于EF5中使用UnitWork,参见另一博文: https://www.cnblogs.com/masonblog/p/9801162.html 每次提交数据库都会打开一个连接,造成结果是: ...
- EF5中 执行 sql语句使用Database.ExecuteSqlCommand 返回影响的行数 ; EF5执行sql查询语句 Database.SqlQuery 带返回值
一: 执行sql语句,返回受影响的行数 在mysql里面,如果没有影响,那么返回行数为 -1 ,sqlserver 里面 还没有测试过 using (var ctx = new MyDbConte ...
- MVC5+EF6 入门完整教程十一:细说MVC中仓储模式的应用
摘要: 第一阶段1~10篇已经覆盖了MVC开发必要的基本知识. 第二阶段11-20篇将会侧重于专题的讲解,一篇文章解决一个实际问题. 根据园友的反馈, 本篇文章将会先对呼声最高的仓储模式进行讲解. 文 ...
- MVC5+EF6 入门完整教程11--细说MVC中仓储模式的应用
摘要: 第一阶段1~10篇已经覆盖了MVC开发必要的基本知识. 第二阶段11-20篇将会侧重于专题的讲解,一篇文章解决一个实际问题. 根据园友的反馈, 本篇文章将会先对呼声最高的仓储模式进行讲解. 文 ...
- Repository,UnitOfWork,DbContext(1)
一.前言 终于到EF了,实在不好意思,最近有点忙,本篇离上一篇发布已经一个多星期了,工作中的小迭代告一段落,终于有点时间来继续我们的架构设计了,在这里先对大家表示歉意. 其实这段时间我并不是把这个系列 ...
- 细说MVC中仓储模式的应用
文章提纲 概述要点 理论基础 详细步骤 总结 概述要点 设计模式的产生,就是在对开发过程进行不断的抽象. 我们先看一下之前访问数据的典型过程. 在Controller中定义一个Context, 例如: ...
- MVC实用架构设计(三)——EF-Code First(1):Repository,UnitOfWork,DbContext
前言 终于到EF了,实在不好意思,最近有点忙,本篇离上一篇发布已经一个多星期了,工作中的小迭代告一段落,终于有点时间来继续我们的架构设计了,在这里先对大家表示歉意. 其实这段时间我并不是把这个系列给忘 ...
- ABP源码分析十:Unit Of Work
ABP以AOP的方式实现UnitOfWork功能.通过UnitOfWorkRegistrar将UnitOfWorkInterceptor在某个类被注册到IOCContainner的时候,一并添加到该类 ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(51)-系统升级
系统很久没有更新内容了,期待已久的更新在今天发布了,最近花了2个月的时间每天一点点,从原有系统 MVC4+EF5+UNITY2.X+Quartz 2.0+easyui 1.3.4无缝接入 MVC5+E ...
随机推荐
- CCF-CSP认证 C++题解目录
持续更新中,记录刷题过程并分享一下小小的心得总结. 试题编号 试题名称 标签 202006-1 线性分类器 | 题解 线性规划 202006-2 稀疏向量| 题解 归并排序思想 202006-3 化学 ...
- Java集合【6.1】-- Collection接口源码详解
目录 一.Collection接口简介 二.Collection源码分析 三.Collection的子类以及子类的实现 3.1 List extend Collection 3.2 Set exten ...
- 一枚程序猿的MacBook M1详细体验报告
前言 2020年11月11日双十一上午,苹果发布了M1芯片的新款Mac,其最大的变化就是将处理器从Intel换成了苹果自研的ARM芯片M1. 上一次苹果更换Mac芯片要追溯到2006年,14年前,苹果 ...
- Java中的Set对象去重
前言部分 Set<T> 去重相信大家一定不陌生,尤其是在 Set<String>.Set<Integer> 等等,但是在使用 Set<实体> ,在不重写 ...
- 【GDKOI2014】JZOJ2020年8月13日提高组T1 阶乘
[GDKOI2014]JZOJ2020年8月13日提高组T1 阶乘 题目 Description Input 第一行有一个正整数T,表示测试数据的组数. 接下来的T行,每行输入两个十进制整数n和bas ...
- 【进阶之路】定时任务调用平台xxl-job
大家好,我是练习java两年半时间的南橘,从一名连java有几种数据结构都不懂超级小白,到现在懂了一点点的进阶小白,学到了不少的东西.知识越分享越值钱,我这段时间总结(包括从别的大佬那边学习,引用)了 ...
- 学JAVA的艰难之路
周五回家了,这两天也没有更新代码系列,很抱歉,之后会补上.今天刚到宿舍,写了会代码,想用一个数组的从键盘输入,再也弄不好了,最终在我不断的翻之前写的代码,终于是找到了一个例子,真的不容易,编程这东西, ...
- moviepy AudioClip的max_volume方法报错ValueError: operands could not be broadcast together with shapes(2,)
☞ ░ 前往老猿Python博文目录 ░ 在<moviepy音视频剪辑:AudioClip的max_volume方法报TypeError: bad operand type for abs(): ...
- PyQt(Python+Qt)学习随笔:QTextEdit的setText、setHtml、setPlainText之间的区别
专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 QTextEdit中提供了三个设置编辑器文本的方法,分别是setTex ...
- PyQt学习随笔:使用QPropertyAnimation开发简单动画
QPropertyAnimation是PyQt5.QtCore模块提供的动画设计类,使用该类可以针对PyQt的界面对象进行动画播放,如果要针对一个指定对象进行动画播放,包括如下步骤: 一.创建动画对象 ...