Software Design Blog

Simple solutions to solve complex problems

Butcher the LINQ to SQL Resource Hog

Has your LINQ to SQL repository ever thrown a "cannot access a disposed object" exception? You can fix it by calling ToList on the LINQ query but it will impede your application’s performance and scalability.

This post covers common pitfalls and how to avoid them when dealing with unmanaged resources such as the lifecycle of a database connection in a pull-based IEnumerable repository. An investigation is made to uncover when Entity Framework and LINQ to SQL resources are disposed of and how to implement an effective solution.

Download Source Code

Setup

The following repository class will be used to model the same behaviour as an actual LINQ to SQL database repository.
    public class Model
    {
        public string Message { get; set; }
    }

    public class Repository : IDisposable
    {
        public IEnumerable<Model> Records
        {
            get
            {
                if (_disposed) throw new InvalidOperationException("Disposed");
                Console.WriteLine("Building message one");
                yield return new Model() { Message = "Message one" };
                if (_disposed) throw new InvalidOperationException("Disposed");
                Console.WriteLine("Building message two");
                yield return new Model() { Message = "Message two" };
            }
        }

        private bool _disposed = false;

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        
        protected virtual void Dispose(bool disposing)
        {
            if (_disposed) return;
            _disposed = true;
        }
    }

LINQ to SQL: Cannot access a disposed object

Let's execute the LINQ query below to call the repository and write the results to the console.
        static void Main(string[] args)
        {
            var records = GetLinqRecords();
            foreach (var record in records)
            {
                Console.WriteLine(record);    
            }
            Console.ReadLine();
        }

        private static IEnumerable<string> GetLinqRecords()
        {
            using (var repository = new Repository())
            {
                return (from model in repository.Records select model.Message);
            }
        }

A LINQ to SQL application would raise the following exception:

An unhandled exception of type 'System.ObjectDisposedException' occurred in System.Data.Linq.dll
Additional information: Cannot access a disposed object.

LINQ to SQL: ToList

Let's execute the LINQ query below by materialising the records to a list first:
        
        static void Main(string[] args)
        {
            var records = GetLinqRecordsToList();
            foreach (var record in records)
            {
                Console.WriteLine(record);    
            }
            Console.ReadLine();
        }

        private static IEnumerable>string< GetLinqRecordsToList()
        {
            using (var repository = new Repository())
            {
                return (from model in repository.Records select model.Message).ToList();
            }
        }
Building message one
Building message two
Message one
Message two

Yield to the rescue

Let's execute the code below using yield instead:
        static void Main(string[] args)
        {
            var records = GetYieldRecords();
            foreach (var record in records)
            {
                Console.WriteLine(record);    
            }
            Console.ReadLine();
        }

        private static IEnumerable<string> GetYieldRecords()
        {
            using (var repository = new Repository())
            {
                foreach (var record in repository.Records)
                {
                    yield return record.Message;
                }
            }
        }
Building message one
Message one
Building message two
Message two

Don’t refactor your code

Let's see what happens when we run a refactored version of the code:
        static void Main(string[] args)
        {
            var records = GetRefactoredYieldRecords();
            foreach (var record in records)
            {
                Console.WriteLine(record);    
            }
            Console.ReadLine();
        }

        private static IEnumerable<string>string<string> GetRefactoredYieldRecords()
        {
            using (var repository = new Repository())
            {
                return YieldRecords(repository.Records);
            }
        }

        private static IEnumerable<string> YieldRecords(IEnumerable<Model> records)
        {
            if (records == null) throw new ArgumentNullException("records");
            foreach (var record in records)
            {
                yield return record.Message;
            }
        }

Déjà Vu. The same error occurred as seen in the LINQ to SQL example. Take a closer look at the IL produced by the compiler using a tool such as ILSpy.

In the refactored and the LINQ to SQL version, instead of returning an IEnumerable function directly, a function is returned that points to another IEnumerable function. Effectively, it is an IEnumerable within an IEnumerable. The connection lifecycle is managed in the first IEnumerable function which will be disposed once the second IEnumerable function is returned to the caller.

Keep it simple, return the IEnumerable function directly to the caller.

Comments are closed