Click here to go to the ABP series articles General Catalogue
DDD-based Modern ASP.--ABP Series 12, ABP domain layer-working Unit (unit of work)
The ABP is "ASP. Boilerplate Project (ASP. NET Template project) "for short.
ABP's official website :http://www.aspnetboilerplate.com
ABP's Open source project on GitHub : https://github.com/aspnetboilerplate
common connection and transaction management methods
Connectivity and transaction management are one of the most important concepts for applications that use databases. When you open a database connection, when to start the transaction, how to release the connection ... That sort of.
As we all know,. NET uses connection pooling (connection pooling). Therefore, creating a connection is actually getting a connection from the connection pool, and this is done because there is a cost to creating a new connection. If no connection exists in the connection pool, a new connection object is created and added to the connection pool. When you release the connection, it is actually sending the connection object back to the connection pool. This is not a practical release. This mechanism is made up of. NET provided by the. Therefore, we should release the connection object after we have finished using it. This is the best practice.
In the application, there are two common towners to create/release a database connection:
The first method : Create a Connection object when the Web request arrives. (Application_BeginRequest this event in Global.asax), use the same connection object to handle all database operations, and close/release the connection at the end of the request (Application_ EndRequest event).
This is a simple but inefficient approach, for reasons:
- Perhaps this Web request does not require the operation of the database, but the connection is turned on. This is an inefficient way to use the connection pool.
- This may make the Web request run longer, and the database operation will require some execution. This is also an inefficient way to use connection pooling.
- This is possible for Web applications. If your application is a widnows Service, this may not be possible.
This is also the best scenario for using transactional database operations. If one operation fails, all operations are rolled back. Because a transaction locks up some data columns (the event data table) in the database, it must be short-lived.
The second method : Create a connection when needed (as long as it is before using it) and release it after using it. This is fairly efficient, but it's tedious and repetitive (create/release connections).
Connection and transaction management for ABP
ABP combines the two methods of connection management and provides a simple and efficient model.
Storage class (Repository classes)
Warehousing is the primary class for database operations. The ABP opens a database connection and enables a transaction when it enters the warehousing method. Therefore, you can safely use the connection to the warehousing method. After the warehousing method is finished, 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 mode, the warehousing method is modular (a unit of work unit). The ABP is fully automated in handling these actions. Here, there 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 NHibernate as the ORM framework. As shown above, there is no need to write program code for any database connection operation (session in NHibernate).
If the warehousing method calls another warehousing method (generally, if the unit of work method invokes another unit of work), the same connection and transaction are used. The first call to the warehousing method is responsible for managing connections and transactions, while the rest of the warehousing methods it calls are simply not managed.
Application Services (Application service classes)
An application service approach is also considered to use the work cell. If we have an application service method as follows:
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 add a person to use the man warehouse and use statistics warehousing to increase the total people quantity. Two warehouses share the same connection and transaction in this example, because this is an application service approach. The ABP opens a database connection and opens a transaction to enter the Creationperson method, and if no exception is thrown, and then commits the transaction at the end of the method, the transaction is rolled back if an exception is thrown. Under this mechanism, all database operations are Createperson (unit of work).
Working Unit (unit of work)
Work units work in the background for warehousing and application services. If you want to control database connections and transactions, you need to manipulate the unit of work directly. Here are two examples of direct use:
The first and best way to use Unitofworkattribute is as follows:
[Unitofwork] Public void Createperson (Createpersoninput input) { varnew person {Name = input. Name, EmailAddress = input. EmailAddress}; _personrepository.insert (person); _statisticsrepository.incrementpeoplecount ();}
As a result, the Createperson method transforms into a unit of work and manages database connections and transactions, and all two warehouse objects use the same unit of work. Note that if this is an application service method, you do not need to add the Unitofwork attribute, see the unit of work Method: Chapter Three, 3.3.5.
The second example is the use of Iunitofworkmanager.begin (...). The method is as follows:
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 use Iunitofworkmanager, as shown above. Therefore, you can create more limited range (limited scope) units of work. In this mechanism, you can usually call the complete method manually. If you do not call, the transaction is rolled back and all exceptions are not stored. The Begin method is overridden to set the options for the unit of work.
This is great, but unless you have a good reason, use the unitofwork attribute sparingly.
Unit of work Disabled unit of work (disabling unit of works)
You might want to disable the work unit of the app service method (because it is enabled by default). To do this, use Unitofworkattribute's Isdisabled property. Examples are as follows:
true )]publicvirtualvoid removefriendship (removefriendshipinput input) { _ Friendshiprepository.delete (input. ID);}
In normal times, you don't need to do this because the method of applying the service should be cell and usually use the database. In some cases, you might want to disable the work Unit for app service:
- Your method does not require any database operations and you do not want to open those database connections that you do not need
- You want to use the work cell within the limited range of the Unitofworkscope class, as described above
Note that if the unit of work method calls this removefriendship method, disabling is ignored and it uses the same unit of work as the method that calls it. Therefore, it is very careful to use disable this feature. In the same way, the above program code works well because the warehousing method defaults to the unit of work.
Working unit without transaction (non-transactional unit of work)
The unit of work is, by default, transactional (which is its nature). Therefore, the ABP initiates/submits/rolls back a dominant database-level transaction. In some special cases, a transaction can cause problems because it may lock some data columns or data tables in the database. In these scenarios, you might want to disable database-level transactions. The Unitofwork property can get a Boolean value from its constructor to make it work like a non-transactional unit of work. Examples are:
[Unitofwork (false)] Public gettasksoutput gettasks (gettasksinput input) { var tasks = _ Taskrepository.getallwithpeople (input. Assignedpersonid, input. State); return New Gettasksoutput { = mapper.map<list<taskdto>>(tasks) };}
It is advisable to do so [Unitofwork (Istransaction:false)]. (Readable and unambiguous).
Note that ORM frameworks (like NHibernate and EntityFramework) store data internally in a single command. Assume that you have updated some of the entities to non-transactional UOW. Even in this scenario, all updates are completed at the end of the unit of work for a single database command. However, if you execute the SQL query directly, it will be executed immediately.
Here is a non-transactional UOW limitation. If you are already in a transactional UOW area, setting IsTransactional to False will be ignored.
Be careful with non-transactional UOW, because in most cases data consolidation should be transactional. If your method simply reads the data and does not change the data, then of course you can use non-transactional.
Work unit calls other working units (A unit of work method calls another)
If the Work cell method (a method that is labeled with the Unitofwork property label) invokes another unit of work method, they share the same connection and transaction. The first method manages the connection, and the other method just uses it. This is possible with all methods executed under the same thread (or within the same Web request). In fact, when the unit of work area starts, all program code executes and shares the same connection transaction in the same thread until the unit of work zone terminates. This is the same for using the Unitofwork property and the Unitofworkscope class. If you create a different thread/task, it uses the unit of work that you belong to.
Automated saving changes (automatically saving changes)
When we use the work cell to the method, the ABP automatically stores all changes at the end of the method. Let's say we need a way to update the person name:
[Unitofwork] Public void updatename (Updatenameinput input) { var person = _personrepository.get (input. PERSONID); = input. NewName; }
In this way, the name is modified! We didn't even call the _personrepository.update method. The ORM framework keeps track of all changes in the entity within the unit of work and reflects all changes to the database.
Note that this does not need to be in the app service declaration Unitofwork, because they are by default a work unit.
GetAll () method for warehousing interfaces (Irepository.getall ())
When you use the GetAll method in the storage method, this must have an open database connection because it returns an object of type IQueryable. This is required because the IQueryable is deferred for execution. It does not immediately execute the database query until you call the ToList () method or use IQueryable in the Foreach loop (or in the case of a query result set). Therefore, when you call the ToList () method, the database connection must be enabled. Example:
[Unitofwork] Publicsearchpeopleoutput searchpeople (searchpeopleinput input) {//Get iqueryable<person> varquery =_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 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 is called by the ToList () method within the method body, and the database connection must be opened when the Iqueryable.tolist () is executed.
As with the GetAll () method, if a database connection is required and there is no warehousing, you must use the unit of work. Note that the app service method defaults to the unit of work.
Limitations of work Cell properties (unitofwork attribute restrictions)
You can use the Unitofwork property tag in the following scenario:
- Class all public or public virtual interface-based methods (like application services are service-based interfaces)
- Public virtual methods for self-injected classes (like MVC Controller and Web API Controller)
- All protected virtual methods.
It is recommended to mark the method as virtual. You cannot apply it on the private method. Because the ABP is implemented using dynamic Proxy, private methods cannot be implemented using inherited methods. When you do not use dependency injection and initialize the class yourself, the Unitofwork property (and any proxies) will not function properly.
Options
There are many options that you can use to control the unit of work.
First, we can change all the default values for all work units in the startup configuration. This is usually accomplished using the Preinitialize method in our module.
Public class simpletasksystemcoremodule:abpmodule{ publicoverride void Preinitialize () { = isolationlevel.readcommitted; = Timespan.fromminutes (+); } // ... other module methods}
Method
Work cell systems operate seamlessly and visually. However, in some cases, you need to invoke its method.
SaveChanges
ABP stores all the changes at the end of the unit of work, and you don't need to do anything. However, there are times when you might want to store all the changes in the process of a unit of work. In this case, you can inject Iunitofworkmanager and call the IUnitOfWorkManager.Current.SaveChanges () method. The ID of the new entity is obtained in the example with the Entity Framework when storage changes. Note that the current unit of work is transactional, and all changes in the transaction are rolled back when the exception occurs, even if SaveChange has been called.
Event
The work cell has a completed/failed/disposed event. You can register these events and perform the required actions. Inject Iunitofworkmanager and use the Iunitofworkmanager.current property to get the currently activated unit of work and register its events.
You may want to perform some program code successfully in the current unit of work. Example:
Public void createtask (Createtaskinput input) { varnew Task {Description = input. Description}; if (input. Assignedpersonid.hasvalue) { = input. Assignedpersonid.value; /* */ }; } _taskrepository.insert (Task);}
I hope that more domestic architects will be able to focus on the ABP project, and perhaps it will help you, perhaps with your participation, this project can develop better.
Welcome to add ABP Architecture Design Exchange QQ Group: 134710707
Click here to go to the ABP series articles General Catalogue
ABP (modern ASP. NET template Development Framework) series 12, ABP domain layer-working Unit (unit of work)