前言

每次提交数据库都会打开一个连接,造成结果是:多个连接无法共用一个数据库级别的事务,也就无法保证数据的原子性、一致性。

解决办法是:在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的更多相关文章

  1. EF中使用UnitOfWork

    前言 关于EF5中使用UnitWork,参见另一博文:  https://www.cnblogs.com/masonblog/p/9801162.html 每次提交数据库都会打开一个连接,造成结果是: ...

  2. EF5中 执行 sql语句使用Database.ExecuteSqlCommand 返回影响的行数 ; EF5执行sql查询语句 Database.SqlQuery 带返回值

    一: 执行sql语句,返回受影响的行数 在mysql里面,如果没有影响,那么返回行数为  -1 ,sqlserver 里面  还没有测试过 using (var ctx = new MyDbConte ...

  3. MVC5+EF6 入门完整教程十一:细说MVC中仓储模式的应用

    摘要: 第一阶段1~10篇已经覆盖了MVC开发必要的基本知识. 第二阶段11-20篇将会侧重于专题的讲解,一篇文章解决一个实际问题. 根据园友的反馈, 本篇文章将会先对呼声最高的仓储模式进行讲解. 文 ...

  4. MVC5+EF6 入门完整教程11--细说MVC中仓储模式的应用

    摘要: 第一阶段1~10篇已经覆盖了MVC开发必要的基本知识. 第二阶段11-20篇将会侧重于专题的讲解,一篇文章解决一个实际问题. 根据园友的反馈, 本篇文章将会先对呼声最高的仓储模式进行讲解. 文 ...

  5. Repository,UnitOfWork,DbContext(1)

    一.前言 终于到EF了,实在不好意思,最近有点忙,本篇离上一篇发布已经一个多星期了,工作中的小迭代告一段落,终于有点时间来继续我们的架构设计了,在这里先对大家表示歉意. 其实这段时间我并不是把这个系列 ...

  6. 细说MVC中仓储模式的应用

    文章提纲 概述要点 理论基础 详细步骤 总结 概述要点 设计模式的产生,就是在对开发过程进行不断的抽象. 我们先看一下之前访问数据的典型过程. 在Controller中定义一个Context, 例如: ...

  7. MVC实用架构设计(三)——EF-Code First(1):Repository,UnitOfWork,DbContext

    前言 终于到EF了,实在不好意思,最近有点忙,本篇离上一篇发布已经一个多星期了,工作中的小迭代告一段落,终于有点时间来继续我们的架构设计了,在这里先对大家表示歉意. 其实这段时间我并不是把这个系列给忘 ...

  8. ABP源码分析十:Unit Of Work

    ABP以AOP的方式实现UnitOfWork功能.通过UnitOfWorkRegistrar将UnitOfWorkInterceptor在某个类被注册到IOCContainner的时候,一并添加到该类 ...

  9. ASP.NET MVC5+EF6+EasyUI 后台管理系统(51)-系统升级

    系统很久没有更新内容了,期待已久的更新在今天发布了,最近花了2个月的时间每天一点点,从原有系统 MVC4+EF5+UNITY2.X+Quartz 2.0+easyui 1.3.4无缝接入 MVC5+E ...

随机推荐

  1. VMW14.x虚拟机安装Mac10.13系统教程

    之前虚拟机安装Mac经常出问题,所以这次又重新安装Mac,为了加深映像和之后回忆方便,特写下此次安装教程. 一 工具的准备: 首先准备教程的软件和包 1,虚拟机VMW    下载地址:https:// ...

  2. javaAgent打包找不到premain类文件解决

    agent 作用和开发 可以用独立于应用程序之外的代理(agent)程序来监测和协助运行在JVM上的应用程序.这种监测和协助包括但不限于获取JVM运行时状态,替换和修改类定义等. 由此可知agent ...

  3. 【Luogu U41492】树上数颜色——树上启发式合并(dsu on tree)

    (这题在洛谷主站居然搜不到--还是在百度上偶然看到的) 题目描述 给一棵根为1的树,每次询问子树颜色种类数 输入输出格式 输入格式: 第一行一个整数n,表示树的结点数 接下来n-1行,每行一条边 接下 ...

  4. 树莓派4b 安装最新wiringpi库

    树莓派4自带的wiringPi库默认是2.50,无法映射到gpio,所以需要更新到2.52才能与树莓派映射: 1. 安装自带的wiringPi库 $ Sudo apt-get install wiri ...

  5. 探究 | 如何捕获一个Activity页面上所有的点击行为

    前言 最近逛wanAndroid论坛,发现一个有趣的问题:如何捕获一个Activity页面上所有的点击行为. 一起研究下吧,不想看源码的小伙伴可以直接看文末总结- 准备工作 先得罗列出页面上的一些点击 ...

  6. 【五校联考1day2】JZOJ2020年8月12日提高组T1 对你的爱深不见底

    [五校联考1day2]JZOJ2020年8月12日提高组T1 对你的爱深不见底 题目 Description 出乎意料的是,幸运E 的小R 居然赢了那个游戏.现在欣喜万分的小R 想要写一张明信片给小Y ...

  7. 通过sql 语句将多行数据拼接到一行中

  8. 自动化运维工具之Puppet模块

    前文我们了解来puppet的变量.流程控制.正则表达式.类和模板的相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/14079208.html:今天我们来 ...

  9. 欢迎使用CSDN-markdown编辑器(这个只能看到一次保存一下)

    欢迎使用Markdown编辑器写博客 本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦: Markdown和扩展Markdown简洁的语法 代码块高亮 图片链接 ...

  10. 老猿学5G专栏完结说明

    老猿学5G是因为工作原因促成的,主要目的是为了研究5G的计费架构相关内容,到今天为止,基本上达成目标,因此这个专栏基本上告一段落了. 回想这2个多月的日子,从一个对5G相关知识完全不熟悉的小白,到现在 ...