Blog of Keith Craig, containing my thoughts, comments and questions. RSS Feed


LINQ to SQL and the "Request-scoped DataContext" Pattern

In this post, I describe a LINQ to SQL architectural pattern for ASP.NET I call “request-scoped DataContext”. It’s based on the Hibernate pattern “thread-local session”.

LINQ to SQL is looking promising, and has the possibility to really increase productivity for enterprise application development. Most enterprise development is done using a layered architecture, usually with the following three layers:

· Presentation—displays data and manages user interaction

· Business logic—implements logic of the application

· Data access—encapsulates communication with the database to load and save data

These layers allow developers to think mainly about one set of concerns at a time, to centralize similar types of code, and to increase unit testability. Essentially, it’s easier to understand and maintain a large application written this way. However, most of the blog posts and examples I have seen about LINQ to SQL (I’ll just call it “LINQ” from here on) involve these three layers smashed together into a single code-behind file for a web page. It’s certainly easier to write, read, and understand a simple example when architected this way, but it leaves open the question of how best to use LINQ in an N-tier ASP.NET application.

When developing with LINQ, management of the DataContext is crucial. The DataContext needs to be created before objects are modified, and it needs to be around to call SubmitChanges() on after they have been modified. As it is a stateful object, you need to call SubmitChanges() on the same instance of the DataContext as you got the objects from (or attached them to).

Given this, I wrote a simple static utility class to assist in management of the DataContext. This example and pattern is based on some of the ideas in the excellent book Hibernate in Action by Christian Bauer and Gavin King. I also borrowed their example of a simple online auction site, as it’s a simple yet understandable example. Here’s the code for my LINQ utility class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Linq;
using System.Reflection;
using System.Threading;
using System.Web;
using BLL;
 
namespace DAL
{
    /// <summary>
    /// Simple class to implement request-scoped DataContext pattern for LINQ to SQL.
    /// 
    /// Note: users must change "BiddingDbDataContext" to their own strongly-typed DataContext.
    /// </summary>
    public static class LinqUtil
    {
        #region Privates
 
        /// <summary>
        /// Dictionary key for the DataContext in HttpContext.Current.Items
        /// </summary>
        private const string DATACONTEXT_ITEMS_KEY = "LinqUtilDataContextKey";
 
 
        /// <summary>
        /// Private property to store the DataContext in the HttpContext.Current.Items
        /// </summary>
        private static BiddingDbDataContext InternalDataContext
        {
            get
            {
                return (BiddingDbDataContext)HttpContext.Current.Items[DATACONTEXT_ITEMS_KEY];
            }
            set
            {
                HttpContext.Current.Items[DATACONTEXT_ITEMS_KEY] = value;
            }
        }
 
        #endregion
 
        #region Public and Protected Properties and Methods
 
        /// <summary>
        /// Returns the current DataContext. If none configured yet, then creates a new one and returns it. Internal access
        /// so that only the DAL layer can access.
        /// </summary>
        /// <returns>A reference to a DataContext</returns>
        internal static BiddingDbDataContext Db
        {
            get
            {
                // If the context is missing, create a new one
                if (InternalDataContext == null)
                {
                    // Note: in a real app, this should get the connection string from secure storage and pass it to the context constructor.
                    string connectionString = @"Data Source=(local)\sqlexpress;Initial Catalog=CaveatEmptor;Integrated Security=True;Pooling=False";
 
                    InternalDataContext = new BiddingDbDataContext(connectionString);
                }
 
                return InternalDataContext;
            }
        }
 
        /// <summary>
        /// Saves all changes on the current DataContext. Public scope to allow calling from upper tiers of application.
        /// </summary>
        public static void SubmitChanges()
        {
            Db.SubmitChanges();
        }
 
        /// <summary>
        /// Cleanup the context (dispose the context and set it to null). Public scope allows calling from upper tiers.
        /// </summary>
        public static void CleanUp()
        {
            if (InternalDataContext != null)
            {
                InternalDataContext.Dispose();
                InternalDataContext = null;
            }
        }
 
        #endregion
    }
}

Note that “BiddingDbDataContext” needs to be replaced with your actual strongly-typed DataContext type.

Basically, this class automates the handling of the DataContext, by storing a single DataContext in the System.Web.HttpContext.Current.Items collection for use by the request (hence the name “request-scoped DataContext”). For more background on why I'm using the HttpContext.Current.Items collection and why my earlier plan of using a static variable marked as [ThreadStatic] isn’t completely safe, see this excellent post by Scott Hanselman. One slightly weird aspect of this is that the DAL assembly needs to reference System.Web. (Note that for a WinForm or WPF application, you could use a static variable tagged with [ThreadStatic] to store the DataContext. In this case, you would need to be sure to do all your work with a given DataContext on the same thread.)

The LinqUtil class offers three non-private static properties and methods:

· Db – returns a reference to the strongly-typed DataContext for the application. If there isn’t one yet, it creates a new one

· SubmitChanges() – saves all tracked object changes

· CleanUp() – disposes the DataContext and sets it to null on the thread

Note that the Db property is scoped as internal—this class is assumed to be part of the data access layer assembly, and it’s cleaner if the presentation and business layers cannot get direct access to the DataContext. The other two methods are public, and are meant to be used from the presentation layer.

Here’s a high-level diagram of the sequence of events when a web request comes in using this pattern (time flows from left to right here):

clip_image002

First the presentation layer calls the DAL to load the relevant objects. It then calls methods on the loaded objects to do logic (perhaps multiple times), and finally saves the objects to the database using the data layer. The nice thing about using the utility class is that everything in this request will be done using the same DataContext, without having to explicitly pass it around. Note that the presentation layer references both the DAL and the BLL, the DAL references the BLL, and the BLL references no other layers. (The fact that the BLL doesn’t reference any other layers makes it much easier to unit test it!) Here’s a screenshot of what the example solution looks like:

clip_image004

The presentation layer would do something like the following example code (the itemId and userId are hardcoded for this example; the code here is modeled after the example code for the thread-local session Hibernate pattern from Hibernate in Action):

        protected void btnBid_Click(object sender, EventArgs e)
        {
            decimal bidAmount = decimal.Parse(txtBidAmount.Text);
            int itemId = 1;
            int userId = 1;
 
            ItemDAO itemDAO = new ItemDAO();
            UserDAO userDAO = new UserDAO();
 
            decimal maxBidAmount = itemDAO.GetMaxBidAmount(itemId);
            Item item = itemDAO.GetItemById(itemId);
            Bid newBid = item.PlaceBid(userDAO.GetUserById(userId), bidAmount, maxBidAmount);
 
            LinqUtil.SubmitChanges();
        }
 

Note that the first call into the DAL (GetMaxBidAmount) implicitly creates a strongly-typed DataContext for this request and stores it in the HttpContext.Current.Items collection. Then all subsequent calls to the DAL reuse that same DataContext, which is then also used in the final SubmitChanges() call.

ItemDAO is a simple DAL class for loading Item-related data:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Linq;
using System.Text;
using BLL;
 
namespace DAL
{
    public class ItemDAO
    {
        public Item GetItemById(int itemId)
        {
            return LinqUtil.Db.Items.Single(i => i.ItemId == itemId);
        }
 
        public decimal GetMaxBidAmount(int itemId)
        {
            decimal? maxBidAmount = LinqUtil.Db.Bids.Select(b => b.Amount).Max();
 
            return maxBidAmount.GetValueOrDefault(0M);
        }
 
    }
}

The GetItemById() ends up being a one-liner, since the LinqUtil class automatically manages the DataContext.

Item is a multi-part class in the BLL which was partially generated by the LINQ design surface; the other part contains custom business logic code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace BLL
{
    public partial class Item
    {
        public Bid PlaceBid(User bidder, decimal bidAmount, decimal maxBidAmount)
        {
            if (this.EndDate < DateTime.Today)
                throw new ApplicationException("Auction already ended.");
 
            if (bidAmount <= maxBidAmount)
                throw new ApplicationException("Bid not high enough");
 
            Bid newBid = new Bid();
            newBid.Amount = bidAmount;
            newBid.Item = this;
            newBid.DatePlaced = DateTime.Today;
            newBid.User = bidder;
 
            this.Bids.Add(newBid);
            return newBid;
        }
    }
}

One thing I really like about this pattern is that the BLL methods have no explicit database calls or references to the DataContext—they may lazy-load something if it’s not already loaded, but other than that, it’s free of database logic, and therefore much easier to unit test.

Finally, since it implements IDisposable, it’s probably a good idea to clean up the DataContext at the end of the request. I did this in the Application_EndRequest event handler in the global.asax.cs:

        protected void Application_EndRequest(object sender, EventArgs e)
        {
            LinqUtil.CleanUp();
        }

 

I hope the idea at the core of this pattern is useful; I wanted to share it because it seemed more elegant than the example LINQ to SQL code I’ve seen so far.

PS: I wrote this using Beta 1 of Orcas. The code details may change as Orcas evolves, but the idea should remain relatively unchanged.

 
Posted by Keith Craig | 24 Comments | Trackback Url | Bookmark with:        
Tags:

Links to this Post

Comments

Monday, 2 Jul 2007 01:05 by Re: LINQ to SQL and the "Request-scoped DataContext" Pattern
Keith, Thanks for this; I've been trying to find an example of n-tier LINQ for a while now. It's reassuring to see you have come to the same conclusion about putting the DC in in the HTTPContext as I have. One minor thing I would chnage is to make the DAO methods static so that you do not have to instantiate the DAO objects first (or have I missed something?). I am also very tempted to put the DAO methods into the BLL class as well. I know this breaks your principle of keeping data access out of the BLL so that it is testible - but I can't help thinking it would make it a lot neater and you would still be able to test the actual logic methods without data access. Have you made any improvements or even chnaged your mind about any of this since you wrote this? Many thanks for sharing, JasonBSteele

Monday, 2 Jul 2007 09:41 by Reply to Re: LINQ to SQL...
Jason, Thanks for the feedback! I don't see any issues with making the DAO methods static--I think it's a fine idea. Another option would be to put all the DAL methods into a single static class rather than separate classes for each BLL object. As long as you have a good naming scheme, this might be easier. (It's likely best for smaller projects.) In terms of putting the data access methods into the BLL, the only issue that I can think of is that I made the Db property that returns the DataContext internal, so that only the DAL assembly could access it. Merging the BLL and DAL would mean that both the "BLL-like" and "DAL-like" methods could access it. Likely this would just mean that developers would need to be a bit more careful to make sure they don't access the property from the wrong method--I don't think this would be too problematic. I agree that mixing the BLL and DAL methods in a single class doesn't affect testability as long as the BLL-like methods don't call into the database. Other than the thoughts above inspired by your comments, I haven't thought of any other improvements. I'll write more if I do.

Saturday, 14 Jul 2007 01:12 by ExecutionContext
Did you considered using ExecutionContext instead of HttpContext? This way the same utility class can be used in other scenarios, like WinForm.

Tuesday, 17 Jul 2007 10:06 by Re: ExecutionContext
Until you posted your reply, I had not known about the ExecutionContext! Thanks! Yes, I think this is a good idea--the only concern I have is that I'm not sure if the ExecutionContext survives between web requests. If so, I think this could be a bit of a risk, as intentionally unsaved changes from a previous request could be unintentionally saved by a subsequent request, creating a very hard to reproduce bug. If the ExecutionContext is persisted between web requests, then it is a simple modification to build a check into the LinqUtil class to see if you are running in ASP.NET or something else. If HttpContext.Current is null, it is not ASP.NET, and you should use the ExecutionContext; otherwise, use the HttpContext.Current.Items collection.

Wednesday, 18 Jul 2007 07:38 by distributed architecture
Would this would work in a distributed application (ie seperate servers for web server and application server where only the app server has access to the database)? If you wanted to modify a database row then save the changes would you be able to make the modifications in the presentation layer then call submit changes, or would you call a specific method in the bll with the parameters to change and it would do the work?

Monday, 23 Jul 2007 12:57 by Re: distributed architecture
Good question! I myself usually try to keep the web and business logic running on the same server (it is simpler and performs better), but sometimes you must separate them for other reasons. My understanding is that the DataContext is not serializable (see http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=482413&SiteID=1, for example). Given this, I think that you would need to do something different in a distributed application. I haven't thought much about that type of architecture, but specific methods in the BLL to change values is one option. --Keith

Monday, 27 Aug 2007 06:47 by ObjectTrackingEnabled Option
Might be a good idea to also include an optional value to turn off ObjectTrackingEnabled to speed things up where you do not need to modify data like getting a list of products for display on a shopping cart. Maybe something like this would work: Friend Shared ReadOnly Property Db(Optional ByVal TrackObjects As Boolean = True) As UltraWellnessDataClassesDataContext Get ' If the context is missing, create a new one If InternalDataContext Is Nothing Then ' Note: in a real app, this should get the connection string from secure storage and pass it to the context constructor. ' Dim connectionString As String = ConfigurationManager.ConnectionStrings("ultrawellnesscenterConnectionString").ConnectionString InternalDataContext = New UltraWellnessDataClassesDataContext InternalDataContext.ObjectTrackingEnabled = TrackObjects End If Return InternalDataContext End Get End Property Then use this instead: Public Function GetCustomer(ByVal CustomerID As Guid) As Customer Return (From c In LinqUtil.Db(False).Customers Where c.CustomerID = CustomerID).Single End Function

Tuesday, 4 Sep 2007 01:18 by Connection caching etc?
Can someone make me feel a lot better here...? Is the DC as good at managing its connection pooling etc as for instance, MTS and COM+ were? When you destroy the DC at the end of the request cycle, will its connection be completely destroyed and then re-opened again on the next request (if necessary)? I'm somewhat concerned about this, as a constantly opening and closing connection, per user, is not a good thing. But I do suppose we could just write our own caching mechanism and make sure that the DC uses that cached connection whenever it needs to do anything; what that means though is that I am assuming that if I assign the DC a shared connection that its not going to kill it when I recycle the DC at the end of the request. Any feedback here would be great, thanks in advance. BTW Keith, FANTASTIC post m8, thanks for sharing.

Tuesday, 4 Sep 2007 09:08 by RE: Connection caching etc?
The .NET managed data access classes generally implement connection pooling themselves, unless you turn it off. For example, see http://msdn2.microsoft.com/en-us/library/8xx3tyca(vs.71).aspx, or http://www.ondotnet.com/pub/a/dotnet/2004/02/09/connpool.html. I've mainly used SQL Server, but connection pooling has worked fine in .NET without using COM+ or MTS. I believe that COM+ and MTS connection pooling was only important in the days when ADO (not ADO.NET, but plain vanilla ADO) didn't support pooling. Given this, I don't know of any issues related to pooling when using the DataContext. Hope this helps--and thanks for the positive feedback!

Wednesday, 26 Sep 2007 09:51 by It seems odd to me that the Presentation layer accesses the DAL
I think the Dal should implement interfaces and that the business layer and presentation layer could then reference those interfaces. The implementation of the interfaces could be handled by a Daofactory and possibly accessed in the presentation layer using an IOC container. (Castle). This seems more in line with the Hibernate architecture, at least the one espoused by Bill McCafferty, <a href=http://www.codeproject.com/aspnet/NHibernateBestPractices.asp>Check out his enterprise architecture example</a>

Thursday, 27 Sep 2007 10:50 by RE: It seems odd to me that the Presentation layer accesses the DAL
Thanks for the link--I had not seen that article, and it looks very interesting. As I said in the post, I borrowed the idea for this pattern from the book "Hibernate in Action", written by the creators of Hibernate. I agree that it differs from the article you cited--there are many different ways to build an application using Hibernate. I think that, for many applications, the inversion of control pattern can be very useful. However, you either need an IoC framework like Castle Windsor or Spring.NET, or you end up writing and maintaining significant amounts of additional code for the interfaces. On the other hand, the pattern I've described here seems simpler, in that there's less code and no additional framework required. I plan to take a closer look at the article you cited and give it some thought. Thanks again for the link! Cheers, Keith

Sunday, 2 Dec 2007 05:21 by What else is in the Item class?
You say: "Item is a multi-part class in the BLL which was partially generated by the LINQ design surface". Can you post the rest of this class or at least a representative part? I am not clear on this part because it sounds like it is a DataContext, but DataContext is already defined in "BiddingDbDataContext" in your example. I am new to Linq so I apologize if the answer is obvious.

Monday, 3 Dec 2007 09:31 by Have you tried this with the RTM version?
I just found your post after writing about almost exactly the same thing: http://blog.codeville.net/2007/11/29/linq-to-sql-the-multi-tier-story/ I completely agree that "Request-scoped DataContext" (I called it DataContext-per-unit-of-work) is a helpful pattern, but I haven't yet figured out how to control the DataLoadOptions in a sensible way. Have you tried this?

Tuesday, 4 Dec 2007 11:33 by RE: Have you tried this with the RTM version?
Sorry, I've been busy with other things, and haven't had time to do much with the RTM yet. Specifically, I haven't tried the DataLoadOptions. --Keith

Tuesday, 4 Dec 2007 11:42 by RE: What else is in the Item class?
The rest of the Item class is what the LINQ to SQL designer created. It's basically just the fields associated with the columns of the Item table, and some properties and code to make it work well with LINQ to SQL. Here's the code (it might be from a slightly different version of the program, but it's just to give you an idea of what it looks like). Hope this helps! --Keith --------------------------- [global::System.Data.Linq.Table(Name="dbo.Item")] public partial class Item : global::System.Data.Linq.INotifyPropertyChanging, global::System.ComponentModel.INotifyPropertyChanged { private int _ItemId; private string _Name; private string _Description; private decimal _InitialPrice; private global::System.DateTime _EndDate; private global::System.Data.Linq.EntitySet<Bid> _Bids; [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] public Item() { this._ItemId = default(int); this._Bids = new global::System.Data.Linq.EntitySet<Bid>(new global::System.Action<Bid>(this.Attach_Bids), new global::System.Action<Bid>(this.Detach_Bids)); } [global::System.Data.Linq.Column(Storage="_ItemId", Name="ItemId", DBType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDBGenerated=true, CanBeNull=false)] public int ItemId { get { return this._ItemId; } } [global::System.Data.Linq.Column(Storage="_Name", Name="Name", DBType="VarChar(50) NOT NULL", CanBeNull=false)] public string Name { get { return this._Name; } set { if ((this._Name != value)) { this.OnPropertyChanging("Name"); this._Name = value; this.OnPropertyChanged("Name"); } } } [global::System.Data.Linq.Column(Storage="_Description", Name="Description", DBType="VarChar(MAX) NOT NULL", CanBeNull=false)] public string Description { get { return this._Description; } set { if ((this._Description != value)) { this.OnPropertyChanging("Description"); this._Description = value; this.OnPropertyChanged("Description"); } } } [global::System.Data.Linq.Column(Storage="_InitialPrice", Name="InitialPrice", DBType="Decimal(0,0) NOT NULL", CanBeNull=false)] public decimal InitialPrice { get { return this._InitialPrice; } set { if ((this._InitialPrice != value)) { this.OnPropertyChanging("InitialPrice"); this._InitialPrice = value; this.OnPropertyChanged("InitialPrice"); } } } [global::System.Data.Linq.Column(Storage="_EndDate", Name="EndDate", DBType="SmallDateTime NOT NULL", CanBeNull=false)] public global::System.DateTime EndDate { get { return this._EndDate; } set { if ((this._EndDate != value)) { this.OnPropertyChanging("EndDate"); this._EndDate = value; this.OnPropertyChanged("EndDate"); } } } [global::System.Data.Linq.Association(Name="FK_Bid_Item", Storage="_Bids", OtherKey="ItemId", ThisKey="ItemId")] public global::System.Data.Linq.EntitySet<Bid> Bids { get { return this._Bids; } set { this._Bids.Assign(value); } } public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanging; public event global::System.ComponentModel.PropertyChangedEventHandler PropertyChanged; [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] protected void OnPropertyChanging(string propertyName) { if ((this.PropertyChanging != null)) { this.PropertyChanging(this, new global::System.ComponentModel.PropertyChangedEventArgs(propertyName)); } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] protected void OnPropertyChanged(string propertyName) { if ((this.PropertyChanged != null)) { this.PropertyChanged(this, new global::System.ComponentModel.PropertyChangedEventArgs(propertyName)); } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] private void Attach_Bids(Bid entity) { this.OnPropertyChanging(null); entity.Item = this; this.OnPropertyChanged(null); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] private void Detach_Bids(Bid entity) { this.OnPropertyChanging(null); entity.Item = null; this.OnPropertyChanged(null); } }

Monday, 10 Dec 2007 09:02 by LINQ to SQL is driving me crazy
At how to implement n-tier, I think you may have hit the nail on the head. Let's hope so, because I'm running with it on a new project! - Scott

Tuesday, 1 Jan 2008 06:10 by Re: LINQ to SQL and the "Request-scoped DataContext" Pattern
This approach has a problem, say we will invoke 2 business methods during a request, based on the architecture, they will share the same DataContext stored in the HttpContext. Now method 1 returns a query and method 2 need to apply an LoadOptions and return another query, if you really do this, an exception will come out. "Setting load options is not allowed after results have been returned from a query." The DataContext contains too much information that prevents it to be reused. Do you have any solution?

Monday, 7 Jan 2008 04:05 by RE: LINQ to SQL and the "Request-scoped DataContext" Pattern
I haven't tried that--it's an interesting issue. I'll need to think about it a bit more... --Keith

Wednesday, 23 Jan 2008 05:38 by RE: LINQ to SQL and the "Request-scoped DataContext" Pattern
Hi, any further thoughts on the DataLoadOptions issue? As far as I can see it's a killer for this approach and pretty much for Linq to Sql in N-tier. It means that the only workable approach is one datacontext per method call unless the calling code has knowledge of the loadOptions used by the dataaccess code (yuk). Maybe this is a workable approach? It's certainly far from ideal... Rupert

Tuesday, 26 Feb 2008 04:54 by RE: LINQ to SQL and the "Request-scoped DataContext" Pattern
Hi Rupert, Sorry for the slow response--I got busy with other stuff. Yes, after thinking about this a bit, I agree that this is a bit of a showstopper if you need to use the DataLoadOptions feature. And I agree that trying to predict what DataLoadOptions you will need in advance is unworkable (and a bit silly). I've also been looking into the Entity Framework, and it doesn't seem to have this limitation. There, the Include() property can be put onto an object in the object query itself, and is therefore not part of the datacontext. See, for example, Julie Lerman's post here: http://www.thedatafarm.com/blog/2007/08/30/KnockingOffDannySimmonsEntityFrameworkBeta2List2Span.aspx. Hope this helps, Keith

Thursday, 28 Feb 2008 04:29 by RE: LINQ to SQL and the "Request-scoped DataContext" pattern
Hi Rupert, I just came across this on the MSDN code site: http://code.msdn.microsoft.com/multitierlinqtosql Here the author uses a similar architecture to the above, and suggests setting the DataLoadOptions in the OnInit event of the page for a traditional ASP.NET application. Basically each page could have it's own set of DataLoadOptions, and set them before any queries run. This is a clever idea to do this at the page level, and might be workable. Hope this helps! Keith

Sunday, 27 Apr 2008 06:57 by How to store DataContex
Dino Esposito writes some good stuf about DataContex and gives some clue have to store it in classes http://dotnetslackers.com/articles/csharp/InsideTheLINQToSQLDataContextClass.aspx#591 and http://weblogs.asp.net/despos/archive/2008/03/19/more-on-datacontext-in-hopefully-a-realistic-world.aspx

Tuesday, 10 Jun 2008 03:22 by Artcle on NTier app using LINQ
I have written an article related to this on <a href="http://www.codeproject.com/KB/dotnet/NorthwindNTierBlueprint.aspx"> codeproject </a> Please have a look and post your valuable comments / suggestions there.

Tuesday, 24 Jun 2008 12:34 by Philisophical disagreement
I understand what is trying to be achieved here and you are successful in making it work. From a design point of view however I would not do it as a matter of style. I'm constantly trying to create designs with less moving parts to reduce complexity. For me this solution doesn't simplify the code enough to justify adding it. It's just one more thing to maintain. Of course this is opinion - as I mentioned I think it will work - it just comes down the trade offs you prefer when designing. By the way here is a little more information on Disposing a DataContext: http://lee.hdgreetings.com/2008/06/linq-datacontex.html Regards, ecards coder

Name:
URL:
Email:
Comments:

CAPTCHA Image Validation