概要:在搭建框架,顺手说下写下,关于unitofwork,可能你理解了,可能你还不理解,可能与不可能不是重点,重点是感兴趣就看看吧。

1.工作单元(unitofowork)是什么(后面简写uow)?

  Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.

  Unit of Work --Martin Fowler

  uow是这个老兄(马丁)提出的,上面是他说的话,这里要注意的就是分两个时间点,a)业务操作过程中,对操作的CUD操作的对象的状态进行跟踪操作; b) CUD操作完必经的一步骤当然是提交到数据库,uow保证业务提交使用同意上下文对象,从而保证了事务的一致性,假设提交失败直接回滚。

  简单说:uow可以说是一个开关,开:用于上下文的获取,供所有的仓储对象使用;关:提交操作,提交的过程包含事务控制(是否会滚)。

  所以他这一句话我们可以明确这几个东西:

  ①:一个对象用于存放 业务操作的对象(我们结合仓储使用就是仓储了) repository的临时存储对象;

  ②:同一业务操作的上下文必须保持一致(同一上下文对象)

  3  :维护当前业务的变更操作(微软自带输入法打不出来圆圈三,,,,)

  ④:事务控制

  依据这几点,所以我们可以和容易写出我们想要的代码,(临时先刨除 第三点 ,后面会有补充),先声明:这里未考虑多上下文的情况,因为我这边用不到,但是实现也比较简单,可以将涉及到的hashtable对象换成dictionary等,键为上下位对象类型或者名称。

  1. public class UnitOfWork : IUnitOfWork
  2. {
  3. #region fields
  4. /// <summary>
  5. /// 服务提供器,主要用于查找 框架配置对象,以及DbContextOptionBuilder对象
  6. /// </summary>
  7. private readonly IServiceProvider _provider;
  8. /// <summary>
  9. /// 当前请求涉及的scope生命的仓储对象
  10. /// </summary>
  11. private Hashtable repositorys;
  12.  
  13. private IDbContextTransaction _dbTransaction { get; set; }
  14. /// <summary>
  15. /// 上下文对象,UOW内部初始化上下文对象,供当前scope内的操作使用,保证同一上下文
  16. /// </summary>
  17. public DbContext DbContext => GetDbContext();
  18. #endregion
  19.  
  20. #region ctor
  21. public UnitOfWork(IServiceProvider provider)
  22. {
  23. _provider = provider;
  24. }
  25. #endregion
  26.  
  27. #region public
  28.  
  29. public IRepository<TEntity, TKey> Repository<TEntity, TKey>() where TEntity : class, IEntity<TKey>
  30. {
  31. if (repositorys == null)
  32. repositorys = new Hashtable();
  33.  
  34. var entityType = typeof(TEntity);
  35. if (!repositorys.ContainsKey(entityType.Name))
  36. {
  37. var baseType = typeof(Repository<,>);
  38. var repositoryInstance = Activator.CreateInstance(baseType.MakeGenericType(entityType), DbContext);
  39. repositorys.Add(entityType.Name, repositoryInstance);
  40. }
  41.  
  42. return (IRepository<TEntity, TKey>)repositorys[entityType.Name];
  43. }
  44.  
  45. public void BeginTransaction()
  46. {
  47. //DbContext.Database.UseTransaction(_dbTransaction);//如果多上下文,我们可是在其他上下文直接使用 UserTransaction使用已存在的事务
  48. _dbTransaction = DbContext.Database.BeginTransaction();
  49. }
  50.  
  51. public int Commit()
  52. {
  53. int result = ;
  54. try
  55. {
  56. result = DbContext.SaveChanges();
  57. if (_dbTransaction != null)
  58. _dbTransaction.Commit();
  59. }
  60. catch (Exception ex)
  61. {
  62. result = -;
  63. CleanChanges(DbContext);
  64. _dbTransaction.Rollback();
  65. throw new Exception($"Commit 异常:{ex.InnerException}/r{ ex.Message}");
  66. }
  67. return result;
  68. }
  69.  
  70. public async Task<int> CommitAsync()
  71. {
  72. int result = ;
  73. try
  74. {
  75. result = await DbContext.SaveChangesAsync();
  76. if (_dbTransaction != null)
  77. _dbTransaction.Commit();
  78. }
  79. catch (Exception ex)
  80. {
  81. result = -;
  82. CleanChanges(DbContext);
  83. _dbTransaction.Rollback();
  84. throw new Exception($"Commit 异常:{ex.InnerException}/r{ ex.Message}");
  85. }
  86. return await Task.FromResult(result);
  87. }
  88.  
  89. #endregion
  90.  
  91. #region private
  92. private DbContext GetDbContext()
  93. {
  94. var options = _provider.ESoftorOption();
  95.  
  96. IDbContextOptionsBuilderCreator builderCreator = _provider.GetServices<IDbContextOptionsBuilderCreator>()
  97. .FirstOrDefault(d => d.DatabaseType == options.ESoftorDbOption.DatabaseType);
  98.  
  99. if (builderCreator == null)
  100. throw new Exception($"无法解析数据库类型为:{options.ESoftorDbOption.DatabaseType}的{typeof(IDbContextOptionsBuilderCreator).Name}实例");
  101. //DbContextOptionsBuilder
  102. var optionsBuilder = builderCreator.Create(options.ESoftorDbOption.ConnectString, null);//TODO null可以换成缓存中获取connection对象,以便性能的提升
  103.  
  104. if (!(ActivatorUtilities.CreateInstance(_provider, options.ESoftorDbOption.DbContextType, optionsBuilder.Options) is DbContext dbContext))
  105. throw new Exception($"上下文对象 “{options.ESoftorDbOption.DbContextType.AssemblyQualifiedName}” 实例化失败,请确认配置文件已正确配置。 ");
  106.  
  107. return dbContext;
  108. }
  109.  
  110. /// <summary>
  111. /// 操作失败,还原跟踪状态
  112. /// </summary>
  113. /// <param name="context"></param>
  114. private static void CleanChanges(DbContext context)
  115. {
  116. var entries = context.ChangeTracker.Entries().ToArray();
  117. for (int i = ; i < entries.Length; i++)
  118. {
  119. entries[i].State = EntityState.Detached;
  120. }
  121. }
  122.  
  123. #endregion
  124.  
  125. #region override
  126. public void Dispose()
  127. {
  128. _dbTransaction.Dispose();
  129. DbContext.Dispose();
  130. GC.SuppressFinalize(this);
  131. }
  132. #endregion
  133. }

  接口定义:

  1. /// <summary>
  2. /// 工作单元接口
  3. /// </summary>
  4. public interface IUnitOfWork : IDisposable
  5. {
  6. IRepository<TEntity, TKey> Repository<TEntity, TKey>() where TEntity : class, IEntity<TKey>;
  7.  
  8. void BeginTransaction();
  9.  
  10. int Commit();
  11. Task<int> CommitAsync();
  12. }

2.怎么用?

  就目前而言,博客园中可见到的大部分的 实现都是将uow注入到 repository,通过 uow获取上下文对象,然后在 service中只是直接注入所需的 repository对象,是的,这的确满足我们的开发需求了,也能正常运行。

  1. public class TestService
  2. {
  3. private readonly ITestRepository _testRepository;
  4. public TestService(ITestRepository testRepository){
  5. _testRepository = testRepository;
  6. }
  7. //......其他方法实现
  8. }

  如果有多个仓储对象,依次如上面的方式注入即可。但是,这样做的话,当以后我们有表删除或者新增的时候,我们不得不维护这样的列表。这完全不符合OO设计原则;如果我们有新表的创建或者删除,改动就比较多了。

  如果你有细细观察的话,我们这里的 UOW实现稍有不同,也就涉及到当前请求的 仓储对象(repository),我们在这零时存储到了一个 hashable对象中,那么这时候我们在 service中使用uow和仓储的时候,就不用像上面那样,有多少个需要注册多少次,而只需要注入我们的一个uow对象即可。然后通过uow获取 仓储(repository)对象,因为我们零时将涉及到当前请求(事务)的 仓储已经存储到私有变量的 hashtable中,

  1. public class TestService
  2. {
  3. private readonly IUnitOfWork _uow;
  4. public TestService(IUnitOfWork uow){
  5.       _uow = uow;
  6. }
  7. //......其他方法实现
  8. }

  然后我们在使用仓储(repository)的时候,只需要如下方式使用即可:

  var userRepository = _uow.Repository<User,Guid>();

  var roleRepository = _uow.Repository<Role,Guid>();

  ...

  而在我们用到事务的地方,直接使用uow中的commit提交即可:

  _uow.BeginTransaction();

  var userRepository = _uow.Repository<User,Guid>();

  var roleRepository = _uow.Repository<Role,Guid>();

  ...//一些列其他操作,(CRUD)

  _uow.Commit();

  就像上面说到的,这样保证了当前业务操作涉及的 仓储对象(repository),会保证在 hashtable对象中,同时使用同一个上线问对象(DbContext),Commit提交的时候保证了事务(上下文)的一致性。而且如上面提到的,我们只需要在service层中注入一个uow即可,不论表如何变动,删除或者新增表,我们这里不会收到任何影响。比较理想的一种方式。

3.注意点?

  uow模式注意点,也就是uow的说明 即:

  1.由uow初始化上下文对象,也就是我们代码中的DbContext,;

  2.由uow提供事务的控制方法,以及控制事务回滚,保证最终一致性

  3.这里我们还使用了uow进行仓储对象的 获取。

  4.其他

  

4.补充:对象操作状态的控制?

  上面有说到,uow还需要对操作状态的控制?啥意思?简单说就是,一系列的 增、删、改的 命令操作 的状态控制,这里的实现,园子已经在很早之前就有比较完善的实现了:

  http://www.cnblogs.com/zxj159/p/3505457.html

  基本原理就是 类似我们定义的 hashtable对象,定义三个 Dictionary 变量,用于存储当前 业务操作涉及的 增、删、改、三种操作的 存储变量。

完。

.net core2.x - 关于工作单元(UnitOfWork) 模式的更多相关文章

  1. Asp.Net Core 工作单元 UnitOfWork UOW

    Asp.Net Core 工作单元示例 来自 ABP UOW 去除所有无用特性 代码下载 : 去除所有无用特性版本,原生AspNetCore实现 差不多 2278 行代码: 链接:https://pa ...

  2. .NET Core 工作单元unitofwork 实现,基于NPOCO

    现有项目中的orm 并非efcore,而是非主流的npoco,本身没有自带工作单元所以需要自己手撸一个,现记录一下,基于其他orm的工作单元照例实现应该没有什么问题 该实现基于NPOCO,针对其他的O ...

  3. MVC+EF 理解和实现仓储模式和工作单元模式

    MVC+EF 理解和实现仓储模式和工作单元模式 原文:Understanding Repository and Unit of Work Pattern and Implementing Generi ...

  4. Asp.Net Core仓储模式+工作单元

    仓储模式+工作单元 仓储模式 仓储(Repository)模式自2004年首次作为领域驱动模型DDD设计的一部分引入,仓储本质上是提供提供数据的抽象,以便应用程序可以使用具有接口的相似的简单抽象集合. ...

  5. EsayUI + MVC + ADO.NET(工作单元)

    关联的设计 关联本身不是一个模式,但它在领域建模的过程中非常重要,所以需要在探讨各种模式之前,先讨论一下对象之间的关联该如何设计.我觉得对象的关联的设计可以遵循如下的一些原则: 关联尽量少,对象之间的 ...

  6. ABP框架 - 工作单元

    文档目录 本节内容: 简介 在ABP中管理连接和事务 约定的工作单元 UnitOfWork 特性 IUnitOfWorkManager 工作单元详情 禁用工作单元 非事务性工作单元 工作单元方法调用另 ...

  7. ABP的工作单元

    http://www.aspnetboilerplate.com/Pages/Documents/Unit-Of-Work 工作单元位于领域层.   ABP的数据库连接和事务处理: 1,仓储类 ASP ...

  8. ABP官方文档翻译 3.6 工作单元

    工作单元 介绍 ABP中的连接和事务管理 传统的工作单元方法 控制工作单元 UnitOfWork特性 IUnitOfWorkManager 工作单元详情 禁用工作单元 无事务工作单元 一个工作单元方法 ...

  9. 仓储(Repository)和工作单元模式(UnitOfWork)

    仓储和工作单元模式 仓储模式 为什么要用仓储模式 通常不建议在业务逻辑层直接访问数据库.因为这样可能会导致如下结果: 重复的代码 编程错误的可能性更高 业务数据的弱类型 更难集中处理数据,比如缓存 无 ...

随机推荐

  1. kubernetes 报错汇总

    一. pod的报错: 1. pod的容器无法启动报错: 报错信息: Normal SandboxChanged 4m9s (x12 over 5m18s) kubelet, k8sn1 Pod san ...

  2. AutoMapper入门使用

    AutoMapper入门使用 在应用开发的过程中,首先要了解整个系统中各个系统的组件的作用,然后了解系统的工作流(workflow),最后需要梳理一遍数据流(dataflow),而在整理数据流的过程中 ...

  3. ☆ [洛谷P2633] Count on a tree 「树上主席树」

    题目类型:主席树+\(LCA\) 传送门:>Here< 题意:给出一棵树.每个节点有点权.问某一条路径上排名第\(K\)小的点权是多少 解题思路 类似区间第\(K\)小,但放在了树上. 考 ...

  4. Vivado寄存器初始值问题

    前言 本复位只针对Vivado中的寄存器复位. 什么时候需要复位?到底要不要复位?怎么复位?复位有什么卵用? 该复位的寄存器需要复位,复位使得寄存器恢复初始值,有的寄存器并不需要复位(数据流路径上). ...

  5. Mysql相关知识点梳理(一):优化查询

    EXPLAIN解析SELECT语句执行计划: EXPLAIN与DESC同义,通过它可解析MySQL如何处理SELECT,提供有关表如何联接和联接的次序,还可以知道什么时候必须为表加入索引以得到一个使用 ...

  6. 2019The Preliminary Contest for ICPC China Nanchang National Invitational

    The Preliminary Contest for ICPC China Nanchang National Invitational 题目一览表 考察知识点 I. Max answer 单调栈+ ...

  7. postgres 基本操作

    登陆: $ psql -U <user> -d <dbname> 数据库操作: $ \l      //查看库 $  \c <dbname>   //切换库 // ...

  8. css 函数

    css还有一些强大的函数: 1. calc 可以混合多种单位来计算 div { font-size: calc(100vw/5 + 1rem - 100px) } 2. max.min.clamp m ...

  9. Hadoop基础-镜像文件(fsimage)和编辑日志(edits)

    Hadoop基础-镜像文件(fsimage)和编辑日志(edits) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.查看日志镜像文件(如:fsimage_00000000000 ...

  10. nodejs和npm的关系【转】

    node.js是javascript的一种运行环境,是对Google V8引擎进行的封装.是一个服务器端的javascript的解释器. 包含关系: nodejs中含有npm,比如说你安装好nodej ...