Iris Classon
Iris Classon - In Love with Code

Accidental caching of Entity Framework objects and how to detect EF objects

In our solution we’ve had problems with accidental caching of EF objects. We have our own memory cache implementation, one for each service, that we try to keep in sync across services as the services can cache the same objects. Inconsistency in the cache can be a big problem, not to mention difficult to debug, and by caching EF objects we add to the problem as we cache relational data that might have changes somewhere else. When we use an object, via our repository, we do not know if the object originates from the cache or direct storage- and we shouldn’t have to know. However, at the same time, we have nested objects with complex relationships. Our plan, moving forward, is to un-nest the objects, map them to plain business entity (not EF entities!) objects, and only work with those. Until we get there we will have to go through our repositories and do something about our cached entities, and a first step is to figure out which objects are cached as EF objects?

EF has two properties of interest, LazyLoadingEnabled and ProxyCreationEnabled. We often turn of lazy loading to make sure we fetch all the objects we need when there is no additional filtering and nothing to gain from deferring object creation. Proxy creation is used for two things, change tracking and lazy loading. The entities are wrapped in proxy classes, DynamicProxies, which contains the logic mentioned earlier. The problem with our caching is that we cache the entities as proxy objects, and if we turn of the proxy creation right before we cache the objects we have a slight risk of a race condition where the context we are working on might have change tracking disabled while we do our caching, leaving room for problems if business logic is applied somewhere else where the context is being used. We are going to rewrite our caching logic completely, but for now, there is a method that can be used to detect I the cached object is an EF object by evaluating if the object is wrapped by the proxy class.

This is how the method looks like, but as you can guess, there is a performance penalty so we wouldn’t want to run this all the time, only when we suspect problems or need to do a new inventory of our problem objects:

 
private bool IsProxyObject<T>(T value)
{
	if (value == null)
		throw new ArgumentNullException(nameof(value));

	if (value is IEnumerable list)
	{
		var enumerator = list.GetEnumerator();

		while (enumerator.MoveNext())
		{
			return IsWrappedAsProxy(enumerator.Current);
		}
	}

	return IsWrappedAsProxy(value);

	bool IsWrappedAsProxy(object item)
	{
		return item.GetType().FullName?.Contains("DynamicProxies") ?? false;
	}
}

public void Add<T>(string key, T value, DateTimeOffset timeout)
{
	if (IsProxyObject(value))
	{
		Logger.Info($"Tried to cache EF object: {CacheKeyConverter.To<T>(key)}. StackTrace: {new StackTrace().ToString()}");
		return;
	}

	_cacheLock.EnterWriteLock();
	
	// Logic for Add
}
2019-11-21_10-55-52

Comments

Leave a comment below, or by email.

Last modified on 2019-11-21

comments powered by Disqus