简体   繁体   中英

Single DbContext instance in multi-thread application

In a MVVM WPF application that I am currently working on, the Entity Framework 6 is used as follows:

  • code first entity model
  • one DbContext is created on the start of the application and is shared across (usually passed in constructor)
  • some properties of some entities are binded directly to the UI - or are NotMapped and holding UI related content eg. UserControls that are created in the constructor of such entity
  • there is a separate thread refreshing "jobs" for our application to do - those “jobs” are written directly to the database by external application
  • there is UI thread that is using the same DbContext for adding, deleting or changing of those “jobs” and other entities on click action in the UI
  • there is also another separate thread for refreshing and managing other entities
  • the entities are linked between each other using the advantage of lazy loading

First, we had a problem with context.SaveChanges() - we were experiencing various different exceptions such as:

"New transaction is not allowed because there are other threads running in the session"

"The property "ID" is part of the object's key information and cannot be modified"

"The transaction operation cannot be performed because there are pending requests working on this transaction"

Therefore, we have implemented simple locking for this in our context class, hoping to resolve this issue:

public override int SaveChanges()
{
    lock (this)
    {
        return base.SaveChanges();
    }
}

This helped only partially, since now we are getting the following exception, which is appearing less often:

"An error occurred while saving entities that do not expose foreign key properties for their relationships"

In addition, we have sometimes issues with linked properties. Even though they are all defined as virtual to enable lazy loading, sometimes we do get a null reference exception as they would not be linked.

My main concerns are:

  • after some research, I see that this implementation of EF is not as it should be (the context should be short lived)
  • having UI bindings in model breaks the SoC paradigm
  • DbContext is not thread safe

I think that ideally we should refactor the architecture - perhaps by developing some separate layer to handle those issues, but this would be time consuming in our case and not the preferable solution.

Is there a way to use the DbContext and EF6 the same way as it is already designed in our application, with some changes to fix the issues?

Depending on your use case, when launching queries, it may help to use the .AsNoTracking() extension method. This makes it so the DbContext does not track changes to object properties, so if one thread GETS data using .AsNoTracking() while another thread UPDATES a potentially conflicting set of data (without using .AsNoTracking()), the two operations should not interfere with one another.

Your analysis of the problem so far is correct. DbContexts should be short lived. UI should not be bound to entities, (even though a lot of examples do this, even from Microsoft) and the DbContext is certainly not thread-safe. The bad news is that there isn't likely going to be an easy way out of that.

Switching from one long-lived DbContext to short-lived ones is a fairly trivial matter. However, dealing with entity references that may get passed between DbContext instances, not so trivial. This blends directly into the second flaw with binding to entities since these entities are travelling from domain/business logic to view and back. Short-lived contexts should for the most part solve the thread-safe issue, so long as again the entities are not travelling between contexts.

Tackling a problem like this in an established application can be difficult depending on how mature the application is, and time pressures, but it is not impossible. The key would be to identify the areas of the application that are exhibiting the most problems and look to tackle those first. An area that is fairly small, but heavily used would be ideal to test the waters on what would be involved to re-factor the code-base. To avoid breaking everything you can leverage a bounded context definition to split a DbContext definition per area of an application, so that for a set of related views you can compose a view model structure and populate this from a new, short-lived DbContext that will manage retrieving all entities needed by those screens, and all persistence. The hard part will be establishing a perimeter for your code if the application structure is passing a lot of entities around. At these boundaries, method signatures will need to change so that entities from the original DbContext don't flow into the new code, and calls out to the old code can convert view models back into entities scoped by the original DbContext. Ultimately though the approach you use will need to reflect the exact structure of your application. Code that's relied heavily on abstractions, Generics and the like can be more difficult to re-factor.

The alternative is to start working around detached entities with Detach / Attach and AsNoTracking . In my opinion though, this can lead you down a significantly deeper rabbit hole of complexity, so I would be cautious about considering this as a possible solution. AsNoTracking is a good option in cases where you are reading from a potentially large subset of records and avoid the cost of associating them to a DbContext. Generally the more entities a DbContext is tracking, the slower operations against the context get. So operations such as Searches and reports benefit from AsNoTracking . However when detaching/attaching entities, you need to deliberately check the context instance to see if it isn't already tracking an instance with the same PK before attaching, and ensure the provided entity isn't already tracked by another DbContext. Dealing with detaching/attaching nested entity graphs is also a pain.

A variation of a popular quote. "Someone that has a problem that they believe they can solve by using detached entities, now has two problems." :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM