通用连接和事务管理方法
连接和事务管理是使用数据库的应用程序最重要的概念之一。当你开启一个数据库连接,什么时候开始事务,如何释放连接...诸如此类的。

正如大家都知道的,.Net使用连接池(connection pooling)。因此,创建一个连接实际上是从连接池中取得一个连接,会这么做是因为创建新连接会有成本。如果没有任何连接存在于连接池中,一个新的连接对象会被创建并且添加到连接池中。当你释放连接,它实际上是将这个连接对象送回到连接池。这并不是实际意义上的释放。这个机制是由.Net所提供的。因此,我们应该在使用完之后释放掉连接对象。这就是最佳实践。

在应用程序中,有两个通用的方来创建/释放一个数据库连接:

第一个方法:在Web请求到达的时候,创建一个连接对象。(Application_BeginRequest这个位于global.asax中的事件),使用同一个连接对象来处理所有的数据库操作,并且在请求结束的时候关闭/释放这个连接 (Application_EndRequest事件)。

这是个简易但却没效率的方法,原因:

  • 或许这个Web请求不需要操作数据库,但是连接却会开启。这对于连接池来说是个毫无效率的使用方式。
  • 这可能会让Web请求的运行时间变长,并且数据库操作还会需要一些执行。这也是一种没效率的连接池使用方式。
  • 这对于Web应用来说是可行的。如果你的应用程序是Widnows Service,这可能就无法被实现了。
  • 同样的这是一个使用事务式的数据库操作最佳场景。如果有一个操作发生失败,所有的操作都会回滚。因为事务会锁住数据库中的一些数据列(事件数据表),它必定要是短暂的。

第二个方法: 创建一个连接当需要的时候(只要在使用它之前)并且释放它在使用它之后。这是相当高效的,但是就得乏味而且反复的去进行(创建/释放连接)。

ABP的连接和事务管理
ABP综合上述两个连接管理的方法,并且提供一个简单而且高效的模型。

1.仓储类(Repository classes)

仓储是主要的数据库操作的类。ABP开启了一个数据库连接并且在进入到仓储方法时会启用一个事务。因此,你可以安全地使用连接于仓储方法中。在仓储方法结束后,事务会被提交并且会释放掉连接。假如仓储方法抛出任何异常,事务会被回滚并且释放掉连接。在这个模式中,仓储方法是单元性的(一个工作单元unit of work)。ABP在处理上述那些动作都是全自动的。在这里,有一个简单的仓储:

  1. public class ContentRepository : NhRepositoryBase<Content>, IContentRepository
  2. {
  3. public List<Content> GetActiveContents(string searchCondition)
  4. {
  5. var query = from content in Session.Query<Content>()
  6. where content.IsActive && !content.IsDeleted
  7. select content;
  8.  
  9. if (string.IsNullOrEmpty(searchCondition))
  10. {
  11. query = query.Where(content => content.Text.Contains(searchCondition));
  12. }
  13.  
  14. return query.ToList();
  15. }
  16. }

这个示例使用NHibernate作为ORM框架。如上所示,不需要撰写任何数据库连接操作(NHibernate中的Session)的程序代码。

假如仓储方法调用另一个仓储方法(一般来说,若工作单元方法调用另一个工作单元的方法),都使用同一个连接和事务。第一个被调用到的仓储方法负责管理连接和事务,而其余被它调用的仓储方法则只单纯使用不管理。

2.应用服务(Application service classes)

一个应用服务的方法也被考虑使用工作单元。如果我们拥有一个应用服务方法如下:

  1. public class PersonAppService : IPersonAppService
  2. {
  3. private readonly IPersonRepository _personRepository;
  4. private readonly IStatisticsRepository _statisticsRepository;
  5.  
  6. public PersonAppService(IPersonRepository personRepository, IStatisticsRepository statisticsRepository)
  7. {
  8. _personRepository = personRepository;
  9. _statisticsRepository = statisticsRepository;
  10. }
  11.  
  12. public void CreatePerson(CreatePersonInput input)
  13. {
  14. var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
  15. _personRepository.Insert(person);
  16. _statisticsRepository.IncrementPeopleCount();
  17. }
  18. }

在CreatePerson方法中,我们新增一个person使用person仓储并且使用statistics仓储增加总people数量。两个仓储共享同一个连接和事务于这个例子中,因为这是一个应用服务的方法。ABP开启一个数据库连接并且开启一个事务于进入到CreationPerson这个方法,若没有任何异常抛出,接着提交这个事务于方法结尾时,若有异常被抛出,则会回滚这个事务。在这种机制下,所有数据库的操作在CreatePerson中,都成了单元性的了(工作单元)。

3.工作单元(Unit of work)

工作单元在后台替仓储和应用服务的方法工作。假如你想要控制数据库的连接和事务,你就需要直接操作工作单元。下面有两个直接使用的示例:

首要且最好的使用UnitOfWorkAttribute的方式如下:

  1. [UnitOfWork]
  2. public void CreatePerson(CreatePersonInput input)
  3. {
  4. var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
  5. _personRepository.Insert(person);
  6. _statisticsRepository.IncrementPeopleCount();
  7. }

因此,CreatePerson方法转变成工作单元并且管理数据库连接和事务,两个仓储对象都使用相同的工作单元。要注意,假如这是应用服务的方法则不需要添加UnitOfWork属性,见工作单元方法:第三章,3.3.5。

第二个示例是使用IUnitOfWorkManager.Begin(...)方法如下所示:

  1. public class MyService
  2. {
  3. private readonly IUnitOfWorkManager _unitOfWorkManager;
  4. private readonly IPersonRepository _personRepository;
  5. private readonly IStatisticsRepository _statisticsRepository;
  6.  
  7. public MyService(IUnitOfWorkManager unitOfWorkManager, IPersonRepository personRepository, IStatisticsRepository statisticsRepository)
  8. {
  9. _unitOfWorkManager = unitOfWorkManager;
  10. _personRepository = personRepository;
  11. _statisticsRepository = statisticsRepository;
  12. }
  13.  
  14. public void CreatePerson(CreatePersonInput input)
  15. {
  16. var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
  17.  
  18. using (var unitOfWork = _unitOfWorkManager.Begin())
  19. {
  20. _personRepository.Insert(person);
  21. _statisticsRepository.IncrementPeopleCount();
  22.  
  23. unitOfWork.Complete();
  24. }
  25. }
  26. }

你可以注入并且使用IUnitOfWorkManager,如上所示。因此,你可以创建更多的有限范围 (limited scope)的工作单元。在这个机制中,你通常可以手动调用Complete方法。如果你不调用,事务会回滚并且所有的异常都不会被储存。Begin方法被重写从而设置工作单元的选项。

这很棒,不过除非你有很好的理由,否则还是少用UnitOfWork属性。

工作单元
1.禁用工作单元(Disabling unit of work)

你或许会想要禁用应用服务方法的工作单元(因为它默认是启用的)。要想做到这个,使用UnitOfWorkAttribute的IsDisabled属性。示例如下:

  1. [UnitOfWork(IsDisabled = true)]
  2. public virtual void RemoveFriendship(RemoveFriendshipInput input)
  3. {
  4. _friendshipRepository.Delete(input.Id);
  5. }

平常时, 你不会需要这么做,这是因为应用服务的方法都应该是单元性且通常是使用数据库。在有些情况下,你或许会想要禁用应用服务的工作单元:

(1)你的方法不需要任何数据库操作且你不想要开启那些不需要的数据库连接
(2)你想要使用工作单元于UnitOfWorkScope类的有限范围内,如上所述
注意,如果工作单元方法调用这个RemoveFriendship方法,禁用被忽略且它和调用它的方法使用同一个工作单元。因此,使用禁用这个功能要很小心。同样地,上述程序代码工作的很好,因为仓储方法默认即为工作单元。

2.无事务的工作单元(Non-transactional unit of work)

工作单元默认上是具事务性的(这是它的天性)。因此,ABP启动/提交/回滚一个显性的数据库等级的事务。在有些特殊案例中,事务可能会导致问题,因为它可能会锁住有些数据列或是数据表于数据库中。在此这些情境下, 你或许会想要禁用数据库等级的事务。UnitOfWork属性可以从它的建构子中取得一个布尔值来让它如非事务型工作单元般工作着。示例为:

  1. [UnitOfWork(false)]
  2. public GetTasksOutput GetTasks(GetTasksInput input)
  3. {
  4. var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State);
  5. return new GetTasksOutput
  6. {
  7. Tasks = Mapper.Map<List<TaskDto>>(tasks)
  8. };
  9. }

建议可以这么做[UnitOfWork(isTransaction:false)]。(具有可读性并且明确)。

注意,ORM框架(像是NHibernate和EntityFramework)会在单一命令中于内部进行数据储存。假设你更新了一些的实体于非事务的UoW。即便于这个情境下所有的更新都会于单一数据库命令的工作单元尾部完成。但是,如果你直接执行SQL查询,它会立即被执行。

这里有一个非事务性UoW的限制。如果你已经位于事务性UoW区域内,设定isTransactional为false这个动作会被忽略。

使用非事务性UoW要小心,因为在大多数的情况下,数据整合应该是具事务性的。如果你的方法只是读取数据,不改变数据,那么当然可以采用非事务性。

3.工作单元调用其它工作单元(A unit of work method calls another)

若工作单元方法(一个贴上UnitOfWork属性标签的方法)调用另一个工作单元方法,他们共享同一个连接和事务。第一个方法管理连接,其它的方法只是使用它。这在所有方法都执行在同一个线程下是可行的(或是在同一个Web请求内)。实际上,当工作单元区域开始,所有的程序代码都会在同一个线程中执行并共享同一个连接事务,直到工作单元区域终止。这对于使用UnitOfWork属性和UnitOfWorkScope类来说都是一样的。如果你创建了一个不同的线程/任务,它使用自己所属的工作单元。

自动化的saving changes (Automatically saving changes)

当我们使用工作单元到方法上,ABP自动的储存所有变化于方法的末端。假设我们需要一个可更新person名称的方法:

  1. [UnitOfWork]
  2. public void UpdateName(UpdateNameInput input) {
  3. var person = _personRepository.Get(input.PersonId);
  4. person.Name = input.NewName;
  5. }

就这样,名称就被修改了!我们甚至没有调用_personRepository.Update方法。ORM框架会持续追踪实体所有的变化于工作单元内,且反映所有变化到数据库中。

注意,这不需要在应用服务声明UnitOfWork,因为它们默认就是采用工作单元。

4.仓储接口的GetAll()方法(IRepository.GetAll())

当你在仓储方法外调用GetAll方法, 这必定得有一个开启状态的数据库连接,因为它返回IQueryable类型的对象。这是需要的,因为IQueryable延迟执行。它并不会马上执行数据库查询,直到你调用ToList()方法或在foreach循环中使用IQueryable(或是存取被查询结果集的情况下)。因此,当你调用ToList()方法,数据库连接必需是启用状态。示例:

  1. [UnitOfWork]
  2. public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
  3. {
  4. //Get IQueryable<Person>
  5. var query = _personRepository.GetAll();
  6.  
  7. //Add some filters if selected
  8. if (!string.IsNullOrEmpty(input.SearchedName))
  9. {
  10. query = query.Where(person => person.Name.StartsWith(input.SearchedName));
  11. }
  12.  
  13. if (input.IsActive.HasValue)
  14. {
  15. query = query.Where(person => person.IsActive == input.IsActive.Value);
  16. }
  17.  
  18. //Get paged result list
  19. var people = query.Skip(input.SkipCount).Take(input.MaxResultCount).ToList();
  20.  
  21. return new SearchPeopleOutput { People = Mapper.Map<List<PersonDto>>(people) };
  22. }

在这里,SearchPeople方法必需是工作单元,因为IQueryable在被调用ToList()方法于方法本体内,并且数据库连接必须于IQueryable.ToList()被执行时开启。

一如GetAll()方法,如果需要数据库连接且没有仓储的情况下,你就必须要使用工作单元。注意,应用服务方法默认就是工作单元。

5.工作单元属性的限制(UnitOfWork attribute restrictions)

在下面情境下你可以使用UnitOfWork属性标签:

(1)类所有public或public virtual这些基于界面的方法(像是应用服务是基于服务界面)
(2)自我注入类的public virtual方法(像是MVC Controller和Web API Controller)
(3)所有protected virtual方法。
建议将方法标示为virtual。你无法应用在private方法上。因为,ABP使用dynamic proxy来实现,而私有方法就无法使用继承的方法来实现。当你不使用依赖注入且自行初始化类,那么UnitOfWork属性(以及任何代理)就无法正常运作。

选项
有许多可以用来控制工作单元的选项。

首先,我们可以在startup configuration中改变所有工作单元的所有默认值。这通常是用了我们模块中的PreInitialize方法来实现。

  1. public class SimpleTaskSystemCoreModule : AbpModule
  2. {
  3. public override void PreInitialize()
  4. {
  5. Configuration.UnitOfWork.IsolationLevel = IsolationLevel.ReadCommitted;
  6. Configuration.UnitOfWork.Timeout = TimeSpan.FromMinutes();
  7. }
  8.  
  9. //...other module methods
  10. }

方法
工作单元系统运作是无缝且不可视的。但是,在有些特例下,你需要调用它的方法。

SaveChanges:

ABP储存所有的变化于工作单元的尾端,你不需要做任何事情。但是,有些时候,你或许会想要在工作单元的过程中就储存所有变化。在这个案例中,你可以注入IUnitOfWorkManager并且调用IUnitOfWorkManager.Current.SaveChanges()方法。示例中以Entity Framework在储存变化时取得新增实体的Id。注意,当前工作单元是具事务性的,所有在事务中的变化会在异常发生时都被回滚,即便是已调用SaveChange。

事件
工作单元具有Completed/Failed/Disposed事件。你可以注册这些事件并且进行所需的操作。注入IUnitOfWorkManager并且使用IUnitOfWorkManager.Current 属性来取得当前已激活的工作单元并且注册它的事件。

你或许会想要执行有些程序代码于当前工作单元成功地完成。示例:

  1. public void CreateTask(CreateTaskInput input)
  2. {
  3. var task = new Task { Description = input.Description };
  4.  
  5. if (input.AssignedPersonId.HasValue)
  6. {
  7. task.AssignedPersonId = input.AssignedPersonId.Value;
  8.  
  9. _unitOfWorkManager.Current.Completed += (sender, args) => { /* TODO: Send email to assigned person */ };
  10. }
  11.  
  12. _taskRepository.Insert(task);
  13. }

原文地址:http://www.bkjia.com/Asp_Netjc/1136568.html

解析ABP框架中的事务处理和工作单元,ABP事务处理的更多相关文章

  1. Spring5源码解析-Spring框架中的单例和原型bean

    Spring5源码解析-Spring框架中的单例和原型bean 最近一直有问我单例和原型bean的一些原理性问题,这里就开一篇来说说的 通过Spring中的依赖注入极大方便了我们的开发.在xml通过& ...

  2. AutoMapper在ABP框架中的使用说明

    为了说明AutoMapper如何使用,我专门开设了一个专题来讲,如果您还没有查看该专题,请点击这里.既然系统地学习了AutoMapper,那么接下来就是该用它实战的时候了.今天,我们就来揭开AutoM ...

  3. drf框架中认证与权限工作原理及设置

    0909自我总结 drf框架中认证与权限工作原理及设置 一.概述 1.认证 工作原理 返回None => 游客 返回user,auth => 登录用户 抛出异常 => 非法用户 前台 ...

  4. [Abp 源码分析]六、工作单元的实现

    0.简介 在 Abp 框架内部实现了工作单元,在这里讲解一下,什么是工作单元? Unit Of Work(工作单元)模式用来维护一个由已经被业务事物修改(增加.删除或更新)的业务对象组成的列表.Uni ...

  5. ABP领域层知识回顾之---工作单元

    1. 前言   在上一篇博文中(http://www.cnblogs.com/xiyin/p/6842958.html) 我们讲到了ABP领域层的仓储.这边博文我们来讲 工作单元.个人觉得比较重要.文 ...

  6. ABP框架中一对多,多对多关系的处理以及功能界面的处理(1)

    在我们开发业务的时候,一般数据库表都有相关的关系,除了单独表外,一般还包括一对多.多对多等常见的关系,在实际开发过程中,需要结合系统框架做对应的处理,本篇随笔介绍基于ABP框架对EF实体.DTO关系的 ...

  7. [Abp vNext 源码分析] - 4. 工作单元

    一.简要说明 统一工作单元是一个比较重要的基础设施组件,它负责管理整个业务流程当中涉及到的数据库事务,一旦某个环节出现异常自动进行回滚处理. 在 ABP vNext 框架当中,工作单元被独立出来作为一 ...

  8. scrapy 基础组件专题(一):scrapy框架中各组件的工作流程

    Scrapy 使用了 Twisted 异步非阻塞网络库来处理网络通讯,整体架构大致如下(绿线是数据流向): Scrapy主要包括了以下组件: 引擎(Scrapy)用来处理整个系统的数据流处理, 触发事 ...

  9. 分析facebook的AsyncDisplayKit框架中的Transaction的工作原理

    在AsyncDisplayKit框架中有一个_ASAsyncTransaction模块,用于AsyncDiplayNode的异步事务,使用了dispatch_group实现. 主要目的是将operat ...

随机推荐

  1. Swift 3 迁移工作总结

    写在前面 Swift 3.0 正式版发布了差不多快一个月了,断断续续的把手上和 Swift 相关的迁移到了Swift 3.0.所以写点小总结. 背景 代码量(4万行) 首先,我是今年年初才开始入手 S ...

  2. 【C#】回调方法不通过object参数获得委托实例

    回调方法中几乎都会存在获取委托实例的需求,进而通过委托实例调用EndInvoke以得到异步执行的返回值.在我看过的相关文章中,获取委托实例的方法几乎都是同一个,就是向BeginInvoke的最后一个参 ...

  3. 不用插件 让Firefox 支持网页翻译

    1.进入http://labs.microsofttranslator.com/bookmarklet/ 2.在语言选择框的下拉列表中选择“简体中文” 3.右键点击“翻译”按钮,选择“将此链接加为书签 ...

  4. .NET 中获取调用方法名

    在写记录日志功能时,需要记录日志调用方所在的模块名.命名空间名.类名以及方法名,想到使用的是反射(涉及到反射请注意性能),但具体是哪一块儿还不了解,于是搜索,整理如下: 需要添加相应的命名空间: us ...

  5. 怎样解决PowerDesigner15出现许可证过期问题?

    今天打开PowerDesigner软件,出现许可证过期问题,怎样解决 1)打开软件安装路径(通过搜索打开文件所在路径或通过软件图标的属性打开)

  6. CentOS minimal网络设置

    CentOS minimal版本默认不启动网络,所以要自己配置. 配置过程: 编辑配置文件: vi /etc/sysconfig/network-script/ifcfg-eth0 需要更改两项 NM ...

  7. long和BigDecimal引发的管理思考

    关于long.double.BigDecimal在效率.可用性.灵活性等等方面的技术性讨论和测试其实在网上已经很多了,本文也不是打算讨论他们的实现的,其实笔者也曾在很长的职业生涯周期中一度拘泥于此.但 ...

  8. Python 获取一个对象的名字

    Note: 这里的对象是广义的,一个列表也算 有name的不在讨论之列, 因为可以直接输出 没有name的,可以参考如下代码,使用到的是globals() except_word是用于过滤掉那些不想要 ...

  9. js的querySelector跟querySelectorAll

    querySelector:document.querySelector('.className')------->可以选中.className的一个dom(注意只是一个) document.q ...

  10. scrollify.js 鼠标滚动

    在线实例 实例演示 使用方法 <! doctype html> <html> <head> <script> $(function() { $.scro ...