Introduction
Connection and transaction management is one of the most important concepts in an application that uses a database. When to open a connection, when to start a transaction, how to dispose the connection and so on.. ASP.NET Boilerplate manages database connection and transactions by it's unit of work system.
连接和事务管理是使用数据库的应用程序中最重要的概念之一。何时打开连接,何时启动事务,如何处理连接等等。ASP.NET样板的工作单元管理数据库连接和事务系统。
Connection & Transaction Management in ASP.NET Boilerplate
ASP.NET Boilerplate opens a database connection (it may not be opened immediately, but opened in first database usage, based on ORM provider implementation) and begins a transaction when entering a unit of work method. So, you can use connection safely in this method. At the end of the method, the transaction is commited and the connection is disposed. If the method throws any exception, transaction is rolled back and the connection is disposed. In this way, a unit of work method is atomic (a unit of work). ASP.NET Boilerplate does all of these automatically.
ASP.NET样板开放数据库连接(它可能不会立即被打开,但是打开了第一数据库的使用,基于ORM提供程序实现),开始一个事务,当进入一个工作单元的方法。因此,在这种方法中可以安全地使用连接。在方法结束时,事务提交和连接终止。如果该方法抛出任何异常,则回滚事务并设置连接。这样,工作单元的方法就是原子(工作单元)。ASP.NET的样板做所有这些自动。
If a unit of work method calls another unit of work method, both uses same connection & transaction. The first entering method manages connection & transaction, others use it.
如果一个工作单元方法调用另一个工作单元方法,则它们都使用相同的连接和事务。第一种输入方法管理连接和事务,其他使用它。
Conventional Unit Of Work Methods(常规的工作单元方法)
Some methods are unit of work methods by default:
- All MVC, Web API and ASP.NET Core MVC Controller actions.
- All Application Service methods.
- All Repository methods.
Assume that we have an application service method like below:
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(); } }
In the CreatePerson method, we're inserting a person using person repository and incrementing total people count using statistics repository. Both of repositories shares same connection and transaction in this example since application service method is unit of work by default. ASP.NET Boilerplate opens a database connection and starts a transaction when entering CreatePerson method and commit the transaction at end of the method if no exception is thrown, rolls back if any exception occurs. In that way, all database operations in CreatePerson method becomes atomic (unit of work).
在createperson方法,我们把 person repository和增加总的人使用统计库统计。在本例中,两个存储库共享相同的连接和事务,因为默认情况下应用程序服务方法是工作单元。ASP.NET样板打开一个数据库连接和启动一个事务进入createperson方法和提交事务结束的方法如果没有异常被抛出,回滚如果发生任何异常。这样,在CreatePerson,所有的数据库操作方法成为原子(工作单位)。
In addition to default conventional unit of work classes, you can add your own convention in PreInitialize method of your module like below:
除了默认的工作常规单元类,你可以添加你自己的惯例在分发你模块像下面的方法:
Configuration.UnitOfWork.ConventionalUowSelectors.Add(type => ...);
You should check type and return true if this type should be a conventional unit of work class.
Controlling Unit Of Work
Unit of work implicitly works for the methods defined above. In most cases you don't have to control unit of work manually for web applications. You can explicitly use it if you want to control unit of work somewhere else. There are two approaches for it.
工作单元隐式地工作于上面定义的方法。在大多数情况下,您不必手动控制Web应用程序的工作单元。如果想在其他地方控制工作单元,可以显式地使用它。它有两种方法。
UnitOfWork Attribute
First and preferred approach is using UnitOfWork attribute. Example:
[UnitOfWork] public void CreatePerson(CreatePersonInput input) { var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress }; _personRepository.Insert(person); _statisticsRepository.IncrementPeopleCount(); }
Thus, CreatePerson methods becomes unit of work and manages database connection and transaction, both repositories use same unit of work. Note that no need to UnitOfWork attribute if this is an application service method. Also see 'unit of work method restrictions' section.
因此,createperson方法成为工作单位和管理数据库连接和事务,都使用相同的单元库的工作。注意,如果这是一个应用服务的方法不需要UnitOfWork属性。还请参见“工作单元方法限制”部分。
There are some options of the UnitOfWork attribute. See 'unit of work in detail' section. UnitOfWork attribute can also be used for classes to configure all methods of a class. Method attribute overrides the class attribute if exists.
有一些选项UnitOfWork属性。参见“详细工作单元”部分。UnitOfWork属性也可用于类配置一个类的所有方法。如果存在,方法属性重写类属性。
IUnitOfWorkManager
Second appropaches is using the IUnitOfWorkManager.Begin(...) method as shown below:
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(); } } }
You can inject and use IUnitOfWorkManager as shown here (Some base classes have already UnitOfWorkManager injected by default: MVC Controllers, application services, domain services...). Thus, you can create morelimited scope unit of works. In this approach, you should call Complete method manually. If you don't call, transaction is rolled back and changes are not saved.
你可以注入和使用iunitofworkmanager如下所示(一些基类已经unitofworkmanager注入默认:MVC控制器,应用服务、域名服务…)。因此,您可以创建有限范围的装置作品。在这种方法中,您应该手动调用完整方法。如果不调用,事务将回滚,而更改不会保存。
Begin method has overloads to set unit of work options. It's better and shorter to use UnitOfWork attribute if you don't have a good reason.
开始方法有重载来设置工作单元选项。最好使用短UnitOfWork属性,如果你没有一个好的理由。
Unit Of Work in Detail
Disabling Unit Of Work
You may want to disable unit of work for conventional unit of work methods . To do that, use UnitOfWorkAttribute's IsDisabled property. Example usage:
[UnitOfWork(IsDisabled = true)] public virtual void RemoveFriendship(RemoveFriendshipInput input) { _friendshipRepository.Delete(input.Id); }
Normally, you don't want to do that, but in some situations you may want to disable unit of work:
- You may want to use unit of work in a limited scope with UnitOfWorkScope class described above.
- 你可能想要与unitofworkscope类以上所描述的在一个有限的范围内使用工作单元。
Note that if a unit of work method calls this RemoveFriendship method, disabling this method is ignored and it uses the same unit of work with the caller method. So, use disabling by carefully. Also, the code above works well since repository methods are unit of work by default.
注意,如果一个单位的工作方法调用此removefriendship法,这种方法是忽略,它使用相同的工作单元与来电者的方法禁用。所以,小心地禁用。此外,上面的代码很好地工作,因为默认情况下存储库方法是工作单元。
Non-Transactional Unit Of Work(非事务性工作单元)
A unit of work is transactional as default (by it's nature). Thus, ASP.NET Boilerplate starts/commits/rollbacks an explicit database-level transaction. In some special cases, transaction may cause problems since it may lock some rows or tables in the database. In this situations, you may want to disable database-level transaction. UnitOfWork attribute can get a boolean value in it's constructor to work as non-transactional. Example usage:
工作单元是事务性的缺省(根据其性质)。因此,ASP.NET开始/提交/回滚文件显式数据库事务。在某些特殊情况下,事务可能导致问题,因为它可能会锁定数据库中的某些行或表。在这种情况下,您可能希望禁用数据库级事务。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)
};
}
I suggest to use this attribute as [UnitOfWork(isTransactional: false)]. I think it's more readable and explicit. But you can use as [UnitOfWork(false)].
我建议使用此属性为[ UnitOfWork(istransactional:false)]。我认为它更易读,更明确。但是,你可以使用[ UnitOfWork(false)]。
Note that ORM frameworks (like NHibernate and EntityFramework) internally saves changes in a single command. Assume that you updated a few Entities in a non-transactional UOW. Even in this situation all updates are performed at end of the unit of work with a single database command. But if you execute an SQL query directly, it's performed immediately and not rolled back if your UOW is not transactional.
注意,ORM框架(如NHibernate和EntityFramework)内保存在一个单一的命令更改。假设你在一个非事务性更新UOW几个实体。即使在这种情况下,所有更新都是在工作单元的末端用一个数据库命令执行的。但如果你执行一个SQL查询,它立即执行,如果你不回滚UOW不是事务性的。
There is a restriction for non-transactional UOWs. If you're already in a transactional unit of work scope, setting isTransactional to false is ignored (use Transaction Scope Option to create a non-transactional unit of work in a transactional unit of work).
在非事务性UOWS中有限制。如果你已经在一个事务工作单元的范围,设置istransactional = false 被忽略(使用事务范围选项创建工作非事务性单元在一个事务工作单元)。
Use non-transactional unit of works carefully since most of the times it should be transactional for data integrity. If your method just reads data, not changes it, it can be safely non-transactional.
仔细地使用非事务性的工作单元,因为大多数时候它应该是数据完整性的事务处理。如果您的方法只读取数据,而不更改数据,则可以安全地非事务性地处理。
A Unit Of Work Method Calls Another
Unit of work is ambient. If a unit of work method calls another unit of work method, they share same connection and transaction. First method manages connection, others use it.
工作单元是环境。如果一个工作单元方法调用另一个工作单元方法,它们共享相同的连接和事务。第一种方法管理连接,另一些方法使用它。
Unit Of Work Scope
You can create a different and isolated transaction in another transaction or can create a non-transactional scope in a transaction. .NET defines TransactionScopeOption for that. You can set Scope option of the unit of work to control it.
您可以在另一个事务中创建不同的、独立的事务,也可以在事务中创建非事务作用域。transactionscopeoption。net定义为。您可以设置工作单元的范围选项来控制它。
Automatically Saving Changes(自动保存更改)
If a method is unit of work, ASP.NET Boilerplate saves all changes at the end of the method automatically. Assume that we need method to update name of a person:
[UnitOfWork] public void UpdateName(UpdateNameInput input) { var person = _personRepository.Get(input.PersonId); person.Name = input.NewName; }
That's all, name was changed! We did not even called _personRepository.Update method. O/RM framework keep track of all changes of entities in a unit of work and reflects changes to the database.
就这样,名字被改变了!我们甚至没有调用_personrepository.update方法。O/RM框架跟踪工作单元中所有实体的变化,并反映对数据库的更改。
Note that no need to declare UnitOfWork for conventional unit of work methods.
注意,不需要申明的工作方法常规单元UnitOfWork。
IRepository.GetAll() Method
When you call GetAll() out of a repository method, there must be an open database connection since it returns IQueryable. This is needed because of deferred execution of IQueryable. It does not perform database query unless you call ToList() method or use the IQueryable in a foreach loop (or somehow access to queried items). So, when you call ToList() method, database connection must be alive.
当你调用getall()仓库的方法,必须有一个开放的数据库连接,因为它返回IQueryable。这是因为延期执行IQueryable。它不除非你调用tolist()方法或在foreach循环使用IQueryable执行数据库查询(或者访问查询项目)。所以,当你调用tolist()方法、数据库连接必须活着。
Consider the example below:
[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) }; }
Here, SearchPeople method must be unit of work since ToList() method of IQueryable is called in the method body, and database connection must be open when IQueryable.ToList() is executed.
在这里,searchpeople方法必须工作单位自IQueryable tolist()方法的方法体中,与数据库的连接必须打开时执行tolist() IQueryable。
In most cases you will use GetAll method safely in a web application, since all controller actions are unit of work by default and thus database connection is available in entire request.
在大多数情况下,你会在一个Web应用程序安全的使用GetAll,因为所有的控制器操作的默认工作单元,因此可以在整个请求数据库连接。
UnitOfWork Attribute Restrictions(UnitOfWork属性限制)
You can use UnitOfWork attribute for;
- All public or public virtual methods for classes those are used over interface (Like an application service used over service interface).
- All public virtual methods for self injected classes (Like MVC Controllers and Web API Controllers).
- All protected virtual methods.
-
用于通过接口使用的类的所有公共或公共虚拟方法(如在服务接口上使用的应用程序服务)。
用于自注入类的所有公共虚拟方法(如MVC控制器和Web API控制器)。
所有受保护的虚拟方法。
It's suggested to always make the method virtual. You can not use it for private methods. Because, ASP.NET Boilerplate uses dynamic proxying for that and private methods can not be seen by derived classes. UnitOfWork attribute (and any proxying) does not work if you don't use dependency injection and instantiate the class yourself.
建议始终将方法虚拟化。不能用于私有方法。因为,ASP.NET样板使用动态代理,私有方法不能被派生类视。UnitOfWork属性(和任何代理)不工作如果你不使用依赖注入和实例化类自己的。
Options(选项)
There are some options can be used to change behaviour of a unit of work.
First, we can change default values of all unit of works in the startup configuration. This is generally done in PreInitialize method of our module.
首先,我们可以在启动配置中更改所有工作单元的默认值。这一般是在我们的模块的方法进行操作。
public class SimpleTaskSystemCoreModule : AbpModule { public override void PreInitialize() { Configuration.UnitOfWork.IsolationLevel = IsolationLevel.ReadCommitted; Configuration.UnitOfWork.Timeout = TimeSpan.FromMinutes(30); } //...other module methods }
Second, we can override defaults for a particular unit of work. For that, UnitOfWork attribute constructor and IUnitOfWorkManager.Begin method have overloads to get options.
其次,我们可以重写特定工作单元的缺省值。为此,UnitOfWork属性构造函数和方法重载iunitofworkmanager。开始获得方法。
And lastly, you can use startup configuration to configure default unit of work attributes for ASP.NET MVC, Web API and ASP.NET Core MVC Controllers (see their documentation).
最后,你可以使用启动配置配置ASP.NET MVC的工作属性的默认单位,Web API和ASP.NET核心的MVC控制器(见文献)。
Methods
UnitOfWork system works seamlessly and invisibly. But, in some special cases, you need to call it's methods.
系统可以无缝的和无形UnitOfWork。但是,在某些特殊情况下,您需要调用它的方法。
You can access to current unit of work in one of two ways:
您可以通过以下两种方式之一访问当前工作单元:
- You can directly use CurrentUnitOfWork property if your class is derived from some specific base classes (ApplicationService, DomainService, AbpController, AbpApiController... etc.)
- You can inject IUnitOfWorkManager into any class and use IUnitOfWorkManager.Current property.
- 你可以直接使用CurrentUnitOfWork属性,如果你的类是来源特性((ApplicationService, DomainService, AbpController, AbpApiController.…等)
- 你可以注入IUnitOfWorkManager 到你的任何类,只用 IUnitOfWorkManager.Current 属性
SaveChanges(保存修改)
ASP.NET Boilerplate saves all changes at end of a unit of work, you don't have to do anything. But, sometimes, you may want to save changes to database in middle of a unit of work operation. An example usage may be saving changes to get Id of a new inserted Entity in EntityFramework.
You can use SaveChanges or SaveChangesAsync method of current unit of work.
Note that: if current unit of work is transactional, all changes in the transaction are rolled back if an exception occurs, even saved changes.
ASP.NET样板保存最后一个工作单元所有的改变,你不需要做任何事。但是,有时您可能希望在工作单元的中间操作中保存对数据库的更改。一个例子使用可保存更改到新插入的ID在EntityFramework的实体。
你可以使用调用SaveChanges或现在的工作单位savechangesasync方法。
注意:如果当前工作单元是事务性的,那么如果异常发生,事务中的所有更改都会回滚,甚至保存更改。
Events
A unit of work has Completed, Failed and Disposed events. You can register to these events and perform needed operations. For example,yYou may want to run some code when current unit of work successfully completed. Example:
工作单元完成、失败和处理事件。您可以注册这些事件并执行所需的操作。例如,你可能想运行一些代码,当前的单位工作顺利完成。例子:
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); }