The ObjectContext of Your Desire, Part The Second

In Which We Painlessly Scope the Lifespan of the Entity Framework Context

In the previous post of this pair, we covered where non-barbarian (and non-physicist) developers can hook their business logic code when using the ADO.NET Entity Framework. To round out the set, we will A the frequently-asked Q of when we should instantiate the ObjectContext, and when we should run it out to the curb for the GC. IMHO AFAIK. LOL.

ObjectContext != DbConnection

If you are coming at the Entity Framework from an old-timey ADO.NET perspective, you might align the idea of a data context with that of a data connection. The best way to handle a data connection directly is to enclose it in a using block, which upon its closing brace will pull up its socks and tidy its underlying external resources:

DataTable results = new DataTable();
using (IDbConnection con = GetConnection())
{
    // open the connection
    con.Open(); 
    
    // query the database
    IDbCommand cmd = con.CreateCommand();
    cmd.CommandText = "select * from Employee";
    results.Load(con.ExecuteReader());
}   
// now the connection is closed and 
// ready for garbage collection

While a data context surely uses a data connection or two under the hood, don’t mistake one for the other. The change-tracking mechanism featured in EF bonds a context and its entities like a mother to her babies; when you try to separate one from the other (say, by disposing the ObjectContext with the close of a using block), you may well lose an arm. How, then, can we scope the ObjectContext without defeating the proverbial Wookiee at space-chess, I hear you say?

Go With The Current

In a small Windows-based application, using a single static instance of the ObjectContext is an acceptable plan. This way, changes from different areas of the application can be tracked and saved together, providing a clearer big picture of what your user is up to at any given point. Using a lazy-loading pattern in a static property on the ObjectContext, here’s how we can simplify access to the shared instance of our context:

private static AdventureWorksEntities _current;
/// <summary>
/// Gets the current AdventureWorks ObjectContext for
/// a Windows-based application.
/// </summary>
public static AdventureWorksEntities Current
{
    get
    {
        if (_current == null)
            _current = new AdventureWorksEntities();

        return _current;
    }
}

At some point in your application, you may need to smash this context instance into tiny bits and start over, but for the sake of not boring the other kids in the class to tears, I will leave such a mechanism as an exercise for the reader.

Do You Take Requests?

I like small Windows applications. Small Windows applications are nice. In fact, I’m thinking about dressing up as a small Windows application for Halloween this year. But the majority of my projects these days don’t involve small Windows applications anymore, and I’m willing to wager that you can identify. And we are certainly aware of the perils of spinning up static anything in ASP.NET. So as Web application and/or service developers, we must approach the Entity Framework in a different way in order to remain peril-free.

In order to scale well, as grown-up Web apps and services must, the best lifespan scope for our context is one per HTTP request. We can achieve this by stashing our context in the current HttpContext, like so:

private static AdventureWorksEntities _current;
/// <summary>
/// Gets the current AdventureWorks ObjectContext for
/// a Web-based application.
/// </summary>
public static AdventureWorksEntities Current
{
    get
    {
        // build a unique string to use as a key
        string key = HttpContext.Current.GetHashCode().ToString("x") + "_context";
        
        // if it doesn't already exist, create a 
        // new AdventureWorksContext and add it to the HttpContext
        if (!HttpContext.Current.Items.Contains(key))
            HttpContext.Current.Items.Add(key, new AdventureWorksEntities());

        return HttpContext.Current.Items[key] as AdventureWorksEntities;
    }
}

All Together Now

Being that this is the ObjectContext of your desire, we want it to be defined once in a library that can be shared by both Windows and Web-based apps in an Enterprise situation. Thus, combining the above methods, we arrive at the following:

private static AdventureWorksEntities _current;
/// <summary>
/// Gets the current AdventureWorks ObjectContext for
/// a Windows or Web-based application.
/// </summary>
public static AdventureWorksEntities Current
{
    get
    {
        if (HttpContext.Current != null) // web
        {
            // build a unique string to use as a key
            string key = HttpContext.Current.GetHashCode().ToString("x") + "_context";
            
            // if it doesn't already exist, create a 
            // new AdventureWorksContext and add it to the HttpContext
            if (!HttpContext.Current.Items.Contains(key))
                HttpContext.Current.Items.Add(key, new AdventureWorksEntities());

            return HttpContext.Current.Items[key] as AdventureWorksEntities;
        }
        else // windows
        {
            if (_current == null)
                _current = Create();

            return _current;
        }
    }
}

Lazy Like a Fox

So–a quick sidebar. Have you tried the new version of Entity Framework? You know, the one brought to you by the number 4? Well, the good guys over at Microsoft have now implemented lazy loading for related entities.  Cautious as they are, however, they left it switched off by default. Personally, I am spectacularly lazy and I prefer this feature to be enabled by default.  This is mostly because I was exhausted from doing the IsLoaded/Load dance every time I wanted to explore a related entity outside of a query.

Now, I get that this may cause some of you to fill your shorts with tremblings about performance, but I come from a place of clean and readable code by default, and targeted optimization by exception, where it’s needed. And those among you who have witnessed prematurely optimized code like I have are right there with me.

Get On With It

My point here is this.  Remember in Part the First, when we overrode the OnContextCreated method to add an handler to the SavingChanges event so that we could add custom business logic in the event of a save or delete?  That was awesome.  Well, we can also hook into that same function to tweak our ObjectContext instance with, say, our pet non-default preferences.  Full shorts be damned.

partial void OnContextCreated()
{    // set your fav defaults here
    this.ContextOptions.LazyLoadingEnabled = true;

    // Register the handler for the SavingChanges event.
    this.SavingChanges
        += new EventHandler(context_SavingChanges);
}

And Bob’s your uncle, there it is! The ObjectContext you can lug around with you and depend upon like an old blanket.  At least until .NET is dead and all the cool kids are making .edmx jokes at your expense. But viddy how readable our code can be in the meantime:

// get Jenkins on the phone!
var jenkins = (from p in AdventureWorksEntities.Current.Employees
    where p.Contact.LastName == "Jenkins"
    select p).First();

// check out lazy-loading the related Contact entity!
DialPhone(jenkins.Contact.Phone);

// Jenkins, YOU'RE FIRED.
jenkins.CurrentFlag = 0;

// poor Jenkins
jenkins.Contact.FirstName = "Mud";

// surely this violates Google's 'don't be evil' motto
// good thing we're using .NET
// and don't call me shirley
AdventureWorksEntities.Current.SaveChanges();

You can download the code from this article and its prequel here, and further pursue the subject here.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s