Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Finding Non-disposed Contexts in Entity Framework 6.x Applications

5.00/5 (6 votes)
28 Sep 2014CPOL3 min read 30.4K   205  
It's easy to forget not disposing object contexts, which leads to memory leaks and also leaves too many related connection objects not disposed as well.

Introduction

Object context acts as a unit of work in entity framework based applications and should have a short life time. It's easy to forget not disposing object contexts, which leads to memory leaks and also leaves too many related connection objects not disposed as well.

Here, we have 2 simple methods. First one disposes the context correctly and the second one does not have a using statement. Therefore, its context won't be disposed at the end of the method automatically.

C#
private static void disposedContext()
{
    using (var context = new MyContext())
    {
       Debug.WriteLine("Posts count: " + context.BlogPosts.Count());
    }
}

private static void nonDisposedContext()
{
    var context = new MyContext();
    Debug.WriteLine("Posts count: " + context.BlogPosts.Count());
}

Question: How can I find all of the non-disposed contexts in a large Entity framework 6.x application?

To answer this question, we can use the new Interception mechanism of Entity framework 6.x.

C#
public class DatabaseInterceptor : IDbConnectionInterceptor
{
     public void Closed(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
     {
     }

     public void Disposed(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
     {
     }

     public void Opened(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
     {
     }
      
     // the rest of the IDbConnectionInterceptor methods ...
}

By defining a custom database interceptor and implementing the IDbConnectionInterceptor interface, it's possible to receive notifications of opening, closing and disposing different DbConnection objects. When a context is disposed, its connections will be disposed too.

Problem! How can I track a DbConnection object? Which connections are closed or disposed here? How can I relate the DbConnection object of the Opened method to the Disposed method?
DbConnection object does not have an Id property. It's possible to add a new property to an existing object using extension properties concept.
We can't use GetHashCode() here. Because it's possible to have different references with the same hash values and it is not guaranteed to be unique per object.
.NET 4.0 introduced ConditionalWeakTable<key, value>. Its original purpose is to attach some extra data to an existing object, which allows us to track managed objects too. Also it doesn't interfere with garbage collector, because it uses weak references to objects.

C#
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Threading;

namespace EFNonDisposedContext.Core
{
    public static class UniqueIdExtensions<T> where T : class
    {
        static readonly ConditionalWeakTable<T, string> _idTable =
                                    new ConditionalWeakTable<T, string>();

        // A static field is shared across all instances of the `same` type or T here.
        // This behavior is useful to produce unique auto increment Id's per each different object reference.
        private static int _uniqueId;

        public static string GetUniqueId(T obj)
        {
            return _idTable.GetValue(obj, o => Interlocked.Increment(ref _uniqueId).ToString(CultureInfo.InvariantCulture));
        }

        public static string GetUniqueId(T obj, string key)
        {
            return _idTable.GetValue(obj, o => key);
        }

        public static ConditionalWeakTable<T, string> RecordedIds
        {
            get { return _idTable; }
        }
    }
}

The main part of this class is _idTable as ConditionalWeakTable which manages a dictionary to store object references in conjunction with their unique IDs.

Now it's possible to create a list of connections and their status. IDbConnectionInterceptor interface provides Opened, Closed and Disposed events. During the call of each event, UniqueIdExtensions<DbConnection>.GetUniqueId(connection) returns the unique Id of the connection. Based on this value, we can add a new ConnectionInfo to the list or update its status from Opened to Closed or Disposed.

C#
public enum ConnectionStatus
{
    None,
    Opened,
    Closed,
    Disposed
}

public class ConnectionInfo
{
    public string ConnectionId { set; get; }
    public string StackTrace { set; get; }
    public ConnectionStatus Status { set; get; }

    public override string ToString()
    {
        return string.Format("{0}:{1} [{2}]",ConnectionId, Status, StackTrace);
    }
}

To construct a new ConnectionInfo, we also need the StackTrace of the caller methods. It allows us to find which method calls are responsible to open this connection.

C#
new ConnectionInfo
                {
                    ConnectionId = UniqueIdExtensions<dbconnection>.GetUniqueId(connection),
                    StackTrace = CallingMethod.GetCallingMethodInfo(),
                    Status = status
                }

To extract the caller methods information, we can start with the new instance of the StackTrace class. Its FrameCount property defines different levels of callers. Each frame consists of the information of the calling method, plus its filename and line number.

Filename and line number won't be present the in final log, if the involved assemblies don't have the valid .PDB files.

C#
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;

namespace EFNonDisposedContext.Core
{
    public static class CallingMethod
    {
        public static string GetCallingMethodInfo()
        {
            var stackTrace = new StackTrace(true);
            var frameCount = stackTrace.FrameCount;

            var info = new StringBuilder();
            var prefix = "-- ";
            for (var i = frameCount - 1; i >= 0; i--)
            {
                var frame = stackTrace.GetFrame(i);
                var methodInfo = getStackFrameInfo(frame);
                if (string.IsNullOrWhiteSpace(methodInfo))
                    continue;

                info.AppendLine(prefix + methodInfo);
                prefix = "-" + prefix;
            }

            return info.ToString();
        }

        private static bool isFromCurrentAsm(MethodBase method)
        {
            return method.ReflectedType == typeof(CallingMethod) ||
                method.ReflectedType == typeof(DatabaseInterceptor) ||
                method.ReflectedType == typeof(Connections);
        }

        private static bool isMicrosoftType(MethodBase method)
        {
            if (method.ReflectedType == null)
                return false;

            return method.ReflectedType.FullName.StartsWith("System.") ||
                   method.ReflectedType.FullName.StartsWith("Microsoft.");
        }

        private static string getStackFrameInfo(StackFrame stackFrame)
        {
            if (stackFrame == null)
                return string.Empty;

            var method = stackFrame.GetMethod();
            if (method == null)
                return string.Empty;

            if (isFromCurrentAsm(method) || isMicrosoftType(method))
            {
                return string.Empty;
            }

            var methodSignature = method.ToString();
            var lineNumber = stackFrame.GetFileLineNumber();
            var filePath = stackFrame.GetFileName();

            var fileLine = string.Empty;
            if (!string.IsNullOrEmpty(filePath))
            {
                var fileName = Path.GetFileName(filePath);
                fileLine = string.Format("[File={0}, Line={1}]", fileName, lineNumber);
            }

            var methodSignatureFull = string.Format("{0} {1}", methodSignature, fileLine);
            return methodSignatureFull;
        }
    }
}

With this sample output:

--- Void Main(System.String[]) [File=Program.cs, Line=28]
--- Void disposedContext() [File=Program.cs, Line=76]

Now we can create an in-memory repository to get, add or update the connections list.

C#
public static class Connections
{
    private static readonly ICollection<ConnectionInfo> _connectionsInfo = new List<ConnectionInfo>();

    public static ICollection<ConnectionInfo> ConnectionsInfo
    {
        get { return _connectionsInfo; }
    }

    public static void AddOrUpdate(ConnectionInfo connection)
    {
        var info = _connectionsInfo.FirstOrDefault(x => x.ConnectionId == connection.ConnectionId);
        if (info!=null)
        {
            info.Status = connection.Status;
        }
        else
        {
            _connectionsInfo.Add(connection);
        }
    }

    public static void AddOrUpdate(DbConnection connection, ConnectionStatus status)
    {
        AddOrUpdate(
            new ConnectionInfo
            {
                ConnectionId = UniqueIdExtensions<DbConnection>.GetUniqueId(connection),
                StackTrace = CallingMethod.GetCallingMethodInfo(),
                Status = status
            });
    }
}

Then, we can track the different states of each connection in DatabaseInterceptor class.

C#
using System.Data;
using System.Data.Common;
using System.Data.Entity.Infrastructure.Interception;

namespace EFNonDisposedContext.Core
{
    public class DatabaseInterceptor : IDbConnectionInterceptor
    {
        public void Closed(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
        {
            Connections.AddOrUpdate(connection, ConnectionStatus.Closed);
        }

        public void Disposed(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
        {
            Connections.AddOrUpdate(connection, ConnectionStatus.Disposed);
        }

        public void Opened(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
        {
            Connections.AddOrUpdate(connection, ConnectionStatus.Opened);
        }
      
        // the rest of the IDbConnectionInterceptor methods ...
    }
}

To register this new interceptor, we can call the following line at the beginning of the program.

C#
DbInterception.Add(new DatabaseInterceptor());

At the end, to find out which contexts are not disposed, we can query connections list and filter connections with Status != ConnectionStatus.Disposed.

C#
var connectionsTable = UniqueIdExtensions<DbConnection>.RecordedIds;
var valuesInfo = connectionsTable.GetType().GetProperty("Values", BindingFlags.NonPublic | BindingFlags.Instance);
var aliveConnectionsKeys = (ICollection<string>)valuesInfo.GetValue(connectionsTable, null);

var nonDisposedItems = Connections.ConnectionsInfo
                                  .Where(info => info.Status != ConnectionStatus.Disposed &&
                                                 aliveConnectionsKeys.Contains(info.ConnectionId));
foreach (var connectionInfo in nonDisposedItems)
{
   Debug.WriteLine("+--------ConnectionId:" + connectionInfo.ConnectionId + "----------+");
   Debug.WriteLine(connectionInfo.StackTrace);
}

Also it's important to check the internal properties of ConditionalWeakTable object. Because, the real disposed objects won't be present in its Values collection property.

This is a sample output of the above query:

List of non-disposed ObjectContexts:
+--------ConnectionId:15----------+
-- Void Main(System.String[]) [File=Program.cs, Line=23]
--- Void nonDisposedContext() [File=Program.cs, Line=69]

<p>+--------ConnectionId:16----------+
-- Void Main(System.String[]) [File=Program.cs, Line=24]
--- Void nonDisposedContext() [File=Program.cs, Line=69]


+--------ConnectionId:17----------+
-- Void Main(System.String[]) [File=Program.cs, Line=25]
--- Void nonDisposedContext() [File=Program.cs, Line=69]


+--------ConnectionId:19----------+
-- Void Main(System.String[]) [File=Program.cs, Line=27]
--- Void nonDisposedContext() [File=Program.cs, Line=69]


+--------ConnectionId:22----------+
-- Void Main(System.String[]) [File=Program.cs, Line=30]
--- Void nonDisposedContext() [File=Program.cs, Line=69]

Here nonDisposedContext() method is called 5 times from different locations or lines.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)