DDD-based. NET Development framework-ABP Working Unit (unit of work)

Source: Internet
Author: User
Tags app service

Back to ABP Series

The ABP is "ASP. Boilerplate Project (ASP. NET Template project) "for short.

ASP. Boilerplate is a new starting point for developing modern web applications with best practices and popular technologies, and is designed to be a common Web application framework and project template.

ABP's official website: http://www.aspnetboilerplate.com

ABP Official Document: Http://www.aspnetboilerplate.com/Pages/Documents

Open source project on GitHub: Https://github.com/aspnetboilerplate

I. Public connection and transaction management methods

Connectivity and transaction management are among the most important concepts in applications that use databases. When to open a connection, when to start a transaction, how to release the connection, and so on.

As you probably already know, net uses a connection pool. Therefore, creating a connection is actually getting a connection from the connection pool because creating a connection is expendable. If no connection is available in the connection pool, a new connection is created and the connection is joined to the connection pool. When you release a connection, the connection is actually sent back to the connection pool and is not completely freed. This mechanism is. NET provides the immediately available functionality. Therefore, after we have used a connection, we should release it immediately and create a new connection when needed. In short, best practices remember that these eight words are sufficient: open as late as possible and release early .

There are typically 2 ways to create or release a database connection in an application.

The First approach : Create a connection when the Web request starts (in the Global.asax Application_BeginRequest event) and use the same connection for all database operations. and closes or releases the connection at the end of the request (Application_EndRequest). This method is simple but not efficient enough. Why

    • There may not be a database operation in a request, but the connection is already open. This causes invalid use of the connection pool.
    • In a single request, the request may take a long time and the database operation takes only a short time, which also results in invalid connection pool usage
    • This is only possible in Web applications. If the app is a Windows service, it might not be implemented.

Performing database operations in a transactional manner is considered a best practice. If an operation fails, all operations are rolled back. Because a transaction can lock some rows (even tables) in a database, it must be short lived.

The second method : Create a connection when needed (only before use) and close immediately after use. This is the most effective, but creating or releasing connections everywhere is a tedious task.

II. connectivity and transaction management in the ABP

The ABP combines both approaches and provides a simple yet effective model.

1. Storage type

The primary class for warehouse execution database operations. When entering a warehousing method, the ABP opens a database connection (may not be opened immediately, but must be open when the database is first used, depending on the ORM provider's implementation) and begins a transaction. Therefore, the connection can be safely used in a warehousing method. At the end of the method, the transaction is committed and the connection is freed. If the warehousing method throws any exceptions, the transaction is rolled back and the connection is freed. In this way, the warehousing method is atomic (a unit of work). The ABP is automatically processed for these. Here is a simple warehousing:

 Public classContentrepository:nhrepositorybase<content>, icontentrepository{ PublicList<content> getactivecontents (stringsearchcondition) {        varquery = fromContentinchSession.query<content>()                    whereContent. IsActive &&!content. IsDeletedSelectcontent; if(!string. IsNullOrEmpty (searchcondition)) {query= Query. Where (content =content.        Text.contains (searchcondition)); }        returnquery.    ToList (); }}

This example uses the NHibernate as an ORM. As shown above, there is no code to write the database connection (the session in NHibernate) to open or close.

If a warehousing method invokes other warehousing methods (typically, if a unit of work invokes other units of work), then they share the same connection and transaction. The first way to enter is to manage connections and transactions, and other methods use the same connections and transactions.

2. Application Services

An application service is also considered a unit of work. Let's say we have an app service like the following:

 Public classpersonappservice:ipersonappservice{Private ReadOnlyipersonrepository _personrepository; Private ReadOnlyistatisticsrepository _statisticsrepository;  PublicPersonappservice (ipersonrepository personrepository, istatisticsrepository statisticsrepository) {_personR Epository=personrepository; _statisticsrepository=statisticsrepository; }     Public voidCreateperson (Createpersoninput input) {varperson =Newperson {Name = input. Name, EmailAddress =input.        EmailAddress};        _personrepository.insert (person);    _statisticsrepository.incrementpeoplecount (); }}

In the Createperson method, we used the person warehouse to insert a person and increased the total number of people using statistics warehousing. In this example, the two warehouses share the same connections and transactions because they are in an application service method. The ABP opens a database connection and begins a transaction when entering the Createperson method, and if no exception is thrown the transaction is committed at the end of the method, and if any exception occurs, it will be rolled back. In this way, all the database operations in the Createperson method are atomic (unit of work).

3. Unit of work

The unit of work is implicitly valid for warehousing and application service methods. If you want to control database connections and transactions elsewhere, you can use it explicitly.

Unitofwork Features:

The most popular way is to use Unitofworkattribute. For example:

[Unitofwork]  Public void Createperson (Createpersoninput input) {    varnew person {Name = input. Name, EmailAddress = input. EmailAddress};    _personrepository.insert (person);    _statisticsrepository.incrementpeoplecount ();}

In this way, the Createperson method becomes a unit of work and manages database connections and transactions, and two warehouses use the same unit of work, noting that if this is an application service approach, the Unitofwork feature is not required.

Iunitofworkmanager:

The second method is to use the Iunitofworkmanager.begin () method, for example:

 Public classmyservice{Private ReadOnlyIunitofworkmanager _unitofworkmanager; Private ReadOnlyipersonrepository _personrepository; Private ReadOnlyistatisticsrepository _statisticsrepository;  PublicMyService (Iunitofworkmanager unitofworkmanager, IPersonRepository personrepository, IStatisticsRepository Statisticsrepository) {_unitofworkmanager=Unitofworkmanager; _personrepository=personrepository; _statisticsrepository=statisticsrepository; }     Public voidCreateperson (Createpersoninput input) {varperson =Newperson {Name = input. Name, EmailAddress =input.        EmailAddress}; using(varUnitofwork =_unitofworkmanager.begin ())            {_personrepository.insert (person);            _statisticsrepository.incrementpeoplecount ();        Unitofwork.complete (); }    }}

You can inject and then use Iunitofwork, as demonstrated here (if your app inherits from the Applicationservice class, you can use the Currentunitofwork property directly.) If not, you need to inject Iunitofworkmanager first). In this way, you can create more scoped units of work. In this way, you should call the complete method manually. If there is no call, the transaction is rolled back and the change is not saved.

The Begin method has many overloads to set the unit of work option.

If you don't find a good reason, it is recommended that you use the Unitofwork feature because the shorter the code is, the better.

Three, the work unit detailed

1. Close the unit of work

Sometimes you might want to close the unit of work for the Application service method (because it is on by default), and you can use the Unitofworkattribute isdisabled property. Use the following:

true )]publicvirtualvoid  removefriendship (removefriendshipinput input) {    _ Friendshiprepository.delete (input. ID);}

Normally, you do not need to close the data unit because the application service method should be atomic and generally use the database. But there are some exceptions that make you want to close the work unit of the Application service method:

    • method does not perform any database operations and you do not want to open a database connection that is not necessary.
    • As described above, you want to use a work cell within a limited scope of a Unitofworkscope class.

Note: If a unit of work method calls this Removefriendship method, the function of the latter's shutdown unit will be invalidated and the same unit of work will be used with the caller's method. Therefore, use the close function of the unit of work with care.

2. Non-transactional unit of work

The unit of work is the transaction by default (essentially). Therefore, the ABP starts with commit, and rolls back an explicit database-level transaction. In some special situations, a transaction can cause problems because it may lock up some rows or tables in the database. In this case, you may want to close the database-level transaction. The Unitofwork attribute can obtain a Boolean value in the constructor to work in a non-transactional form. Use the following:

false )] public gettasksoutput gettasks (gettasksinput input) {    var tasks = _ Taskrepository.getallwithpeople (input. Assignedpersonid, input. State);     return New Gettasksoutput            {                = mapper.map<list<taskdto>>(tasks)            };}

[Unitofwork (Istransactional:false)] is recommended because it is more readable, but you can also use [Unitofwork (false)].

Note An ORM framework (such as EF and NH) uses a single command inside to save changes. Suppose you update some entities with a non-transactional UOW (unit of work), even in which case all updates are executed as a single database command at the end of the unit of work. But if you execute a SQL query directly, it executes immediately.

There is a limit to non-transactional UOW. If you are already in the scope of a unit of work for a transaction, setting IsTransactional to False will be ignored.

Use a non-transactional unit of work with caution, because most of the time the integration of the data is transactional. If your method is just reading data, you do not need to change the data, of course, the method can be non-transactional.

3. Work Cell method call other

If a method of a unit of work (using the method declared by the Unitofwork attribute) invokes the method of another unit of work, they share the same connection and transaction. The first method manages the connection, and the other methods use the connection. This is true for methods that run on the same thread (the same request is for the web app). In fact, when a unit of work scope starts, all code executed on the same thread shares the same connection and transaction until the end of the working cell scope. This is true for both the Unitofwork feature and the Unitofworkscope class.

4. Unit of work Scope

In other transactions, you can create a different and isolated transaction, or you can create a non-transactional scope in a transaction. NET defines transactionscopeoption, you can set scope options for the unit of work.

5. Auto-Save

When we use a unit of work for a method, the ABP automatically saves all changes at the end of the method. Let's say we have a way to update the name of a person:

[Unitofwork]  Public void updatename (Updatenameinput input) {    var person = _personrepository.get (input. PERSONID);     = input. NewName;}

You have to do so much, and the name of the person changes. We don't even have to call the _personrepository.update method. The ORM framework tracks all changes to entities in the unit of work and responds to the database with changes.

Note It is not necessary to declare the Unitofwork attribute for app service methods, because they are already units of work by default.

6. Irepository.getall () method

When GetAll () is called outside of a warehousing method, an open database connection must exist because GetAll returns IQueryable, and IQueryable delays execution. The database query is not actually executed until the ToList () method is called or the IQueryable is used in the Foreach loop. Therefore, when you call the ToList () method, the database connection must be alive (alive).

Consider the following example:

[Unitofwork] Publicsearchpeopleoutput searchpeople (searchpeopleinput input) {//back to Iqueryable<person>    varquery =_personrepository.getall (); //Add some filtering    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 a list of page results    varPeople =query. Skip (input. Skipcount). Take (input. Maxresultcount).    ToList (); return Newsearchpeopleoutput {people = mapper.map<list<persondto>>(People)};}

Here, the Searchpeople method must be a unit of work, because IQueryable's ToList () is called in the method body, and when execution Iqueryable.tolist () executes, the database connection must be an open state.

Just like the GetAll () method, if you need a database connection outside of warehousing, you must use a unit of work. The app service method defaults to the unit of work.

7. Limitations of Unitofwork characteristics

The unitofwork can be used for several conditions:

    • The public or public virtual methods of all classes used for the interface (such as the method used for the application service class for the service interface).
    • All public virtual (such as the MVC Controller and Web API Controller) for the self-injected class.
    • All the protected virtual methods.

It is recommended to always declare a method as virtual, but not for the private method. Because the ABP has a dynamic proxy private to the virtual method, the private method cannot be accessed by the derived class. If you do not use dependency injection and instantiate a class, then the Unitofwork attribute (and any proxy) will not work.

Iv. Options

There are many options that you can use to change the behavior of a work cell.

First, we can change the default values for all work units in the launch configuration. This is usually handled in the Preinitialize method of the module.

 Public class simpletasksystemcoremodule:abpmodule{    publicoverride void  Preinitialize ()    {        = isolationlevel.readcommitted;         = Timespan.fromminutes (+);    }     // ... Other module Methods }

Second, we can override the default values for a specific unit of work. For example, the constructors for the Unitofwork attribute and the Begin method of the Iunitofworkmanager have overloads to get the options.

V. Methods

The Unitofwork system works seamlessly and invisibly. But on some occasions, you need to invoke its method.

SaveChanges:

The ABP saves all changes at the end of the work unit, and we don't have to do anything at all. But sometimes you might want to save changes to the database in the middle of a work cell operation. In this case, you can inject Iunitofworkmanager and then call the IUnitOfWorkManager.Current.SaveChanges () method. Note: If the current unit of work is transactional, any changes in the transaction will be rolled back if an exception occurs, even if a saved change is made.

VI. Events

The unit of work has completed,failed and disposed events. You can register these events and then perform the required actions. By injecting Iunitofworkmanager and then using the Iunitofworkmanager.current property to get the active unit of work, then register to its event.

When the current unit of work completes successfully, you may want to run some code, here is an example:

 Public void createtask (Createtaskinput input) {    varnew Task {Description = input. Description};     if (input. Assignedpersonid.hasvalue)    {        = input. Assignedpersonid.value;         /* TODO: Send a message to the person who distributed it */  };    }    _taskrepository.insert (Task);}

DDD-based. NET Development framework-ABP Working Unit (unit of work)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.