Managing Entity Framework ObjectContext lifespan and scope in n-layered ASP.NET applications

Jordan van Gogh describes a way to manage ObjectContext lifespan and scope in the Business Logic Layer (BLL) of a small to medium sized ASP.NET web application.

 

 

Introduction

Since the introduction of the ADO.NET Entity Framework, I've been looking for ways on how to incorporate this new technology in a typical n- layered ASP.NET application. I've started off by replacing the traditional - mostly handwritten - Data Access classes (DAL) with a database generated Entity Data Model (EDM). This offers some great benefits, for example by speeding up development time, and reducing the number of bugs in the DAL.

The middle layer – or Business Logic Layer (BLL) as I like to call it - classes remain effectively the same as in the .NET 2.0 days. The only difference is in how they use the Data Access objects, which needless to say reside in the Data Access Layer (DAL). Instead of referencing their counterpart DAL classes (for example the ProductBLL class methods may call data access methods in the ProductDAL class), the BLL classes now use an instance of an ObjectContext for their data access purposes. In this role, the ObjectContext class replaces the more traditional DAL classes.

Up till now everything I've described has been pretty straightforward. However, some problems may arise when you start to think about how to use an ObjectContext in a scenario where you have multiple BLL classes, and you want to be able to exchange business objects (i.e. domain entities) between instances of those classes. Since all changes are tracked by an ObjectContext (instead of by the objects themselves), it's not always easy to "mix" objects that originate from different ObjectContext instances. However, there are several ways to share ObjectContext instances between BLL classes within the BLL. Each with its own advantages and disadvantages.

One way to do it for example, is to use a single ObjectContext for each business transaction (in a Unit of Work pattern). Another way to do it, is by globally sharing a single ObjectContext instance between all BLL classes. Each of these methods will be discussed in more detail later in this article.

The problems of working with multiple ObjectContext instances

Like I mentioned in the introduction, working with multiple instances of an ObjectContext class simultaneously can be a bit troublesome. In the Entity Framework changes are tracked by an ObjectContext instance, instead of by the business objects/entities themselves. To be able to define and save a relationship between objects that originated from different ObjectContext instances, you would first have to “detach” an entity (i.e. business object) from its originating ObjectContext instance, and then “attach” it to the ObjectContext of the other entity. For example:

01.NorthwindObjectContext objectContext1 = new NorthwindObjectContext();
02.Categories category = objectContext1.Categories
03.                                    .FirstOrDefault(c => c.CategoryID == 1);
04.NorthwindObjectContext objectContext2 = new NorthwindObjectContext();
05.Products product = objectContext2.Products
06.                                 .FirstOrDefault(p => p.ProductID == 1);
07./* Detach from originating ObjectContext: */
08.objectContext1.Detach(category);
09./* Attach to the other ObjectContext: */
10.objectContext2.Attach(category);
11./* Define a relationship between the category and product objects: */
12.product.Categories = category;
13.objectContext2.SaveChanges();

Moreover, the Detach() method only detaches the object that was supplied as an argument. It does not automatically detach related objects.

The code sample above is just a demonstration of the concept of “detaching” and “attaching” entities. Obviously, I could have just loaded both the category and product objects from the same ObjectContext instance, and thus avoiding any problems whatsoever. This is also true for projects that query an Entity Data Model (EDM) directly in their Presentation Layer (in for example the code behind files). However, in my projects – no matter how small – I always use a middle layer (BLL) to store my LINQ-to-Entities queries and business logic. It’s my preferred way of working, because it promotes maintainability and extensibility. It also allows me to unit test my code.

Working with a middle layer (BLL) also means that you will be dealing with multiple BLL classes. There will probably be a separate BLL class for each of your business objects (i.e. domain entities). For example if we look at our code sample above, we might have created a ProductsBLL and a CategoriesBLL class. Business objects returned from instances of those classes, may define relationships with one another. Persisting these relationships will result in an exception being raised if both of the BLL classes use different ObjectContext instances. So we have to either make sure that both of the BLL classes use the same ObjectContext instance, or that we have “manually” tied the objects who defined the relationship to the same ObjectContext instance.

Writing code for attaching and detaching entities in a n-layered ASP.NET application can sometimes become a bit cumbersome, and moreover it’s often unnecessary. There are several good ways of sharing your ObjectContext instances among your BLL classes. In the sections that follow, I will discuss a few of these ways in detail. In the sample code that I have provided, I’ve also included a small generic framework for managing ObjectContext instantiation in your middle layer classes.

One shared static ObjectContext

Lets first start off with a way of sharing an ObjectContext that isn’t suitable for ASP.NET webapplications. I’ve read a lot of forum posts on the internet, in which developers tend to use an ObjectContext as a static class member. In this way the ObjectContext can be globally shared between all classes that use it. It offers some great flexibility, since all business objects/entities in the application originate from the same ObjectContext instance.

Listing 1: Using a static ObjectContext

01.private static NorthwindObjectContext _objectContext;
02./// <summary>
03./// Returns a shared static NorthwindObjectContext instance.
04./// </summary>
05.public NorthwindObjectContext ObjectContext
06.{
07.    get
08.    {
09.        if (_objectContext == null)
10.            _objectContext = new NorthwindObjectContext();
11.        return _objectContext;
12.    }
13.}

However, you shouldn’t use a static ObjectContext in an ASP.NET application, since static members have a lifespan beyond that of a single HTTP request. They’re actually bound to the lifespan of the AppDomain, which might be minutes or hours. In fact, static class members in ASP.NET are even shared between multiple threads and users of the application. Using the same ObjectContext instance from within multiple threads simultaneously can cause serious problems.

It’s also generally accepted that ObjectContext instances should have a short lifespan, so keeping an ObjectContext around for minutes or even hours is probably not a good practice. But if you’re looking for the flexibility that is provided using a static ObjectContext, then have a look at the solution that is presented in the next section.

One shared ObjectContext instance per HTTP request

Since sharing an ObjectContext as a static member in an ASP.NET application is a no-go, we have to find some other way to achieve similar flexibility. To do this we have to somehow store an ObjectContext instance in a central place for the lifespan of one - and only one - HTTP request. This is where the HttpContext.Current object comes into place.

The Items property of the HttpContext.Current object is a Hashtable for storing objects on a per-request basis. All of your BLL (middle layer) classes have access to this property, so this is a place in which we can store an ObjectContext instance, and then share it between all instances of those classes.

Listing 2: Using the HttpContext.Current.Items property

01./// <summary>
02./// Returns a shared ObjectContext instance.
03./// </summary>
04.public NorthwindObjectContext ObjectContext
05.{
06.    get
07.    {
08.        string ocKey = "ocm_" + HttpContext.Current.GetHashCode().ToString("x");
09.        if (!HttpContext.Current.Items.Contains(ocKey))
10.            HttpContext.Current.Items.Add(ocKey, new NorthwindObjectContext());
11.        return HttpContext.Current.Items[ocKey] as NorthwindObjectContext;
12.    }
13.}

It’s a good practice to use a “Lazy Load” pattern in this scenario, so that we only create a new ObjectContext instance when we actually need one.

By globally sharing an ObjectContext instance between classes on a per-request basis, you’ll make sure that every business object in your ASP.NET webapplication originates from the same ObjectContext instance. In this way, you won’t have to worry about which object is being tracked by which ObjectContext, and you can easily exchange business objects (i.e. entities) between all of your BLL classes. Since the ObjectContext instance has the lifespan of only one HTTP request, it’s also relatively short lived.

Note:

Be wary of any asynchronous operations that may occur within your ASP.NET application, when using a globally shared ObjectContext instance. Two or more threads simultaneously trying to update data using the same ObjectContext instance can cause serious problems.

One ObjectContext instance per business transaction

This is the last method of managing an ObjectContext lifespan that I’ll be discussing in this article. Using this method we will be creating and using one instance of an ObjectContext class within the scope of a business transaction. After the business transaction is complete, we will save all changes to the underlying data store and then dispose the ObjectContext instance.

By a business transaction I mean a series of operations that atomically belong together. For example: in a webshop application, if we want to store an order in our system, we would first have to create an Order object. Next we would convert the contents of the shopping cart into Orderline objects. These Orderline objects would then have to be added to the Order object. Only after these steps are complete, we can safely store the complete Order object to the database. By using the Entity Framework in such business transactions, changes should be tracked by one single instance of an ObjectContext class. Doing so we are actually applying the “Unit of Work” pattern.

Sharing an ObjectContext within the scope of a business transaction is a trivial task in an ASP.NET application that doesn’t make use of BLL classes. However, in applications that do have a middle layer things become a little bit harder. We need to somehow “connect” the BLL instances within the scope, so they can all use the same ObjectContext instance. This is best done using some sort of helper object, which would contain the ObjectContext instance, and thus defining the scope. We could then for example create an “AddToScope” method on the helper object, which would be responsible for placing the BLL instances into scope. However, there is a more elegant and transparent construction possible. By mimicking part of the behavior of the .NET Framework’s TransactionScope class, we could make our BLL classes self-aware of the scope they currently belong to.

In the sample code below, an instance of the custom UnitOfWorkScope class in conjunction with a (C#) “using” statement is responsible for defining the ObjectContext scope. All BLL instances created within scope can automatically detect it, and therefore use the same ObjectContext instance.

Listing 3: Defining the scope of a business transaction

01./* Create a new ObjectContext scope, in which all BLL (middle layer) objects share
02. * the same ObjectContext instance: */
03.using (new UnitOfWorkScope(true)) /* "true" flag indicates that all changes
04.                                   * should be saved at the end of the scope. */
05.{
06.    /* The OrderBLL class can automatically detect the current scope: */
07.    OrderBLL orderBLL = new OrderBLL();
08.    /* The OrderlineBLL class can automatically detect the current scope: */
09.    OrderlineBLL orderlineBLL = new OrderlineBLL();
10.    /* The ProductBLL class can automatically detect the current scope: */
11.    ProductBLL productBLL = new ProductBLL();
12.    /* Create a new order: */
13.    Order newOrder = new Order();
14.    newOrder.FirstName = "Jordan";
15.    newOrder.LastName = "van Gogh";
16.    // .. add more order properties here ...
17.    /* Make sure the newOrder object will be added to the data store: */
18.    orderBLL.Add(newOrder);
19.    /* Convert the ShoppingCartItem objects into Orderline objects: */
20.    foreach (ShoppingCartItem cartItem in _shoppingCart.Items)
21.    {
22.        Orderline newLine = new Orderline();
23.        /* Retrieve a Product object based on its PK: */
24.        Product product = productBLL.GetProductByID( cartItem.ProductID );
25.        /* We should be able to define a relationship between the 
26.         * newLine object and the product object since we are currently 
27.         * in a "Unit of Work" scope, in which both orderlineBLL and 
28.         * productBLL objects share the same ObjectContext instance: */
29.        newLine.Product = product;
30.        newLine.Quantity = cartItem.Quantity;
31.        /* Make sure the orderline object will be added to the data store: */
32.        orderlineBLL.Add(newLine);
33.        /* Define a relationship between the newLine and the newOrder objects: */
34.        newOrder.Orderlines.Add(newLine);
35.    }
36.} /* End of scope. The newOrder object and it's Orderline objects will 
37.   * now be persisted to the underlying data store. */

After studying the code sample above, those who aren’t familiar with the TransactionScope class may be wondering how the BLL objects can automatically detect the current scope, just by placing them in a “using” construct. Well, this is actually achieved by using a thread static class member. Values of thread static class members are only bound to the current thread, instead of the entire application as is the case with normal static members. Besides their scope, they act in the same way as normal static members. In .NET we can make a static class member thread static by adding a [ThreadStatic] attribute to it.

The UnitOfWorkScope class encapsulates an ObjectContext instance. By setting the value of a thread static member to a UnitOfWorkScope instance, we have a way of sharing its encapsulated ObjectContext among our BLL objects within the current thread. It’s almost similar to sharing and using a global static ObjectContext, except this time it’s only being shared within the current thread.

Listing 4: The UnitOfWorkScope class implementation

01./// <summary>
02./// Defines a scope wherein only one ObjectContext instance is created, 
03./// and shared by all of those who use it. Instances of this class are 
04./// supposed to be used in a using() statement.
05./// </summary>
06.public sealed class UnitOfWorkScope : IDisposable
07.{
08.    [ThreadStatic]
09.    private static UnitOfWorkScope _currentScope;
10.    private NorthwindObjectContext _objectContext;
11.    private bool _isDisposed, _saveAllChangesAtEndOfScope;
12.    /// <summary>
13.    /// Gets or sets a boolean value that indicates whether to automatically save 
14.    /// all object changes at end of the scope.
15.    /// </summary>
16.    public bool SaveAllChangesAtEndOfScope
17.    {
18.        get { return _saveAllChangesAtEndOfScope; }
19.        set { _saveAllChangesAtEndOfScope = value; }
20.    }
21.    /// <summary>
22.    /// Returns a reference to the NorthwindObjectContext that is created 
23.    /// for the current scope. If no scope currently exists, null is returned.
24.    /// </summary>
25.    internal static NorthwindObjectContext CurrentObjectContext
26.    {
27.        get { return _currentScope != null ? _currentScope._objectContext : null; }
28.    }
29.    /// <summary>
30.    /// Default constructor. Object changes are not automatically saved 
31.    /// at the end of the scope.
32.    /// </summary>
33.    public UnitOfWorkScope()
34.        : this(false)
35.    { }
36.    /// <summary>
37.    /// Parameterized constructor.
38.    /// </summary>
39.    /// <param name="saveAllChangesAtEndOfScope">
40.    /// A boolean value that indicates whether to automatically save 
41.    /// all object changes at end of the scope.
42.    /// </param>
43.    public UnitOfWorkScope(bool saveAllChangesAtEndOfScope)
44.    {
45.        if (_currentScope != null && !_currentScope._isDisposed)
46.            throw new InvalidOperationException("ObjectContextScope instances " +
47.                                                "cannot be nested.");
48.        _saveAllChangesAtEndOfScope = saveAllChangesAtEndOfScope;
49.        /* Create a new ObjectContext instance: */
50.        _objectContext = new NorthwindObjectContext();
51.        _isDisposed = false;
52.        Thread.BeginThreadAffinity();
53.        /* Set the current scope to this UnitOfWorkScope object: */
54.        _currentScope = this;
55.    }
56.    /// <summary>
57.    /// Called on the end of the scope. Disposes the NorthwindObjectContext.
58.    /// </summary>
59.    public void Dispose()
60.    {
61.        if (!_isDisposed)
62.        {
63.            /* End of scope, so clear the thread static 
64.             * _currentScope member: */
65.            _currentScope = null;
66.            Thread.EndThreadAffinity(); 
67.            if (_saveAllChangesAtEndOfScope)
68.                _objectContext.SaveChanges();
69.            /* Dispose the scoped ObjectContext instance: */
70.            _objectContext.Dispose();
71.            _isDisposed = true;
72.        }
73.    }
74.}

The UnitOfWorkScope class should only be used in a “using” construct. At the start of the construct a new UnitOfWorkScope instance is created. The constructor of this class creates a new ObjectContext instance. This ObjectContext is then shared and used by other BLL objects within the “using” construct. At the end of the construct the Dispose method of the UnitOfWorkScope object is automatically called, which in turn disposes the encapsulated ObjectContext instance.

In our BLL classes we can use the internal static CurrentObjectContext property to get a reference of the ObjectContext instance that’s bound the current scope. We could for example define a ObjectContext property within these classes, as in the code sample below:

Listing 5: Using the CurrentObjectContext property of the UnitOfWorkScope class

01.private NorthwindObjectContext _objectContext;
02./// <summary>
03./// Returns the ObjectContext instance that belongs to the 
04./// current UnitOfWorkScope. If currently no UnitOfWorkScope
05./// exists, a local instance of the NorthwindObjectContext class
06./// is returned.
07./// </summary>
08.protected NorthwindObjectContext ObjectContext
09.{
10.    get
11.    {
12.        if (UnitOfWorkScope.CurrentObjectContext != null)
13.            return UnitOfWorkScope.CurrentObjectContext;
14.        else
15.        {
16.            if (_objectContext == null)
17.                _objectContext = new NorthwindObjectContext();
18.            return _objectContext;
19.        }
20.    }
21.}

Using one “fresh” ObjectContext instance per business transaction, is generally considered to be a good practice. And by using the techniques I demonstrated in this section, you can also make sure that it’s properly disposed when it’s no longer needed.

Note:

Using thread static class members in a wider scope than in the specified “using” construct can result in unpredictable behavior. This is because ASP.NET threads might be re-used, and thus if there are any leftover thread static values, they might also be unknowingly used again.

Sample Application

To demonstrate the techniques that I’ve described in this article I've created a small sample application, based on a part of the Northwind database. It also includes a small ready-to-use generic ObjectContext management framework. As you may see in the screenshot of the VS solution explorer below, I've created a 3-layered ASP.NET application.

The solution consists of 5 projects. I will give a brief overview of the functions of each of the projects, and how they relate to one another. I won't go into too much detail, since this article is about managing ObjectContext lifespan and scope, and not about n-layered design in general.

Note:

The code samples that I’ve presented in the previous sections of this article, are loosely based on the code in the sample project. However, they have been simplified to avoid confusion. So you may find that although some classes share the same name as classes used in the previous code samples, they may have different implementations.

Northwind.DataLayer.Model

This project represents the Data Access Layer (DAL) of the application. It contains its main data access component, in the form of an ObjectContext. Since I chose to generate a model directly from database, the business objects (i.e. domain entities) are also contained in this project. I've have selected two tables from the Northwind database - Categories and Products - to be included in my model. This means we have two business objects: Categories and Products.

Note:

As you may see, the names of the business objects are plural. This is because all the tables in the Northwind database are named plural.

Northwind.BusinessLayer.Facade

This project represents the Business Logic Layer (BLL): the core of the application. In here we will store most of the LINQ-to-Entities queries. Although nowadays it’s easy to use an Entity Data Model directly in the Presentation Layer, it offers many benefits to isolate and centralize your LINQ-to-Entities queries, business validation rules, and other business logic in a separate project. For instance it can dramatically improve the maintainability and extensibility of your application. Another big advantage is that it will also enable you to unit test your queries and logic.

I've chosen to name this project Façade, because it only acts as a gateway for the ObjectContext class of the DAL. It contains some business validation rules, but in this case it doesn't contain any real business logic. In larger - real life - solutions, I usually add another project to my Business Layer called BusinessLogic to store any real business logic that may exist (in this case that would have resulted in a Northwind.BusinessLayer.BusinessLogic project). A Façade project would then provide a simplified interface to the classes contained in that project.

The class diagram of the Façade project:

The FacadeBase class is the generic base class for the other Façade classes. It offers some virtual generic methods to provide a consistent interface for derived classes. I've included a separate derived Façade class for each of the business objects. These classes provide an interface for the Presentation Layer to work with. The Presentation Layer is not allowed to use an ObjectContext directly, so every business object/entity operation must be done using the Façade classes.

Then there is also the UnitOfWorkScope class, which in this sample application is derived from the generic ObjectContextScope class of the Framework project. The function of this class has already been discussed in the previous section.

Northwind.BusinessLayer.Facade.Tests

This is the unit testing project. It contains some unit tests for the Façade project (the BLL). I've only included a few partial tests for demonstration purposes only.

Northwind.Framework.ObjectContextManagement

Classes contained within this project manage ObjectContext instantiation and scope. It's a generic framework, so it can be easily integrated in other projects too.

The class diagram of this project:

The generic ObjectContextManager classes manage ObjectContext instantiation and scope for the Business Logic Layer classes of the application (in this case the Façade classes). Each derived ObjectContextManager class handles instantiation and scope in its own distinct way.

  • AspNetObjectContextManager: This manager class can only be used in an ASP.NET application. Provides one (and only one) ObjectContext instance for the lifespan of one HTTP request. This instance is then shared by all BLL/Facade classes who make use of it. It also comes with a HttpModule - the AspNetObjectContextDisposalModule class - that automatically handles the disposal of the shared ObjectContext instance at the end of the HTTP request.
  • StaticObjectContextManager: Provides one static ObjectContext instance for the lifespan of the AppDomain. All BLL/Facade classes who use this class, will share the same ObjectContext instance. This can be useful in small Console applications or maybe unit testing projects, however you shouldn't use it in your ASP.NET websites and webapplications. I have earlier explained why.
  • ScopedObjectContextManager: This manager class allows you to define your own ObjectContext scopes. Scopes are defined by creating an instance of an ObjectContextScope class in a (C#) "using" construct. An ObjectContext scope maintains only one instance of an ObjectContext class. BLL/Facade objects created within the "using" construct (and thus within scope) can then automatically detect the current scope through an encapsulated ScopedObjectContextManager instance. All of those BLL/Facade objects will then automatically use and share the ObjectContext instance that’s bound to the current scope. At the end of the scope this ObjectContext instance is also automatically disposed.

Managing ObjectContext instantiation

The Visual Studio solution of a typical n-layered ASP.NET application is likely to include more than one project that references the BLL project of the solution. If you look at the sample application, besides the website project in the Presentation Layer, the Northwind.Facade.Tests project also makes use of the BLL/Façade classes. If for example you want your website to use a globally shared ObjectContext instance per HTTP request and you integrate this technique in your BLL/Façade classes, you would be unable to use these classes in your Tests project, since the Tests project does not run in a http context and is therefore unable to use the HttpContext.Current.Items property. A way to overcome such problems is to move to the process of ObjectContext instantiation out of your BLL/Façade classes, and to put it in a separate class. You would create a separate class for each technique that you want to be able to use in your application. Your BLL/Façade classes should then use an instance of one of these classes based on application configuration.

In the sample application I’ve provided a small generic framework for managing ObjectContext instantiation and scope within your BLL/Façade classes. It consists of an abstract base class called ObjectContextManager, and three derived classes called AspNetObjectContextManager, StaticObjectContextManager and ScopedObjectContextManager. The behavior of each of these classes have been described in the previous section of this article. The Façade (BLL) classes are able to instantiate one of these classes based on application configuration.

For example, the Web.config file of the website contains the following configuration:

1.<configSections>  
2.    <section name="Northwind.BusinessLayer.Facade.ObjectContext" 
3.           type="System.Configuration.SingleTagSectionHandler"/>
4.</configSections>
5.<Northwind.BusinessLayer.Facade.ObjectContext 
6.managerType="Northwind.Framework.ObjectContextManagement.AspNetObjectContextManager"/>

Based on these configuration settings, the Façade/BLL classes will use one globally shared ObjectContext per HTTP request.

The Northwind.BusinessLayer.Facade.Tests project on the other hand, has a slightly different configuration:

1.<configSections>
2.  <section name="Northwind.BusinessLayer.Facade.ObjectContext"
3.           type="System.Configuration.SingleTagSectionHandler"/>
4.</configSections>
5.<Northwind.BusinessLayer.Facade.ObjectContext
6. managerType="Northwind.Framework.ObjectContextManagement.ScopedObjectContextManager"
7. />

With these configuration settings, the Façade/BLL classes will use one single ObjectContext per business transaction.

Using these ObjectContextManager classes for managing ObjectContext instances, will make your BLL more adaptable. If for example you ever to need to create a new project based on a brand new Presentation Layer technology, you only need to derive a new ObjectContextManager class, and configure your BLL to use it.

Figure 5: The relationship between the Façade classes and the generic ObjectContextManager classes

The relationship between the Façade classes and the generic ObjectContextManager classes

In this article I’ve described a few methods of managing ObjectContext lifespan and scope in a n-layered ASP.NET application. Each of these methods works in its own distinct way. We have seen how to use one globally shared ObjectContext per HTTP request, and we have also seen how to share one ObjectContext between multiple middle layer objects in a business transaction.

It’s important to understand that this is just my take on the matter. There might be several better ways of accomplishing the same tasks, that I haven’t figured out yet. So keep on reading!

 

From:http://dotnetslackers.com/articles/ado_net/Managing-Entity-Framework-ObjectContext-lifespan-and-scope-in-n-layered-ASP-NET-applications.aspx

posted @ 2010-11-12 11:31  zhh  阅读(940)  评论(0编辑  收藏  举报