Blog Home  Home Feed your aggregator (RSS 2.0)  
Eric Malamisura's Blog - LINQ
Geek Ramblings
 
 Thursday, April 24, 2008

So I have been working on a project where we are taking the existing data access code which is in pretty bad shape and we are trying to redesign and redo most of it using LINQ to SQL.  So after some research and reading from Scott Gu, LINQ in Action, and MSDN White Paper about how to use LINQ to SQL I noticed most of the examples they provide are overly immature.  They play like everything is great, and just drop it all in your code behind, or have a single layer of responsibility and it will all just work like magic!!  This sounded to good to be true, and after my experience it really is too good to be true!

 

So in this series of articles I am going to go through all of the things that we encountered.  The first thing you will run into with LINQ to SQL is that the DataContext is stateful and this is particularly concerning if you are planning to use it in a stateless environment like ASP.NET.  You can download the source code to the Northwind sample project that follows the design that is being discussed in this series here.

 

Below is a list of the issues we encountered while using LINQ to SQL and how we dealt with these issues.

  • DataContext Lifetime Management (Part 1)
  • Entity Persistence (Part 2)
  • Data Concurrency (Part 3)
  • N-Tier Design (Part 4)

DataContext Lifetime Management

So how do you manage the lifecycle of the DataContext?  There is a pretty good article (LINQ to SQL DataContext Lifetime Management) about this on Rick Strahl's blog and I won't cover what he already has in his article, I would recommend going to go read it and take from what you want.  I borrowed multiple concepts from his article and adapted them to my needs.

 

The solution we chose was keep the HttpContext around per a HTTP request, this was achieved by writing an HttpModule and creating an instance of this DataContext and storing in the HttpContext.Current.Items collection.  Now using this method you will have access to the same DataContext per a page request essentially, so you can do all of your work against a single DataContext and then submit it.

 

Below is the code that was used for storing the DataContext in a web application:

 

   10 public class WebContextStorage : IContextStorage

   11     {

   12         public WebContextStorage()

   13         {

   14 

   15         }

   16 

   17         #region IContextStorage Members

   18 

   19         public T GetItemContext<T>(string key)

   20         {

   21             Dictionary<string, object> storage = GetDictionaryFromStorage();

   22             return (storage == null) ? default(T) : (storage.ContainsKey(key) ? (T)storage[key] : default(T));

   23         }

   24 

   25         public void SetItemContext<T>(string key, T item)

   26         {

   27             Dictionary<string, object> storage = GetDictionaryFromStorage();

   28 

   29             if (storage == null)

   30             {

   31                 storage = new Dictionary<string, object>();

   32                 HttpContext.Current.Items.Add("storage", storage);

   33             }

   34 

   35             storage[key] = item;

   36         }

   37 

   38         private static Dictionary<string, object> GetDictionaryFromStorage()

   39         {

   40            return (Dictionary<string, object>)HttpContext.Current.Items["storage"];

   41         }

   42 

   43         #endregion

   44     }

 

There are other issues you need to deal with however, in our case we have Windows Services that need to be able to access our data to run various jobs, etc.  But no HttpContext exists in a WindowsService, so if you read Rick Strahl's blog you will already realize the solution to this is to use ThreadLocalStorage, we took a similar approach and used a Dictionary decorated with the [ThreadStatic] attribute, which was easier and we might be loosing some of the benefits of using ThreadLocalStorage but for now this is working just fine for us.

 

Below is the code that was used to store the DataContext in a non-web application:

 

    9 public class ThreadContextStorage : IContextStorage

   10     {

   11         [ThreadStatic]

   12         private static readonly Dictionary<string, object> _storage = new Dictionary<string, object>();

   13 

   14         public ThreadContextStorage()

   15         {

   16 

   17         }

   18 

   19         #region IContextStorage Members

   20 

   21         public T GetItemContext<T>(string key)

   22         {

   23             return (_storage.ContainsKey(key) ? (T)_storage[key] : default(T));

   24         }

   25 

   26         public void SetItemContext<T>(string key, T item)

   27         {

   28             _storage[key] = item;

   29         }

   30 

   31         #endregion

   32     }

 

So first off, we have two DataContext Lifetime Management strategies above, so we built a DataContextManager that follows the Strategy Design Pattern.  Now I don't like to get hung up on strict design pattern techniques, I always take the concept and adapt it to what works best for my situation, but I do want to point it out as a concept we used.  So we ended up writing something called the DataContextManager, the name is not really important but what it does is so below is the code:

 

   13  public class DataContextManager

   14     {

   15         private readonly IContextStorage _contextStorage;

   16 

   17         public DataContextManager(IContextStorage contextStorage)

   18         {

   19             if (contextStorage == null)

   20             {

   21                 throw new ArgumentNullException("contextStorage");

   22             }

   23 

   24             _contextStorage = contextStorage;

   25         }

   26 

   27         public T GetDataContext<T>() where T : DataContext, new()

   28         {

   29             T dataContext = _contextStorage.GetItemContext<T>(typeof(T).FullName);

   30             if (dataContext == null)

   31             {

   32                 dataContext = new T();

   33                 dataContext.Log = new DebuggerWriter();

   34                 _contextStorage.SetItemContext<T>(typeof(T).FullName, dataContext);

   35             }

   36 

   37             return dataContext;

   38         }

   39 

   40         public void Submit<T>() where T : DataContext

   41         {

   42             T dataContext = _contextStorage.GetItemContext<T>(typeof(T).FullName);

   43 

   44             if (dataContext != null)

   45             {

   46                 dataContext.SubmitChanges();

   47             }

   48         }

   49     }

 

So as you can see it is very simple, you could also add a new GetDataContextManager to pass in a connection string if needed but in our case we figured no one should need this ability since we have all of our connection strings in the configuration file we can just configured those using the LINQ to SQL designer.  If this becomes an issue we may add this ability.

 

The DataContextManager instance is stored in a ServiceLookup class we have that follows the popular design pattern by Martin Fowler called the Registry.  Essentially all it does is stores instances of all the service objects you will be using throughout your application, in our case this is built in a module on the Init so it is built once per AppDomain creation and remains static throughout.  If you remember from the lifecycle process of ASP.NET, first time a user visits the application it loads the AppDomain, when the HttpApplication is created it loads the HttpModules, and HttpHandlers, you can read about this in great detail in one of Rick Strahl's articles.

 

Below is where we define our implementation of the ServiceLookup class:

 

    8 public static class ServiceLocator

    9     {

   10         private static readonly Dictionary<string, object> _map = new Dictionary<string, object>();

   11 

   12         public static void Load<T>(T service)

   13         {

   14             Load(typeof(T).FullName, service);

   15         }

   16 

   17         public static void Load<T>(string key, T service)

   18         {

   19             _map.Add(key, service);

   20         }

   21 

   22         public static T Get<T>()

   23         {

   24             return Get<T>(typeof(T).FullName);

   25         }

   26 

   27         public static T Get<T>(string key)

   28         {

   29             return (T)_map[key];

   30         }

   31     }

 

As discussed above we want all of the objects in our ServiceLocator to be static, and maintain their lifetime throughout the lifetime of the AppDomain, this way we do not have reconstruct them multiple times.  To do this we use a HttpModule as seen below:

 

   16 public class ServiceBuilderModule : IHttpModule

   17     {

   18         public void Init(HttpApplication context)

   19         {

   20             ServiceBuilder.BuildServiceDependencies();

   21         }

   22 

   23         public void Dispose() { }

   24     }

 

The ServiceBuilder is a helper static class that simply does all the work so we can keep all of the code out of the module itself:

 

   12 public static class ServiceBuilder

   13     {

   14         public static void BuildServiceDependencies()

   15         {

   16             DataContextManager dataContextManager = new DataContextManager(new WebContextStorage());

   17             ServiceLocator.Load<DataContextManager>(dataContextManager);

   18             ServiceLocator.Load<CustomerDao>(new CustomerDao(dataContextManager));

   19         }

   20     }

 

We will cover in a later topic how the CustomerDao is used, etc. But for now the DataContextManager is what to really pay attention to since it constructs/retrieves your DataContext.  Next in order to get the DataContext all you have to do is request it from the ServiceLocator for example:

   24 public AdventureWorksLiteDataContext DataContext

   25         {

   26             get

   27             {

   28                 return _dataContextManager.GetDataContext<AdventureWorksLiteDataContext>();

   29             }

   30         }

 

So that is all for Part 1 in this series, next we will cover EntityPersistence and how we solved the problem of maintaining the state of a LINQ Entity across page posts.  If you have any questions do not hesitate to email me or preferably to comment below!

Thursday, April 24, 2008 6:04:08 PM (GMT Standard Time, UTC+00:00)  #    Comments [2]    |   | 
Copyright © 2008 Eric Malamisura. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.