Making Entity Framework (v1) work, Part 1: DataContext lifetime management

EFv1 is sorely lacking in many areas. That doesn’t seem to stop people from using it, however. If EFv2 weren’t on the way (March 2010), I’d probably still be using NHibernate for everything. However, EFv2 (actually named v4 to “align” it with the .NET version number, or as a marketing ploy to make it seem more mature than it is) makes up for most of the deficiencies in v1, such as lazy loading and mapping to POCOs. Whatever the case, if you’re currently using v1, then here are some good ways to “make it work”.

DataContext lifetime management

When and where should you create the DataContext? Rick Strahl has an excellent post that explains the problems, and he offers an excellent solution. Rather than re-explain everything in his post, I’m just going to show you my solution.

A singleton that uses a static backer would be a bad idea, because in an ASP.NET application, you’d end up using the same DataContext for everyone, and you’d run into a host of problems. However, I DO like what the singleton pattern offers me: a global way to uniformly access an object, from anywhere I want. I don’t want to have to pass around a DataContext object everywhere I go. We just need to fix up our singleton so that it isn’t using a static backer. What I want is a DataContext per user, per post back. For this, we can use HttpContext.Current.Items which is a bag where you can store any object by key (string), and it’s unique per user, and it’s lifetime is limited to a single request/response cycle.

However, I don’t want to ONLY use HttpContext.Current.Items, because if I’m running a console application, or unit tests, HttpContext.Current is null, and I’ll end up with a NullReferenceException. So here’s the UnitOfWorkStore class I use to manage things like this:
	/// <summary>
	/// Utility class for storing objects pertinent to a unit of work.
	/// </summary>
	public static class UnitOfWorkStore
	{
		/// <summary>
		/// Retrieve an object from this store via unique key.
		/// Will return null if it doesn't exist in the store.
		/// </summary>
		/// <param name="key"></param>
		/// <returns></returns>
		public static object GetData(string key)
		{
			if (HttpContext.Current != null)
				return HttpContext.Current.Items[key];
			return CallContext.GetData(key);
		}


		/// <summary>
		/// Put an item in this store, by unique key.
		/// </summary>
		/// <param name="key"></param>
		/// <param name="data"></param>
		public static void SetData(string key, object data)
		{
			if (HttpContext.Current != null)
				HttpContext.Current.Items[key] = data;
			else
				CallContext.SetData(key, data);
		}

	}

I like this class because it “just works”, whether it’s an ASP.NET project, console app, or a unit test. However, if you are developing a GUI application, I would do some more research. While this class works, you don’t want to end up storing a ton of things in CallContext. You’ll want to find a more advanced unit of work class that gives you more control over scope and lifetime. My console apps that use my EF model run and terminate quickly, as do my unit tests, so the simple class above is perfect for me.

Now that we have a way to store things per user, per post back, let’s create a DAL or DataLayer class that will act as a singleton for our DataContext.

	/// <summary>
	/// This is the data access layer management class.
	/// </summary>
	public partial class MyDataLayer
	{
		/// <summary>
		/// This is our key to store an instance of this class in the <see cref="UnitOfWorkStore" />.
		/// This is used in the <see cref="Instance" /> property.
		/// </summary>
		private static readonly string UOW_INSTANCE_KEY = "MyDataLayer_Instance";

		/// <summary>
		/// This is used for thread-safety when creating the instance of this class to be stored in
		/// the UnitOfWorkStore.
		/// </summary>
		private static readonly object s_objSync = new object();

		// The DataContext object
		private readonly MyEntities m_context;



		// ********************************************************************************
		// *** Constructor(s) *************************************************************
		// ********************************************************************************

		/// <summary>
		/// Default constructor.  Creates a new MyEntities DataContext object.
		/// This is hidden (private) because the instance creation is managed as a "unit-of-work", via the
		/// <see cref="Instance" /> property.
		/// </summary>
		private MyDataLayer()
		{
			m_context = new MyEntities();
		}



		// ********************************************************************************
		// *** Public properties **********************************************************
		// ********************************************************************************

		/// <summary>
		/// The ObjectContext object that gives us access to our business entities.
		/// Note that this is NOT static.
		/// </summary>
		public MyEntities Context
		{
			get { return m_context; }
		}


		/// <summary>
		/// This will get the "one-and-only" instance of the MyDataLayer that exists for the lifetime of the current "unit of work",
		/// which might be the lifetime of the currently running console application, a Request/Response iteration of an asp.net web app,
		/// an async postback to a web service, etc.
		/// 
		/// This will never return null.  If an instance hasn't been created yet, accessing this property will create one (thread-safe).
		/// This uses the <see cref="UnitOfWorkStore" /> class to store the "one-and-only" instance.
		/// 
		/// This is the instance that is used by all of the DAL's partial entity classes, when they need a reference to a MyEntities context
		/// (MyDataLayer.Instance.Context).
		/// </summary>
		public static MyDataLayer Instance
		{
			get
			{
				object instance = UnitOfWorkStore.GetData(UOW_INSTANCE_KEY);

				// Dirty, non-thread safe check
				if (instance == null)
				{
					lock (s_objSync)
					{
						// Thread-safe check, now that we're locked
						if (instance == null) // Ignore resharper warning that "expression is always true".  It's not considering thread-safety.
						{
							// Create a new instance of the MyDataLayer management class, and store it in the UnitOfWorkStore,
							// using the string literal key defined in this class.
							instance = new MyDataLayer();
							UnitOfWorkStore.SetData(UOW_INSTANCE_KEY, instance);
						}
					}
				}

				return (MyDataLayer)instance;
			}
		}
	}

Note that the constructor is private, so you can’t directly instantiate this class. You have to go through the static “singleton accessor”, MyDataLayer.Instance. Where this differs from most singleton implementations is that we don’t use a private static field to hold our singleton instance, we use the UnitOfWorkStore class. Now anywhere in your code, you can refer to MyDataLayer.Instance.Context, and you’ll have a DataContext object that is unique to the current ASP.NET user, and that will exist only during the current request/response, and is then discarded. Handy!

Up next: code generation with T4 templates that can see your EDMX model (using the SFS plugin for Visual Studio 2008) to get methods in each of your entity classes like:
  • GetById(int id) (or Guid id, or whatever the type of your PK is, the code generation will do it correctly)
  • GetById(int id, params string[] includes)
  • GetPage(int pageIndex, int pageSize, string where, string orderBy, out int rawRecordCount, params string[] includes)
  • And more…



This entry was posted in Code and tagged , , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

One Comment

  1. Matthew
    Posted September 1, 2011 at 2:32 pm | Permalink

    simply awesome!

3 Trackbacks

  1. [...] The Unit of work is based on this article. And the website and CMS are running in an seperate IIS application pool on my local [...]

  2. By How to create an iPhone app on October 18, 2014 at 3:48 am

    How to create an iPhone app

    Making Entity Framework (v1) work, Part 1: DataContext lifetime management

  3. […] Rick Strahl and Samuel Maecham have taught me that we should keep your datacontext per user per request. Which means putting it in a HttpContext for web applications. Read all about it here […]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>