In my previous post, I pointed out a good article by Rick Strahl on different ways to handle DataContext management and mentioned that I used his 'Business Object' method. This actually isn't quite accurate, so let me expand on that a little bit.
I'll use a standard e-Commerce site as an example. If you think about most of the data access that is required when creating an eCom site, there is little about the DataContext that you really need to worry about. Product lists, navigation elements, search results, and other items are essentially read-only, so you can use a DataContext and get rid of it.
Even things like user profiles or shopping carts do not require you to carry around a DataContext. If you need to add/remove something to a shopping cart, it is much better to recreate the cart, make the adjustments, and immediately submit your changes on the shopping cart page than to hold on to a DataContext, or even the cart itself (if you need to keep the count for display purposes, it is much better to save the count itself in a session variable...you don't need the cart except on your shopping cart page). The same can be said for user profile information (if you need to worry about elements of a user's profiles, it is better to save those elements than the full profile). Not carrying around complex objects makes a web application significantly more scalable.
However, when it comes to something like the checkout process, a process that needs to traverse multiple pages (and often both backwards and forwards), the need to manage your DataContext becomes important. When moving a code base over to LINQ to SQL initially, I tried to use a DataContext per page, and then attempt to detach, attach, pulse until smooth, etc. on the order review page, and it was untenable and unmaintainable.
Instead, what I found does work is to create a 'Business Process' class (for this example, let's call it Checkout) that manages a single DataContext throughout its entire life cycle.
In its constructor, I would do something like:
public Checkout()
{
_context = new MyDataContext();
}
where _context is a private field that only has a public read-only property. The checkout class is created at the beginning of the checkout process (typically, when the 'Checkout' button is clicked) and stored in session.
On each page within the checkout process, if I need to do any data access work, I can use the contained DataContext and since it is carried around throughout the process, I can call SubmitChanges() when the order is submitted, and everything works just fine.
I won't walk through the entire process, but imagine a page where you need to enter shipping information. Your Address object is likely to want to have a State object and at first glance, this poses a problem. When your UI address form contains a dropdown list to let you choose a state, that list is going to be populated from some other DataContext. But this is okay. You can recreate the State object from the value of the selected item by using your contained DataContext, which you will also use to InsertOnSubmit() the new Address object (or if it is an existing item chosen from a user profile generated list, the DataContext will already contain it and so you can SubmitChanges() just fine). You do have an extra call to the database doing this, but it's pretty minor (frankly, if your scalability is threatened by this, you have other issues), or you could set the StateID directly on your Address object (if that is how your LINQ classes are setup). You will also need to create a few extra methods in your data layer partial classes to accept a DataContext, but this is again pretty minor.
By focusing your DataContext management on a process instead of at the individual business object level, you can eliminate almost all management entirely (since it is almost always unimportant), implementing a process-level DataContext only where it is needed.