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月份换了份工作,一上来就忙的要死.根本没时间学东西,好在新公司的新项目里面遇到了之前没遇到过的难题.那遇到难题就要想办法解决咯,一个请求,调用两个服务,同时操作更新两个数据库. ...
随机推荐
- 基于VC的声音文件操作(四)
(五)读取wav的实例 跟据WAVE文件的格式,实现了读取双声道立体声数据的例子如下: BYTE * GetData(Cstring *pString) //获取声音文件数据的函数,pString参数 ...
- java基础2_算术运算
一 算术运算符,包括+,-,*,/,%, 1. 如果在一个算术运算中有int,double,float那么最终运算的结果是double,那么也就是说参与运算的类型和得到的结果:结果一定是参与运算的精度 ...
- SAP DataServices企业定制培训
No. Item Remark 1 Dataservices overview DS概述 2 SAP Dataservices 安装与配置 DS的配置 3 DS ETL开发<1> for ...
- 一个demo让你彻底理解Android触摸事件的并发
注:本文涉及的demo的地址:https://github.com/absfree/TouchDispatch 1. 触摸动作及事件序列 (1)触摸事件的动作 触摸动作一共有三种:ACTION_DOW ...
- map() 函数
map()函数 map()是 Python 内置的高阶函数,它接收一个函数 f 和一个 list,并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回. 例如,对于li ...
- linux上创建ftp服务器下载文件///使用AWS服务器作为代理,下载sbt相关的包
最近觉得自己下载有些jar的速度太慢了,就在aws上下好了,然后转到我电脑上来,在aws上开了ftp服务器.结果就倒腾了一上午,作个记录,以便后面查看. 1.安装vsftpd yum -y insta ...
- win下修改mysql默认的字符集以防止乱码出现
环境:win8.1+mysql5.6.11+xampp(v3.2.1) 默认的编码如下 查看方式: show variables like 'character%'; 结果: 从以上信息可知数据库的编 ...
- 我的ORM之十三 -- 性能参数
我的ORM索引 测试环境 台式机: 主板:映泰Z77 CPU:i5 3470(3.2GHz) 内存:DDR3 1600 8G(单条) 硬盘:创见 SSD 256G ORM从过程上,可以分两个大的部分: ...
- 【Bugly干货分享】一起用 HTML5 Canvas 做一个简单又骚气的粒子引擎
Bugly 技术干货系列内容主要涉及移动开发方向,是由Bugly邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 前言 好吧,说是“粒子引擎”还是大言不 ...
- ubuntu git 使用
apt-get install git//ubuntu安装git mkdir -p /var/www/gitProj //创建文件夹 cd /var/www/gitProj //进入文件夹 git i ...