Abp公共连接和事务管理方法
Conection 和事务管理在使用数据库的应用中是一个最重要的概念。当你打开一个连接,开始一个事务,如何来处理这些连接等等。
您也许知道,.NET使用了连接池。所以,创建一个连接实际上是从连接池里得到一个连接,因为创建一个新的连接需要花费一段时间。
如果在池中没有空闲的连接,则会创建一个新的连接并添加到连接池中。当你释放一个连接,实际上是将该连接放回到连接池中。并没有完全释放
这种机制是.NET提供的即插即用的功能。所以在使用完连接后,应该立即释放掉,除非你在需要的时候才创建一个连接
在应用中创建/释放一个数据库连接有两个通用方法。
第一个方法:当一个web请求并开始创建一个新的连接(通常在global.aspx中的Application_BeginRequest方法中执行),在所有数据库操作使用相同的连接,并且在请求结束时关闭/释放
该连接(Application_EndRequest event).这中方式是简单的但并不是很好,为什么?
1、也许在一个请求中并没有数据库操作,但是这个连接会一直处于打开状态。这是使用连接池非常低效的一种方式。
2、在一次请求中,可能请求耗时很长时间而数据库操作只花了很短的时间,这也是使用连接池非常低效的一种方式。
3、在一个WEB应用中是可行的,但是在Windows服务时,这将是不可行的。
所以,以事务方式来处理数据库操作被认为是一种最佳实践。如果其中一个操作失败,其他所有操作就会回滚。因为事务可以锁定数据库中一些行(甚至表),它必须是短暂存在的。
第二种方法:当需要的时候创建一个连接(在使用它之前),这是最高效方式,不需要在每个地方乏味并重复的创建/释放连接。
在 ASP.NET Boilerplate中使用连接和事务管理
仓储类
执行数据库操作仓储主要的类。ASP.NET Boilerplate打开一个数据库连接(也许没有立即打开,但是在第一次使用数据库会打开,这要基于ORM实现方式,当进入到仓储方法会开启一个事务。所以,在仓储方法中,你可以使用一个安全的连接。在方法结束时,事务被提交同时连接被释放。如果仓储方法抛出任何异常,事务会回滚并且释放连接。这样的话,一个仓储方法是一个原子性质的(一个工作单元)。ASP.NET Boilerplate会自动处理这些。这里,有一个简单的仓储:
public class ContentRepository : NhRepositoryBase<Content>, IContentRepository
{
public List<Content> GetActiveContents(string searchCondition)
{
var query = from content in Session.Query<Content>()
where content.IsActive && !content.IsDeleted
select content;
if (!string.IsNullOrEmpty(searchCondition))
{
query = query.Where(content => content.Text.Contains(searchCondition));
}
return query.ToList();
}
}
这个列子使用了NHibernate作为ORM工具,从上面显示来看,没有看到数据库连接(Session in Nihernate)打开/关闭的代码。
如果一个仓储方法调用了另一个仓储方法(一般,如果一个工作单元方法调用另一个工作单元的方法),它们使用相同的连接和事务。
应用服务(Application Services)
一个应用服务方法也可以被认为一个工作单元,假设我们一个应用服务方法,如下:
public class PersonAppService : IPersonAppService
{
private readonly IPersonRepository _personRepository;
private readonly IStatisticsRepository _statisticsRepository;
public PersonAppService(IPersonRepository personRepository, IStatisticsRepository statisticsRepository)
{
_personRepository = personRepository;
_statisticsRepository = statisticsRepository;
}
public void CreatePerson(CreatePersonInput input)
{
var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
_personRepository.Insert(person);
_statisticsRepository.IncrementPeopleCount();
}
}
在CreatePerson方法中,我们使用person仓储插入一个Person同时使用一个统计仓储新增总人数。在这个列子中所有的仓储共享相同连接和事务,因为这是一个应用服务方法。ASP.NET Boilerplate打开一个数据库连接并且当进入到CreatePerson方法会开启一个事务,如果方法结束时没有任何异常抛出则会提交这个事务。正是因为这种方式,所有在CreatePerson方法里数据库操作将变成原子性的(一个工作单元)。
工作单元(Unit Of Work)
工作单元对于仓储和应用服务方法隐式有效(你不需要使用UnitOfWork属性显示调用),如果你想在其他地放操作数据库连接和事务,你必须显示的使用它们。这里有两个方法:
UnitOfWork 属性
首选的方式是使用UnitOfWorkAttribute属性,例如:
[UnitOfWork]
public void CreatePerson(CreatePersonInput input)
{
var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
_personRepository.Insert(person);
_statisticsRepository.IncrementPeopleCount();
}
这样,CreatePerson方法变成一个管理数据库连接和事务的工作单元。同时仓储使用相同的工作单元,注意在这是一个数据服务方法并不需要UnitOfWork属性、
IUnitOfWorkManager
第二种方式是用IUnitOfWorkManager.Begin(...)方法,如下所示:
public class MyService
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly IPersonRepository _personRepository;
private readonly IStatisticsRepository _statisticsRepository;
public MyService(IUnitOfWorkManager unitOfWorkManager, IPersonRepository personRepository, IStatisticsRepository statisticsRepository)
{
_unitOfWorkManager = unitOfWorkManager;
_personRepository = personRepository;
_statisticsRepository = statisticsRepository;
}
public void CreatePerson(CreatePersonInput input)
{
var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
using (var unitOfWork = _unitOfWorkManager.Begin())
{
_personRepository.Insert(person);
_statisticsRepository.IncrementPeopleCount();
unitOfWork.Complete();
}
}
}
你可以注入并使用IUnitOfWorkManager就想上面那样(如果你从ApplicationService类派生自的应用服务(ApplicationService)类,你可以直接使用CurrentUnitOfWork,如果没有,你应该注入IUnitOfWorkManager)。正因为如此,你可以创建更多限制作用域的工作单元,通常你应该调用Compete()方法,如果你没有调用,事务将会回滚,改变的数据将不会保存。
如果你没有很好的方式,你最好使用UnitOfWork属性。
工作单元详解。
关闭工作单元
如果你想关闭应用服务方法的工作单元(因为默认是开启的),为了实现这样,UnitOfWork的关闭属性(IsDisabled),例子如下:
[UnitOfWork(IsDisabled = true)]
public virtual void RemoveFriendship(RemoveFriendshipInput input)
{
_friendshipRepository.Delete(input.Id);
}
一般情况,你不要这样做,因为一个操作数据库应用服务方法必须保持原子性,
有些情况你可能想关闭工作单元:
1、你的方法没有执行任何数据库操作,你不想打开不必要的数据库连接。
2、如上面描述的那样,你想要在一个UnitOfWorkScope类的有限作用域内使用工作单元
注意上面,如果一个工作单元方法调用这个RemoveFriendship方法,关闭属性将被忽略,它使用调用方法相同的工作单元,所以,请小心使用关闭属性,
禁用事务的工作单元
工作单元默认是事务的,ASP.NET Boilerplate开启/提交/回滚一个显示的数据库级别的事务。在一些特殊情况中,事务可能会导致一些问题,因为在数据库中它也许会锁定一些行或者表。在这样的情况下,你想关闭数据库级别的事务,UnitOfWork属性可以在构造函数中设置一个布尔值,像下面这样:
[UnitOfWork(isTransactional: false)]
public GetTasksOutput GetTasks(GetTasksInput input)
{
var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State);
return new GetTasksOutput
{
Tasks = Mapper.Map<List<TaskDto>>(tasks)
};
}
我建议使用[UnitOfWork(isTransactional: false)] ,我认为这样更具有可读性和明确性,你也可以使用[UnitOfWork(false)].
注意ORM框架(像NHibernate和EntityFramework)内部使用了一个单一的命令来保存更改。假设你在非事务工作更新了一些实体,虽然在这种情况下所有的更新都是在工作单元结束时使用单一数据库命令来执行,但是你直接执行一个SQL查询,它会立马执行。
对于非事务工作单元这是一个限制,如果你已经在一个事务单元的工作域中,设置 isTransactional为False将被忽略。
自动保存变化
当我们为一个方法使用工作单元,ASP.NET Boilerplate会在方法结束时自动的保存所有变化,假定我们需要更新一个Person的姓名:
[UnitOfWork]
public void UpdateName(UpdateNameInput input)
{
var person = _personRepository.Get(input.PersonId);
person.Name = input.NewName;
}
就这样,姓名被改变!我们不需要调用_personRepository.Update()方法,ORM框架会跟踪工作单元中所有实体的变化并将数据变化反馈到数据库中。
注意:不要为应用服务方法定义UnitWork属性,因为他们默认是一个工作单元。
IRepository.GetAll() 方法
当你在仓储方法外调用GetAll(),这里必须打开一个数据库连接因为它返回IQueryable。这是必须的,因为IQueryable是延迟执行的。它没有执行任何数据库查询,除非你调用ToList()方法,或者在foreach语句中循环使用。所以,当你调用ToList()方式时,数据库连接被激活。像下面的列子:
[UnitOfWork]
public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
{
//Get IQueryable<Person>
var query = _personRepository.GetAll();
//Add some filters if selected
if (!string.IsNullOrEmpty(input.SearchedName))
{
query = query.Where(person => person.Name.StartsWith(input.SearchedName));
}
if (input.IsActive.HasValue)
{
query = query.Where(person => person.IsActive == input.IsActive.Value);
}
//Get paged result list
var people = query.Skip(input.SkipCount).Take(input.MaxResultCount).ToList();
return new SearchPeopleOutput { People = Mapper.Map<List<PersonDto>>(people) };
}
这里SearchPeople()方法必须设置工作单元,因为IQueryabled的ToList()方法体内调用了,当执行IQueryabled. ToList()时,数据库连接必须要打开。
像GetAll()这样的方法,在仓储外部数据库连接是需要的,你必须使用工作单元。
注意服务方法默认设置成工作单元的。
UnitOfWork 属性的限制
SaveChanges
ASP.NET Boilerplate会在工作单元结束保存所有的变化,你不需要做任何事情,但是,有时候,你也许想在工作单元中途将数据库改变。如果这样,你可以注入IUnitOfWorkManager并且调用IUnitOfWorkManager.Current.SaveChanges()方法,一个例子你要先保存改变来获取一个新插入的实体的ID。注意:如果当前的工作单元是事务的,如果遇到一个异常,在事务中所有的变化将会回滚,虽然保存变化了、
Events
一个工作单元有 Completed, Failed and Disposed事件,你可以注册这样事件来执行需要的操作,注入IUnitOfWorkManager,使用IUnitOfWorkManager.Current属性来激活工作单元,并注册它的事件。
当当前的工作单元成功执行,你也许想运行这些代码。例如:
public void CreateTask(CreateTaskInput input)
{
var task = new Task { Description = input.Description };
if (input.AssignedPersonId.HasValue)
{
task.AssignedPersonId = input.AssignedPersonId.Value;
_unitOfWorkManager.Current.Completed += (sender, args) => { /* TODO: Send email to assigned person */ };
}
_taskRepository.Insert(task);
}
Abp公共连接和事务管理方法的更多相关文章
- spring声明式事务管理总结
事务配置 首先在/WEB-INF/applicationContext.xml添加以下内容: <!-- 配置事务管理器 --> <bean id="transactionM ...
- Spring声明式事务配置管理方法
环境配置 项目使用SSH架构,现在要添加Spring事务管理功能,针对当前环境,只需要添加Spring 2.0 AOP类库即可.添加方法: 点击项目右键->Build Path->Add ...
- Spring声明式事务配置管理方法(转)
项目使用SSH架构,现在要添加Spring事务管理功能,针对当前环境,只需要添加Spring 2.0 AOP类库即可.添加方法: 点击项目右键->Build Path->Add libra ...
- Mybatis事务(一)事务管理方式
Mybatis管理事务是分为两种方式: (1)使用JDBC的事务管理机制,就是利用java.sql.Connection对象完成对事务的提交 (2)使用MANAGED的事务管理机制,这种机制mybat ...
- Mybatis事务管理
一.Mybatis事务 1.事务管理方式 Mybatis中的事务管理方式有两种: 1.JDBC的事务管理机制,即使用JDBC事务管理机制进行事务管理 2.MANAGED的事务管理机制,Mybatis没 ...
- Spring 事务管理tx,aop
spring tx:advice事务配置 2016年12月21日 17:27:22 阅读数:7629 http://www.cnblogs.com/rushoooooo/archive/2011/08 ...
- Spring 框架的事务管理
1. Spring 框架的事务管理相关的类和API PlateformTransactionManager 接口: 平台事务管理器(真正管理事务的类); TransactionDefinition 接 ...
- 基于 <tx> 和 <aop> 命名空间的声明式事务管理
环境配置 项目使用SSH架构,现在要添加Spring事务管理功能,针对当前环境,只需要添加Spring 2.0 AOP类库即可.添加方法: 点击项目右键->Build Path->Add ...
- LCN分布式事务管理(一)
前言 好久没写东西了,9月份换了份工作,一上来就忙的要死.根本没时间学东西,好在新公司的新项目里面遇到了之前没遇到过的难题.那遇到难题就要想办法解决咯,一个请求,调用两个服务,同时操作更新两个数据库. ...
随机推荐
- Java中if语句,嵌套使用 一道面试题
偶然看到这道题,起初还迷糊了,特此记录 int a = 100;if(a < 100) if(a < 200){ System.out.println("a < 200&q ...
- java基础5_数组
一 数组是一个多个相同数据的集合.数组的长度一旦定义,将不能改变.数组的下标是从0开始的,到数组的长度-1结束. 1.数组的声明,初始化 int[] a = new int[3]{1,2,3}; in ...
- HTML5-属性
点击图片打开详细介绍页面
- Resellerclub –Cpanel 主机中如何设置 index 缺省首页
管理面板,点击[文件管理器],如图: ],如图 1. 进入 cPanel 管理面板,点击[文件管理器],如图: 选中[主目录] 显示隐藏文件],如图: ],如图 2. 选中[主目录]和[显示隐藏文件] ...
- tcp/udp高并发和高吐吞性能测试工具
在编写一个网络服务的时候都比较关心这个服务能达到多少并发连接,而在这连接的基础上又能达到一个怎样的交互能力.编写服务已经是一件很花力气的事情,而还要去编写一个能够体现结果的测试工具就更加消耗工作时间. ...
- __defineGetter__ && __defineSetter__
看别人源码碰到的,做个备忘. //针对对象增加get\set方法,返回绑定函数的返回值 Date.prototype.__defineGetter__('getYear', function() {r ...
- C#中的线程一(委托中的异步)
C#中的线程一(委托中的异步) 一.同步委托 我们平时所用的委托以同步居多,我们编写一个方法和相关委托进行演示: publicdelegatevoid DoSomethingDelegate(stri ...
- ReentrantLock实现原理深入探究
前言 这篇文章被归到Java基础分类中,其实真的一点都不基础.网上写ReentrantLock的使用.ReentrantLock和synchronized的区别的文章很多,研究ReentrantLoc ...
- Java虚拟机2:Java内存区域及对象
几个计算机的概念 为以后写文章考虑,也为巩固自己的知识和一些基本概念,这里要理清楚几个计算机中的概念. 1.计算机存储单位 从小到大依次为位Bit.字节Byte.千字节KB.兆M.千兆GB.TB,相邻 ...
- 字体大小自适应纯css解决方案
viewpoint css3提供了一些与当前viewpoint相关的元素,vw,vh,vim等. “viewpoint” = window size vw = 1% of viewport width ...