工作单元(UnitOfWork) 模式 (2) .NET Core
1.工作单元(UnitOfWork)是什么?
Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
UnitOfWork是Martin Fowler提出的,上面是他说的话,这里要注意的就是分两个时间点,a)业务操作过程中,对操作的CUD操作的对象状态进行跟踪操作; b) CUD操作完毕提交到数据库,UnitOfWork保证业务提交使用同意上下文对象,从而保证了事务的一致性,假设提交失败直接回滚。
简单说:UnitOfWork可以说是一个开关,开:用于上下文的获取,供所有的仓储对象使用;关:提交操作,提交的过程包含事务控制(是否回滚)。
所以他这一句话我们可以明确这几个东西:
1. 一个对象用于存放 业务操作的对象(我们结合仓储使用就是仓储了) repository的临时存储对象;
2. 同一业务操作的上下文必须保持一致(同一上下文对象);
3. 维护当前业务的变更操作;
4. 事务控制;
依据这几点,我们就可以写出想要的代码,(临时先刨除 第三点 ,后面会有补充),先声明:这里未考虑多上下文的情况,因为我这边用不到,但是实现也比较简单,可以将涉及到的hashtable对象换成dictionary等,键为上下位对象类型或者名称。
接口定义:
/// <summary>
/// 工作单元接口
/// </summary>
public interface IUnitOfWork : IDisposable
{
IRepository<TEntity, TKey> Repository<TEntity, TKey>() where TEntity : class, IEntity<TKey>;
void BeginTransaction();
int Commit();
Task<int> CommitAsync();
}
public class UnitOfWork : IUnitOfWork
{
/// <summary>
/// 服务提供器,主要用于查找 框架配置对象,以及DbContextOptionBuilder对象
/// </summary>
private readonly IServiceProvider _provider;
/// <summary>
/// 当前请求涉及的scope生命的仓储对象
/// </summary>
private Hashtable repositorys;
private IDbContextTransaction _dbTransaction { get; set; }
/// <summary>
/// 上下文对象,UnitOfWork内部初始化上下文对象,供当前scope内的操作使用,保证同一上下文
/// </summary>
public DbContext DbContext => GetDbContext();
public UnitOfWork(IServiceProvider provider)
{
_provider = provider;
}
public IRepository<TEntity, TKey> Repository<TEntity, TKey>() where TEntity : class, IEntity<TKey>
{
if (repositorys == null)
repositorys = new Hashtable();
var entityType = typeof(TEntity);
if (!repositorys.ContainsKey(entityType.Name))
{
var baseType = typeof(Repository<,>);
var repositoryInstance = Activator.CreateInstance(baseType.MakeGenericType(entityType), DbContext);
repositorys.Add(entityType.Name, repositoryInstance);
}
return (IRepository<TEntity, TKey>)repositorys[entityType.Name];
}
public void BeginTransaction()
{
//DbContext.Database.UseTransaction(_dbTransaction);//如果多上下文,我们可是在其他上下文直接使用 UserTransaction使用已存在的事务
_dbTransaction = DbContext.Database.BeginTransaction();
}
public int Commit()
{
int result = 0;
try
{
result = DbContext.SaveChanges();
if (_dbTransaction != null)
_dbTransaction.Commit();
}
catch (Exception ex)
{
result = -1;
CleanChanges(DbContext);
_dbTransaction.Rollback();
throw new Exception($"Commit 异常:{ex.InnerException}/r{ ex.Message}");
}
return result;
}
public async Task<int> CommitAsync()
{
int result = 0;
try
{
result = await DbContext.SaveChangesAsync();
if (_dbTransaction != null)
_dbTransaction.Commit();
}
catch (Exception ex)
{
result = -1;
CleanChanges(DbContext);
_dbTransaction.Rollback();
throw new Exception($"Commit 异常:{ex.InnerException}/r{ ex.Message}");
}
return await Task.FromResult(result);
}
private DbContext GetDbContext()
{
var options = _provider.ESoftorOption();
IDbContextOptionsBuilderCreator builderCreator = _provider.GetServices<IDbContextOptionsBuilderCreator>()
.FirstOrDefault(d => d.DatabaseType == options.ESoftorDbOption.DatabaseType);
if (builderCreator == null)
throw new Exception($"无法解析数据库类型为:{options.ESoftorDbOption.DatabaseType}的{typeof(IDbContextOptionsBuilderCreator).Name}实例");
//DbContextOptionsBuilder
var optionsBuilder = builderCreator.Create(options.ESoftorDbOption.ConnectString, null);//null可以换成缓存中获取connection对象,以便性能的提升
if (!(ActivatorUtilities.CreateInstance(_provider, options.ESoftorDbOption.DbContextType, optionsBuilder.Options) is DbContext))
throw new Exception($"上下文对象'{options.ESoftorDbOption.DbContextType.AssemblyQualifiedName}'实例化失败,请确认配置文件已正确配置");
return dbContext;
}
/// <summary>
/// 操作失败,还原跟踪状态
/// </summary>
/// <param name="context"></param>
private static void CleanChanges(DbContext context)
{
var entries = context.ChangeTracker.Entries().ToArray();
for (int i = 0; i < entries.Length; i++)
{
entries[i].State = EntityState.Detached;
}
}
public void Dispose()
{
_dbTransaction.Dispose();
DbContext.Dispose();
GC.SuppressFinalize(this);
}
}
2.怎么用?
就目前而言,博客园中可见到的大部分的 实现都是将 UnitOfWork 注入到 repository,通过 UnitOfWork 获取上下文对象,然后在 service 中只是直接注入所需的 repository 对象,是的,这的确满足我们的开发需求了,也能正常运行。
public class TestService
{
private readonly ITestRepository _testRepository;
public TestService(ITestRepository testRepository){
_testRepository = testRepository;
}
//......其他方法实现
}
如果有多个仓储对象,依次如上面的方式注入即可。但是,这样做的话,当以后我们有表删除或者新增的时候,我们不得不维护这样的列表。这完全不符合OO设计原则;如果我们有新表的创建或者删除,改动就比较多了。
如果你有细细观察的话,我们这里的 UnitOfWork实现稍有不同,也就涉及到当前请求的 仓储对象(repository),我们在这临时存储到了一个 hashable对象中,那么这时候我们在 service中使用UnitOfWork和仓储的时候,就不用像上面那样,有多少个需要注册多少次,而只需要注入一个UnitOfWork对象即可。然后通过UnitOfWork获取 仓储对象(repository),因为我们临时将涉及到当前请求(事务)的 仓储已经存储到私有变量的 hashtable中。
public class TestService
{
private readonly IUnitOfWork _UnitOfWork;
public TestService(IUnitOfWork UnitOfWork){
_UnitOfWork = UnitOfWork;
}
//......其他方法实现
}
然后我们在使用仓储(repository)的时候,只需要如下方式使用即可:
var userRepository = _UnitOfWork.Repository<User,Guid>();
var roleRepository = _UnitOfWork.Repository<Role,Guid>();
...
而在我们用到事务的地方,直接使用UnitOfWork中的commit提交即可:
_UnitOfWork.BeginTransaction();
var userRepository = _UnitOfWork.Repository<User,Guid>();
var roleRepository = _UnitOfWork.Repository<Role,Guid>();
...//一些列其他操作,(CRUD)
_UnitOfWork.Commit();
就像上面说到的,这样保证了当前业务操作涉及的 仓储对象(repository),会保证在 hashtable对象中,同时使用同一个上下文对象(DbContext),Commit提交的时候保证了事务(上下文)的一致性。而且如上面提到的,我们只需要在service层中注入一个UnitOfWork即可,不论表如何变动,删除或者新增表,我们这里不会受到任何影响。比较理想的一种方式。
3.注意点?
UnitOfWork模式注意点:
1.由UnitOfWork初始化上下文对象,也就是我们代码中的DbContext
2.由UnitOfWork提供事务的控制方法,以及控制事务回滚,保证最终一致性
3.这里我们还使用了UnitOfWork进行仓储对象的获取。
4.其他
4.补充:对象操作状态的控制
上面有说到,UnitOfWork还需要对操作状态的控制,简单说就是,一系列的 增、删、改 命令操作的状态控制,可参考:https://www.cnblogs.com/zhaoshujie/p/12260188.html
基本原理就是类似我们定义的 hashtable对象,定义三个 Dictionary 变量,用于存储当前业务操作涉及的 增、删、改 三种操作的存储变量。
工作单元(UnitOfWork) 模式 (2) .NET Core的更多相关文章
- .net core2.x - 关于工作单元(UnitOfWork) 模式
概要:在搭建框架,顺手说下写下,关于unitofwork,可能你理解了,可能你还不理解,可能与不可能不是重点,重点是感兴趣就看看吧. 1.工作单元(unitofowork)是什么(后面简写uow)? ...
- Asp.Net Core 工作单元 UnitOfWork UOW
Asp.Net Core 工作单元示例 来自 ABP UOW 去除所有无用特性 代码下载 : 去除所有无用特性版本,原生AspNetCore实现 差不多 2278 行代码: 链接:https://pa ...
- .NET Core 工作单元unitofwork 实现,基于NPOCO
现有项目中的orm 并非efcore,而是非主流的npoco,本身没有自带工作单元所以需要自己手撸一个,现记录一下,基于其他orm的工作单元照例实现应该没有什么问题 该实现基于NPOCO,针对其他的O ...
- Asp.Net Core仓储模式+工作单元
仓储模式+工作单元 仓储模式 仓储(Repository)模式自2004年首次作为领域驱动模型DDD设计的一部分引入,仓储本质上是提供提供数据的抽象,以便应用程序可以使用具有接口的相似的简单抽象集合. ...
- MVC+EF 理解和实现仓储模式和工作单元模式
MVC+EF 理解和实现仓储模式和工作单元模式 原文:Understanding Repository and Unit of Work Pattern and Implementing Generi ...
- ABP框架 - 工作单元
文档目录 本节内容: 简介 在ABP中管理连接和事务 约定的工作单元 UnitOfWork 特性 IUnitOfWorkManager 工作单元详情 禁用工作单元 非事务性工作单元 工作单元方法调用另 ...
- ABP官方文档翻译 3.6 工作单元
工作单元 介绍 ABP中的连接和事务管理 传统的工作单元方法 控制工作单元 UnitOfWork特性 IUnitOfWorkManager 工作单元详情 禁用工作单元 无事务工作单元 一个工作单元方法 ...
- EsayUI + MVC + ADO.NET(工作单元)
关联的设计 关联本身不是一个模式,但它在领域建模的过程中非常重要,所以需要在探讨各种模式之前,先讨论一下对象之间的关联该如何设计.我觉得对象的关联的设计可以遵循如下的一些原则: 关联尽量少,对象之间的 ...
- ABP的工作单元
http://www.aspnetboilerplate.com/Pages/Documents/Unit-Of-Work 工作单元位于领域层. ABP的数据库连接和事务处理: 1,仓储类 ASP ...
- .NET Core MongoDB数据仓储和工作单元模式封装
前言 上一章我们把系统所需要的MongoDB集合设计好了,这一章我们的主要任务是使用.NET Core应用程序连接MongoDB并且封装MongoDB数据仓储和工作单元模式,因为本章内容涵盖的有点多关 ...
随机推荐
- Java的深浅拷贝认识
目录 浅拷贝 深拷贝 分辨代码里的深浅拷贝 在Java中,深拷贝和浅拷贝是对象复制的两种方式,主要区别在于对对象内部的引用类型的处理上. 浅拷贝 定义: 浅拷贝是指创建一个新的对象,但这个新对象的属性 ...
- conda错误 创建新环境conda create -n TF117 python=3.5时报错 An unexpected error has occurred. Conda has prepared the above report.
创建新环境conda create -n TF117 python=3.5时报错 An unexpected error has occurred. Conda has prepared the ab ...
- 一文搞懂C++继承、多继承、菱形继承、虚继承
继承 目录 继承 继承 继承的访问权限 子类赋值给父类 赋值兼容规则 "天然"的行为 验证: 1. 其他权限继承能否支持赋值兼容规则 2.是否"天然",有没有产 ...
- OC的引用计数
一.引用计数 引用计数是Objetive-C语言的内存管理机制,用于管理OC对象(通常指包含isa指针的结构体)的内存. 一个对象的引用计数为大于0的计数,表示这个对象被持有,不能被释放,当引用计数为 ...
- NOIP模拟56
前言 话说, T2 和 T3 的题面好像放反了. T1 爆零 解题思路 是个原题.. 当时 WindZR 25分钟就码完了,然后我就慌死,写完就开始调,诶,我当时场上不是切了吗,怎么现在打不对了.. ...
- v-for 伪标签:template:
根据一组数组或对象的选项列表进行渲染. v-for指令需要使用 (item,index) in 数组或对象 形式的特殊语法,同时还需要指定key值,key的作用在vue进行新旧数据比对渲染页面里,如果 ...
- 算法金 | 只需十四步:从零开始掌握Python机器学习(附资源)
大侠幸会,在下全网同名「算法金」 0 基础转 AI 上岸,多个算法赛 Top 「日更万日,让更多人享受智能乐趣」 1. 引言 1.1 教程目的与读者定位 "启程"往往是最具挑战性的 ...
- 设置双击ps1脚本直接用Powershell打开
设置双击ps1脚本直接用Powershell打开. 默认.ps1 文件双击是不能打开的,只能右键运行. 解决方法: 1.按windows+R打开运行,输入regedit,打开注册表 2.找到HKEY_ ...
- Django路由层、视图层及模板层
Django路由层 URL配置(URLconf)就像Django所支撑网站的目录.它的本质是URL与要为该URL调用的视图函数之间的映射表; 你就是以这种方式告诉Django,对于客户端发来的某个UR ...
- Vue学习:15.组件化开发
组件化开发 组件化开发是一种软件开发方法,它将应用程序拆分成独立的.可重用的模块,每个模块都被称为组件.这些组件可以独立开发.测试.维护和部署,从而提高了代码的可维护性.可扩展性和复用性.在前端开发中 ...