(Translator: One of the difficulties with using EF to develop applications is their DbContext lifecycle management, and whether your management strategy will support the need for upper-level services to use stand-alone transactions, nested transactions, parallel execution, asynchronous execution, etc.). Mehdi El Gueddari has done in-depth research and excellent work and has written an excellent article, and now I will share it with you in Chinese. Since the original text is too long, the translated article will be divided into four articles. This is the first article you see. Original address: http://mehdi.me/ambient-dbcontext-in-ef6/) about DbContext
This is not the first article to write about how to manage the lifecycle of DbContext based on EntityFramework applications.
For many applications, the solution presented above (basically using the DI container to inject a dbcontext instance of each Web request) works well. Their advantages are also very simple-at least at first glance.
However, for certain applications, these solutions are inherently flawed and have a problem. That is, some features become impossible to implement or need to resort to adding complex structures or adding ugly algorithms to handle dbcontext creation and management.
Here are some examples of real-world applications that have prompted me to rethink the way I manage dbcontext:
An application contains asp.net mvc and WEBAPI built Web applications. It may also contain background services built by console and Windows services, including Task Scheduler services and those that handle messages from MSMQ and RABBITMQ. Most of the articles I link above assume that all services are running in the context of the Web request, but this is clearly not the case here.
It reads and writes data from multiple databases, including a primary database, a database, a reporting database, and a log database. Its domain model is divided into several separate groups, each with its own dbcontext type, assuming that only one dbcontext type is here, in any case, is unworkable.
It relies heavily on Third-party remote APIs, such as Facebook,twitter or LinkedIn's APIs, but these APIs do not support transactions. Many user actions require several remote API calls to be made before returning results to the user. Most of the articles I link above assume that "a Web request contains only one business transaction" and that it should either be submitted or rolled back, and it is clear that this rule does not apply here. Because a remote API call failure does not mean that we can magically "rollback" the results of any of the remote API calls that have been completed before. (For example, when you use the Facebook API to submit a status update to Facebook, you can't roll back a status update to Facebook because subsequent operations fail). Therefore, in such applications, a user action often requires us to perform multiple business transactions, each of which can be persisted independently. (You might argue that there might be some way to redesign the whole system to avoid this--of course it's possible.) But if the program is designed that way, and it works well, we have to deal with it. )
Many services are heavily parallelization, either through asynchronous IO or, more often, through the TPL Library, Task.run () or Parallel.Invoke () methods are used to distribute tasks to multiple threads for execution. In this scenario, most of the common methods of managing DbContext are not used.
In this article, I'll delve into the various parts of DbContext lifecycle management. We will see the pros and cons of several common strategies to address this problem. Finally, we will summarize a strategy for managing the DbContext lifecycle, which can address the challenges mentioned above.
Of course, there is no perfect solution. But at the end of the article, you will have the tools and knowledge to make informed decisions about your particular application.
This is a very long and detailed article, it takes a certain amount of time to read and digest. For entityframework based applications, the strategy you choose to use to manage the DbContext lifecycle will be an important decision-it will be correct, maintainable, and extensible for your program
Have a significant impact on sex. So it's worth taking some time to think it over before making a choice. the terminology to be used in this article
In this article, I'll refer to the word "service" many times, not as a remote service (rest or other), but rather as a service object--where you often place business logic implementations--that are responsible for executing business rules and defining business transaction boundaries.
Of course, your code base may use different names, depending on the design pattern you use to create the application architecture. Therefore, what I call "service" may also be called "Workflow (Workflow)", "Coordinator (orchestrator)", "Performer (executor)", "Interactor", "command", " Processor (handler) "or some other name.
Not to mention that there are many applications that do not define a suitable place to store business logic at all, but simply place it in a location, such as a controller (controllers) in an MVC application.
But it's not about the topic we're talking about--when I say "service," which is where the business logic is stored, it can be a random controller (Controller) method or a service class in a layered architecture. key points to consider
When developing or evaluating a management DbContext lifecycle strategy, it is important to remember the key scenarios and features that it supports.
Here are some of the things I think are important to most programs. your service must control the boundaries of the business services (but not necessarily the dbcontext lifecycle)
The main difficulty in managing DbContext is to understand the differences and associations between the life cycle of the dbcontext and the lifecycle of the business transaction.
DbContext implements the work Unit mode:
Maintain a list of business-affected objects and coordinate the resolution of changes and concurrency issues.
In practice, when you use a DbContext instance to load, update, add, and delete a persistent entity, it tracks these changes in memory. Unless you call the SaveChanges () method, it will not persist these changes to the underlying database.
A service method, as defined above, will be responsible for defining the boundaries of the business transaction.
The result is this:
A service method must run through the entire business transaction with the same DbContext instance, so that all modifications to the persisted object can be tracked, and the modifications are either submitted to the underlying database or rolled back in an atomic manner.
Your service must be the only component in the system that is responsible for calling the Dbcontext.savechanges () method. What happens if other modules of the system (such as the Warehousing (repsitory) method) call the SaveChanges () method. It will lead to a partial change in the submission, leaving your data in an inconsistent state.
The SaveChanges () method must be called at the end of each business transaction. If the call in the middle of a business transaction can also result in inconsistent, partially committed state.
A DbContext instance is one that can span multiple (sequential) business transactions. Once a business transaction has been completed and invoking the Dbcontext.savechanges () method to persist all modifications, it is entirely possible for us to reuse the same dbcontext in the next business transaction.
That is, the life cycle of the DbContext instance is not necessarily tied to a separate business transaction lifecycle. The advantages and disadvantages of managing the lifecycle of DbContext instances independently of the business transaction lifecycle
Example
A common scenario for managing the lifecycle of DbContext instances independently of the business transaction lifecycle is Web applications. The common approach is that the DbContext instance is created at the start of each request and then invoked by all services during the execution of the Web request and released at the end of the request.
Advantages
Here are two important reasons why you should separate the life cycle management of a Dbcontex instance from business transaction lifecycle management.
Potential for performance improvement. Each DbContext instance maintains a level-one cache of objects loaded from the database. When you query an entity through a primary key, DbContext takes precedence over the first-level cache and attempts to query from the database when it is not available. Depending on your data query pattern, reusing the same dbcontext in multiple sequential business transactions results in fewer database queries due to a level caching.
More use of deferred loading scenarios. If the service returns a persisted object (instead of view models or other DTOs) then you can take advantage of the deferred loading of these objects, and the lifecycle of the DbContext instance that loads those entities must go beyond the scope of the business transaction. If the service releases the DbContext instance before returning the entity object, any deferred load properties that are triggered on those returned objects will fail (the use of deferred loading is another argument, which is not discussed in depth). In our Web application example, deferred loading is often used on entities that the service layer returns to the controller action method (Controller action methods). In this case, the lifecycle of the DbContext instance that the service method uses to load the entity will remain inflamed throughout the Web request process (or at least until the end of the action method). keeping DbContext beyond the scope of business transactions is the problem of activating the State
While it is possible to reuse the same dbcontext across multiple business transactions, the DbContext lifecycle should be kept short. Its first-level cache will eventually become obsolete and cause concurrency problems. If your application uses optimistic concurrency policies, then business transactions will fail because of dbupdateconcurrencyexception. It is often possible to use a "single Web request DbContext instance" in a Web application--because Web request times are usually very short. But in desktop applications, the strategy that is often recommended is to use a "one window (form) DbContext instance", but this is often problematic-so consider it before you adopt it.
It should be noted that if you use pessimistic concurrency strategies, you cannot reuse the same DbContext instance across multiple business transactions. The proper implementation of pessimistic concurrency policies involves maintaining an active database transaction at the correct database isolation level throughout the life cycle of the DbContext instance-which prevents you from committing or rolling back data independently in separate business transactions.
Reusing the same DbContext instance in more than one business transaction can also lead to catastrophic bug--service methods that may accidentally commit modifications from a failed business transaction.
Finally, the lifecycle of managing DbContext instances outside of your service approach tends to bind your application to a developed infrastructure--which in the long run makes your application less flexible and more difficult to evolve and maintain.
For example, for a very simple Web application, which relies on a "Web request to create a DbContext instance" policy to manage the DbContext lifecycle, it is easy to fall into a trap--either in the controller (Controller) or the view ( view), or by passing the persisted objects between service methods, assuming that they all use the same DbContext instance. When the inevitable introduction of multithreading or the transfer of these operations to the background windowsservice, these well-designed sand castle collapse-because there are no more Web requests to bind DbContext instance.
Therefore, it is recommended to avoid managing the lifecycle of DbContext instances independently of business transactions. You should create their own dbcontext instances within each service method and release the instance at the end of the business transaction.
This prevents the use of deferred loading outside the service (or lets the service method return the transport object instead of the persisted object to prevent the use of deferred loading outside the service). In addition, it is best not to pass the persisted objects to the service--because these objects are not attached to the dbcontext that the service will be using. Although there are so many restrictions, in the long run, it will give us a good flexibility and maintainability. your service must control the scope and isolation level of the database transaction
If your application uses a database that provides transactions that support acid four elements (if you are using entityframework, so be sure), then your service can control the scope and isolation level of the database transaction, or you will not be able to write the correct code.
As we'll see later, entityframework all the write operations together with a display database transaction--By default, the Read Committed isolation level--The default setting for SQL Server--can fit most business transactions. This is especially true if you rely on optimistic concurrency strategies to detect and avoid "update conflicts".
In any case, most applications still need to use other isolation levels for certain operations.
For example, if you perform a report query scenario, you may find that dirty reading is not a problem and choose to use the READ UNCOMMITTED isolation level-which eliminates lock contention.
And some business rules may have to use repeatable READ or SERIALIZABLE isolation level (especially if your project uses pessimistic concurrency strategies). These scenarios require the service to display the scope of the control transaction. the way to manage dbcontext should be independent of the system architecture
Software system architecture and design patterns evolve over time to accommodate new business rules and load upgrades.
You certainly don't want to restrict your upgrade to a specific architecture because of the policy of the selected management DbContext lifecycle. the way to manage dbcontext should be independent of the application type
Although most applications now start with a Web application, the policies you choose to use to manage the DbContext lifecycle should not assume that your service methods will be invoked only in the context of the web-based request. In general, your service layer (if any) should be independent of the type of application that uses it.
Shortly after the application starts, you may need to create command-line tools to perform operational tasks or create Windows services to handle timed tasks or require long running background operations. When this happens, you expect to be able to reference the assembly where your service resides for your console application or Windows service program. You wouldn't want to see your service being used by an DbContext application type when you need to completely refactor the admin instance. ways to manage DbContext should support multiple DbContext derived classes
If your application needs to access multiple databases (such as a report database, a Log/audit database), or if you separate your domain model into multiple aggregation groups, you will have multiple DbContext derived classes.
For nhibernate users, this is equivalent to managing multiple sessionfactory instances.
Whichever strategy you choose should allow the service to choose the type of dbcontext you want. Management DbContext should be able to support asynchronous workflows provided by EF6
In. NET 4.5, ADO. NET introduces the ability to support asynchronous database queries. Subsequent asynchronous support is also included in the ENTITYFRAMEWORK6-it allows you to read and write data using a complete asynchronous workflow.
Needless to say, whatever you choose to manage DbContext, it must work well with the EF asynchronous functionality.
(Translator Note: Next, we'll see some of the default behavior of DbContext)