EF Caching: How to detach objects *completely* before inserting them into HttpRuntime cache?

Viewed 1054

Some background:

Working with:

  • .NET 4.5 (thinking of migrating to 4.5.1 if it's painless)
  • Web Forms
  • Entity Framework 5, Lazy Loading enabled
  • Context Per Request
  • IIS 8
  • Windows 2012 Datacenter

Point of concern: Memory Usage

Over the project we are currently on, and probably our first bigger project, we're often reading some bigger chunks of data, coming from CSV imports, that are likely to stay the same for very long periods of time.

Unless someone explicitly re-imports the CSV data, they are guaranteed to be the same, this happens in more than one places in our project and similar approach is used for some regular documents that are often being read by the users. We've decided to cache this data in the HttpRuntime cache.

It goes like this, and we pull about 15,000 records consisting mostly of strings.

//myObject and related methods are placeholders
public static List<myObject> GetMyCachedObjects()
{
    if (CacheManager.Exists(KeyConstants.keyConstantForMyObject))
    {
        return CacheManager.Get(KeyConstants.keyConstantForMyObject) as List<myObject>;
    }
    else
    {
        List<myObject> myObjectList = framework.objectProvider.GetMyObjects();
        CacheManager.Add(KeyConstants.keyConstantForMyObject, myObjectList, true, 5000);
        return myObjectList;
    }
}

The data retrieving for the above method is very simple and looks like this:

public List<myObject> GetMyObjects()
{
    return context.myObjectsTable.AsNoTracking().ToList();
}

There are probably things to be said about the code structure, but that's not my concern at the moment.

I began profiling our project as soon as I saw high memory usage and found many parts where our code could be optimized. I never faced 300 simultaneous users before and our internal tests, done by ourselves were not enough to show the memory issues. I've highlighted and fixed numerous memory leaks but I'd like to understand some Entity Framework related unknowns.

Given the above example, and using ANTS Profiler, I've noticed that 'myObject', and other similar objects, are referencing many System.Data.Entity.DynamicProxies.myObject, additionally there are lots of EntityKeys which hold on to integers. They aren't taking much but their count is relatively high.

For instance 124 instances of 'myObject' are referencing nearly 300 System.Data.Entity.DynamicProxies.

Usually it looks like this, whatever the object is: Some cache entry, some object I've cached and I now noticed many of them have been detached from dbContext prior caching, the dynamic proxies and the objectContext. I've no idea how to untie them.

enter image description here

My progress:

I did some research and found out that I might be caching something Entity Framework related together with those objects. I've pulled them with NoTracking but there are still those DynamicProxies in the memory which probably hold on to other things as well.

Important: I've observed some live instances of ObjectContext (74), slowly growing, but no instances of my unitOfWork which is holding the dbContext. Those seem to be disposed properly per request basis.

I know how to detach, attach or modify state of an entry from my dbContext, which is wrapped in a unitOfWork, and I often do it. However that doesn't seem to be enough or I am asking for the impossible.

Questions:

  • Basically, what am I doing wrong with my caching approach when it comes to Entity Framework?
  • Is the growing number of Object Contexts in the memory a concern, I know the cache will eventually expire but I'm worried of open connections or anything else this context might be holding.
  • Should I be detaching everything from the context before inserting it into the cache?
  • If yes, what is the best approach. Especially with List I cannot think of anything else but iterating over the collection and call detach one by one.
  • Bonus question: About 40% of the consumed memory is free (unallocated), I've no idea why .NET is reserving so much free memory in advance.
3 Answers
Related