We have found that setting AutoDetectChangesEnabled = false
can have substantial (i.e. factor of 10) performance impacts.
Background: Our system is composed entirely of EF model objects that use change detecting proxies. That is, all of our DB fields and relational properties are declared as virtual. We also have a relatively deeply structured object model. That is object A contains a set of object B, which in turn contain a set of Object C, etc. We have observed that instantiating a non-trivial (> 100) number of these objects via a EF/LINQ query is expensive. For example, in one case instantiating 250 objects required about 2 seconds. We also observed that instantiating the same structure, but using anonymous objects instead required about 25 ms. Lastly, we observed that if we set AutoDetectChangesEnabled = false
, that we could use the query instantiating EF model objects and materialization again was about 25 ms.
So, at least for us, there were huge gains to be made by setting it false. We use a Unit Of Work pattern, and we explicitly indicate whether the Unit of Work is read-only or not. For a read-only unit of work, setting AutoDetectChangesEnabled = false
is perfectly safe, since there never will be any changes. In fact, we added this change to our system two years after our initial release (so there were many, many pre-existing unit of works in the code) and the change did not break anything at all, and significantly improved performance.
We also experimented with AsNoTracking()
and found that it gave us essentially no performance increase at all. As I understand it, a query with AsNoTracking()
means that the objects will not be placed into the identity map, and this will force EF to re-fetch the object from disk if it is referenced more than once within the context (e.g. in different queries). So there is some potential downside to AsNoTracking()
.
Implementation Details:
- We have a subclass of DBContext which provides much of the infrastructure for our unit of work
- Our unit of work is basically a light weight wrapper around the context
- When a unit of work is allocated (typically in a using block) it receives one of these contexts via injection (we use Castle/Windsor)
- During initialization the unit of work calls a method on the context to which sets AutoDetectChangesEnabled to false
- Currently, we do this all the time because we always use change detecting proxies and they do not require AutoDetectChangesEnabled
- Previously we only did it for 'read-only' units of work, since if nothing is modified in a UoW there is no need to detect changes (when we allocated a unit of work we explicitly indicate whether it is read-only or not)
-