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公共连接和事务管理方法的更多相关文章

  1. spring声明式事务管理总结

    事务配置 首先在/WEB-INF/applicationContext.xml添加以下内容: <!-- 配置事务管理器 --> <bean id="transactionM ...

  2. Spring声明式事务配置管理方法

    环境配置 项目使用SSH架构,现在要添加Spring事务管理功能,针对当前环境,只需要添加Spring 2.0 AOP类库即可.添加方法: 点击项目右键->Build Path->Add ...

  3. Spring声明式事务配置管理方法(转)

    项目使用SSH架构,现在要添加Spring事务管理功能,针对当前环境,只需要添加Spring 2.0 AOP类库即可.添加方法: 点击项目右键->Build Path->Add libra ...

  4. Mybatis事务(一)事务管理方式

    Mybatis管理事务是分为两种方式: (1)使用JDBC的事务管理机制,就是利用java.sql.Connection对象完成对事务的提交 (2)使用MANAGED的事务管理机制,这种机制mybat ...

  5. Mybatis事务管理

    一.Mybatis事务 1.事务管理方式 Mybatis中的事务管理方式有两种: 1.JDBC的事务管理机制,即使用JDBC事务管理机制进行事务管理 2.MANAGED的事务管理机制,Mybatis没 ...

  6. Spring 事务管理tx,aop

    spring tx:advice事务配置 2016年12月21日 17:27:22 阅读数:7629 http://www.cnblogs.com/rushoooooo/archive/2011/08 ...

  7. Spring 框架的事务管理

    1. Spring 框架的事务管理相关的类和API PlateformTransactionManager 接口: 平台事务管理器(真正管理事务的类); TransactionDefinition 接 ...

  8. 基于 <tx> 和 <aop> 命名空间的声明式事务管理

    环境配置 项目使用SSH架构,现在要添加Spring事务管理功能,针对当前环境,只需要添加Spring 2.0 AOP类库即可.添加方法: 点击项目右键->Build Path->Add ...

  9. LCN分布式事务管理(一)

    前言 好久没写东西了,9月份换了份工作,一上来就忙的要死.根本没时间学东西,好在新公司的新项目里面遇到了之前没遇到过的难题.那遇到难题就要想办法解决咯,一个请求,调用两个服务,同时操作更新两个数据库. ...

随机推荐

  1. PHP程序员如何突破技术瓶颈

    身边有几个做PHP开发的朋友,也接触到不少的PHP工程师,他们常疑虑自己将来在技术上的成长与发展,我常给他们一些建议,希望他们能破突自己,有更好的发展. 先明确我所指的PHP工程题,是指毕业工作后,主 ...

  2. 在WPF的WebBrowser控件中屏蔽脚本错误的提示

    在WPF中使用WebBrowser控件显示网页时,经常会报脚本错误的提示,如何屏蔽掉这些错误提示呢.方法是定义如下方法: public void SuppressScriptErrors(WebBro ...

  3. html5 localStorage实现表单本地存储

    随笔开头,我不得不吐槽,为什么我的随笔每次发布之后,都会在分分钟之内移出首页.好气啊!! 进入正题了,项目中有许多表单输入框要填写,还有一些单选复选框之类的.用户可能在填写了大量的信息之后,不小心刷新 ...

  4. 读bootstrap2.3.2有感1

    起步: 下载编译好的bootstrap2文件,百度新版jquery.js,并复制html模版(hello world)放置在同一目录,然后看了下官网上的范例网站,心里还是很激动啊~ <!DOCT ...

  5. 【C-数组】

    一.一维数组 ①.定义方式 类型说明符 数组名 [常量表达式]; 如:int array[10]; 注意: 1) 数组的类型实际上是指数组元素的类型.对于同一个数组,其所有元素的数据类型都是相同的. ...

  6. 【C++自绘控件】如何用GDI+来显示图片

    在我们制作一个应用软件的时候往往需要在窗口或控件中添加背景图.而图片不仅有BMP格式的,还有JPEG.PNG.TIFF.GIF等其它的格式.那么如何用jpg格式的图片来当背景呢? 这里用到了GDI+, ...

  7. 对初学者的MPLS 常见问题

    对初学者的MPLS 常见问题 2015年6月8日 16:04 阅读 186 问:什么是多协议标签交换 (MPLS)? 答:MPLS是一种数据包转发技术,该技术使用标签来做出数据转发决策. 利用MPLS ...

  8. [Linux-shell] AWK

    Go to the first, previous, next, last section, table of contents. Printing Output One of the most co ...

  9. FUND

    The Shaanxi Natural Science Plan Project of China Grant NO.: 2014JM8322

  10. On One Side Kolmogorov Type Inequalities

    Let \(X_1,X_2,\ldots,X_n\) be independent random variables. Denote \[S_n=\sum_{i=1}^n X_i.\] The  we ...