Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ObjectContext is leaking memory for detached entities

I already checked this using a memory profiler and there are no real entities which stay in memory but hash-sets, dictionaries and EntityKey objects -- but I found no way how to disconnect these references.

So simple question: How do I stop the context (or its ObjectStateManager) from growing infinitely in size?

[And yes, I know that long living contexts should be avoided, but in this case it's one complex analysis run which needs several hierarchical data being loaded (and the sample below is just a minimal problem demonstration) so finally it is a "short" living one-operation context.]

Steps to repro:

  • create a new console application
  • create a EF model for a Northwind database (either use some real SQL Server or copy Northwind.sdf from Compact Samples folder)
  • use code below:

Code [Updated, doesn't need real DB connection anymore]:

class Program
{
    static void Main()
    {
        const double MiB = 1024 * 1024;
        using ( var context = new NorthwindEntities() )
        {
            var last = GC.GetTotalMemory(true) / MiB;
            Console.WriteLine("before run: {0:n3} MiB", last);
            var id = 0;
            while ( true )
            {
                Run(context, ref id);

                GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
                GC.WaitForPendingFinalizers();
                var current = GC.GetTotalMemory(true) / MiB;
                Console.WriteLine("after run: {0:n3} MiB (+{1:n3} MiB)", current, current - last);
                last = current;

                if ( Console.KeyAvailable )
                    break;
                Console.WriteLine(new string('-', 100));
            }
        }
    }

    static void Run(NorthwindEntities context, ref int id)
    {
        for ( int i = 0; i < 100000; i++ )
        {
            var category = new Category { Category_ID = ++id };
            category.EntityKey = new EntityKey("NorthwindEntities.Categories", "Category_ID", id);
            var product = new Product { Product_ID = id, Category_ID = id };
            product.EntityKey = new EntityKey("NorthwindEntities.Products", "Product_ID", id);
            product.Category = category;
            context.Attach(product);
            context.Detach(product);
            context.Detach(category);
        }

        var ctr = 0;
        Console.WriteLine("Enumerating living/attached objects:");
        const EntityState AllStates = EntityState.Added | EntityState.Deleted | EntityState.Modified | EntityState.Unchanged;
        foreach ( var entry in context.ObjectStateManager.GetObjectStateEntries(AllStates) )
            Console.WriteLine("  #{0} [{1}] {2}", ++ctr, entry.EntityKey, entry.Entity);
        if ( ctr == 0 )
            Console.WriteLine("  NOTHING (as expected)");
    }
}
like image 813
springy76 Avatar asked Oct 02 '11 21:10

springy76


1 Answers

Since I'm only detaching entities directly after having called SaveChanges(), I'm now counting the number of detached entities and when the counter reaches 10,000 I detach all still living (and needed) objects from the context and create a new context to which I attach all detached objects. Downside: The IsLoaded property of EntityReferences and EntityCollections is now always false (but I don't rely on this).

like image 146
springy76 Avatar answered Nov 03 '22 01:11

springy76