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月份换了份工作,一上来就忙的要死.根本没时间学东西,好在新公司的新项目里面遇到了之前没遇到过的难题.那遇到难题就要想办法解决咯,一个请求,调用两个服务,同时操作更新两个数据库. ...
随机推荐
- 命令行解析Crash文件
做了快两年的开发了,没有写过博客,最近公司app上架,程序崩溃被拒绝了,可是给的crash文件,又看不出哪里的问题,网上各种搜,终于找到了解决的办法,想想还是写个博客吧,希望给哪些也遇到这类问题的朋友 ...
- nginx日志格式来分析网站访问速度与瓶颈
参考地址:http://www.ttlsa.com/nginx/nginx-modules-ngx_http_log_request_speed/ 查看nginx 安装模块和配置 /usr/local ...
- MyEclipse调用Matlab打包函数
本文部分内容参考了http://www.360doc.com/content/15/1103/16/1180274_510463048.shtml 一.检查Java环境 对于已经装上JAVA环境的计算 ...
- iptables Data filtering详解
内容简介防火墙的概述iptables简介iptables基础iptables语法iptables实例案例详解(一)防火墙的简介防火墙是指设置在不同网络或网络安全域之间的一系列部件的组合,它能增强机构内 ...
- java 多线程--- Thread Runnable Executors
java 实现多线程的整理: Thread实现多线程的两种方式: (1)继承 Thread类,同时重载 run 方法: class PrimeThread extends Thread { long ...
- entityframework使用oracle的几个小问题
问题一:Operation is not valid due to the current state of the object 生成的edmx文件有问题,解决方法参考链接 问题二:InvalidO ...
- 【Python】调用WPS V9 API,实现PPT转PDF
WPS 的API,即COM,主要分为V8与V9两个版本,网上容易查到的例子,都是V8的. 现在官网上可以下载的,2013抢鲜版,就是V9的API. Python 调用COM 需要安装 Python f ...
- 获取机器安装.NET版本的几种方式
当调查应用程序问题时,通常需要先确认目标机器所安装的 .NET Framework 的版本.可以通过如下方式来确认版本号: 通过控制面板安装程序查询 通过查询注册表获取版本信息 通过查看安装目录获取版 ...
- [.net 面向对象编程基础] (4) 基础中的基础——数据类型转换
[.net面向对象编程基础] (4)基础中的基础——数据类型转换 1.为什么要进行数据转换? 首先,为什么要进行数据转换,拿值类型例子说明一下, 比如:我们要把23角零钱,换成2.30元,就需要把整形 ...
- 如何开始DDD(完)
连续写了两篇文章,这一篇我想是序的完结篇了.结合用户注册的例子再将他简单丰富一下.在这里只添加一个简单需求,就是用户注册成功后给用户发一封邮件.补充一下之前的代码 public class Domai ...