Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread safe Entity Framework 6

Just starting testing EF6 and its Async functions. Boy was I surprised when I realized that they are not thread safe. I kinda assumed that that was the point.

I had my own Task based extension methods for years already, but what I was waiting for from EF was to make them thread safe.

At least my Task based functions locked as to not interfere with each other. EF6 doesn't even go that far. But the main problem is something that my code shares with theirs. i.e. Try issuing an async query and then before it completes try accessing a navigation property (on a pre-loaded totally separate entity in the same context) which triggers lazy loading. This could be triggered either by the UI, or by other code outside of the immediate function, or by a dozen other scenarios.

As far as I can tell. The only two shared (between entities) mutable resources in a dbContext are the connection and change tracking (caching). If we could add locking around those to functionalities then we would have a thread safe context.

We could even do it in two stages. If we could implement a provider that locked the one centralized function used to query the database. Then any non tracked queries - either by returning non entity (anonymous) objects or by calling AsNoTracking() - would be thread safe, and would be safe to call with Async functions even when another thread might be asking for a lazy loaded object.

Our scaleability would be no worse off then we are now that we have to use one context per thread, and even the Async functions are off the table if you try to skip even one await to introduce a bit of parallelism or are working in an evented system (like wpf) that might trigger once the awaited function returns with the task.

So my question is. Has anyone implemented a provider like this. Or would anyone be willing to work with me on it?

like image 649
Rabbi Avatar asked Nov 03 '13 15:11

Rabbi


People also ask

Is Entity Framework thread-safe?

Popular Answer. Entity Framework is not thread-safe. An MVC controller is instantiated per request. Thus if you use one DbContext per request, you're safe as long as you don't manually spawn threads in your controller actions (which you shouldn't do anyway).

Why is DbContext not thread-safe?

This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. When concurrent access goes undetected, it can result in undefined behavior, application crashes and data corruption.

What Happens If not thread-safe?

Conditionally safe: Different threads can access different objects simultaneously, and access to shared data is protected from race conditions. Not thread safe: Data structures should not be accessed simultaneously by different threads.


1 Answers

I think you are facing an architectural issue. What you describes is an application where UI directly uses EF objects, and it breaks the "separation of concerns" paradigm.

On my side, I use customs thread-safe caches on a Model layer, letting everything happens on the Model layer. I implemented thread-safety on my cache with the well-known AsyncLock.

DbContext objects, and every EF CRUD related operations have a very limited lifetime. Each CRUD Operation instantiate it's own DbContext, and returns Model Objects to the cache, then, contexts are garbage collected. My applications uses caches as an abstraction layer, and caches uses EF as a DB abstraction layer.

For example, exploring attached properties on Objects, is done by implementing custom methods on the Model layer, which takes the object Id as parameter, and returns a list of related objects to the cache. The UI asks the Cache, then the Cache asks EF, then once available, the call made to the cache returns objects to the UI. Simple as that.

EntityFramework is not designed to be thread safe, so there's no way to work with it in a multi-threaded way. (EF thread safety)

Instead of having parallel access to you DbContext, you have to build a Model layer which can be accessed in a multi-threaded way. And your model can make multiple parallel calls to you DB, but keep in mind that each call must instantiate and keep it's own DbContext. At the end of each call, the related DbContext must be disposed.

DbContext are really fast to instantiate, the only downside is the network latency. That's why a memory cache is a good idea.

like image 87
rducom Avatar answered Sep 28 '22 03:09

rducom