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.
08.
objectContext1.Detach(category);
09.
10.
objectContext2.Attach(category);
11.
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.
02.
03.
using
(
new
UnitOfWorkScope(
true
))
04.
05.
{
06.
07.
OrderBLL orderBLL =
new
OrderBLL();
08.
09.
OrderlineBLL orderlineBLL =
new
OrderlineBLL();
10.
11.
ProductBLL productBLL =
new
ProductBLL();
12.
13.
Order newOrder =
new
Order();
14.
newOrder.FirstName =
"Jordan"
;
15.
newOrder.LastName =
"van Gogh"
;
16.
17.
18.
orderBLL.Add(newOrder);
19.
20.
foreach
(ShoppingCartItem cartItem
in
_shoppingCart.Items)
21.
{
22.
Orderline newLine =
new
Orderline();
23.
24.
Product product = productBLL.GetProductByID( cartItem.ProductID );
25.
26.
27.
28.
29.
newLine.Product = product;
30.
newLine.Quantity = cartItem.Quantity;
31.
32.
orderlineBLL.Add(newLine);
33.
34.
newOrder.Orderlines.Add(newLine);
35.
}
36.
}
37.
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.
50.
_objectContext =
new
NorthwindObjectContext();
51.
_isDisposed =
false
;
52.
Thread.BeginThreadAffinity();
53.
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.
64.
65.
_currentScope =
null
;
66.
Thread.EndThreadAffinity();
67.
if
(_saveAllChangesAtEndOfScope)
68.
_objectContext.SaveChanges();
69.
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
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!
About Jordan Van Gogh
|
Sorry, no bio is available
View complete profile here.
|
You might also be interested in the following related blog posts
DLinq: Mapping a Database to a DataContext class.
read more
DLinq: Playing with knives.
read more
Get The Most Out Of Your Format String
read more
Talking Points: ADO.NET Entity Framework
read more
Sneak Peek at the EntityDataSource Control
read more
EF: GetRelationTypeExpensiveWay
read more
Exploring EntityKeys, Web Services and Serialization a little further
read more
|
|
Please login to rate or to leave a comment.