Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF returns old values

I'm using EF6 + WPF with MVVM design pattern in my desktop application. I'm using also Autofac as DI container.

I read a lot about EF context lifetime management and I decided to have only one instane of EF context for single view model instance. I found several interesting articles about that approach, so I thought that it is only good way to manage EF context. I use Autofac to manage EF lifetime, so every time I create new view model, there will be created only one new EF context.

But of course, I came across a problem. Most of my EF queries work good, but the following query always returns old (cached) value. I call this query every time I push 'Execute' button, so there are many executions per single view / view model

this.context.someTable.Where(arg => arg.value == "value").Single();

I know that I can always reload the entity with following code

this.context.Entry(entity).Reload();

But for me it is not a good solution. I know also that if I dispose current context and recreate before next query I will always receive current values. But this approach conflicts with one context per one view model approach.

What should I fix / change to avoid EF caching issues and still have good performance.

like image 363
rraszewski Avatar asked Jan 20 '15 09:01

rraszewski


2 Answers

You shouldn't persist a context

I would recommend you abandon the single shared context. I did this recently for a large WPF application. An EF context is designed to be a unit-of-work, you should use it and then call .Dispose(). If you need to read in relationship properties eagerly, you should use .Include() hints. You should construct your contexts in a using block so you know where you lose scope, and to ensure the context is disposed.

You will find that performance of EF can actually diminish as it needs to refer to its internal cache and state. I have found bulk data insert patterns deteriorate if a shared context is used. EF doesn't perform as well as an RDBMS.

As you have experienced, you can persist the context and benefit from cached entities, but if that's becoming a pain, due to the nature of your system and requirements of the user, you are not really benefiting from the caching anymore. Your backing RDBMS should be fast enough. As soon as you cache in any way (including EF second-level caching and ASP.NET Output Caching), you immediately need to plan how to expire your cached entities. This adds a lot more work for your coders, and gives your systems spectacular new ways to fail.

For instance, consider that a benefit of EF is the automatic resolution of relationship properties. You can be seamlessly reaching across a graph of data until you hit an entity which is cached and stale. In such circumstances, it is difficult to expire the cache before retrieving such entities.

But if you must reload on Update

If you really don't want to change your architecture to the Microsoft recommended/intended way. I suggest you keep track of all open contexts (adding to static collection on construction, removing on dispose, double-check with finalizer pattern, and finalizer suppression on dispose), and implement some generic code in the save pipeline (there are a few ways of doing this) which attempts to reload the entity across all open contexts. This is an proactive way of expiring your EF entity caches. This may affect performance on larger collections, but you could have a whitelist or blacklist of entities to process, rather than processing all saved entities.

Personally, I'm glad I made the change (restructuring to short-lived contexts), long term there are huge benefits in terms of code maintainability and system stability.

like image 153
Kind Contributor Avatar answered Sep 27 '22 20:09

Kind Contributor


Following method forces EF to requery the query to the database and do not cache the result:

this.context.someTable.AsNoTracking().Where(arg => arg.value == "value").Single();

The important method call is AsNoTracking

like image 20
Michael Mairegger Avatar answered Sep 27 '22 18:09

Michael Mairegger