Before picking up where I left off yesterday, I have to first make a small disclaimer. I received a couple of comments stating that the caching solution I presented wasn’t that sophisticated or complete or that the cache keys that I’ve used were not the right and the answer is of course “yes”. The solution I presented was far from perfect but it served well as a simplified demonstration of the problems one might face when caching Linq to SQL entities.
Ok now that we’re done with the typicalities let’s see what was wrong with the solution shown earlier. To demonstrate the problem I’m going to add a single button to my web form and attach an event handler to it. In the event handler I’m going to ask the Person from the Cache and Lazy load his phones.
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
DataBind();
}
}
public override void DataBind()
{
base.DataBind();
employeesRepeater.DataSource = new List<Person>() { GetPerson("M") };
employeesRepeater.DataBind();
}
protected void btnGetPhone_Click(object sender, EventArgs e)
{
Person person = GetPerson("M"); //This person comes from the cache
var phones = person.PersonPhones.ToList(); //Attempt to lazy load throws exception as Context has been disposed
}
protected Person GetPerson(string lastNameStartsWith)
{
using (AdventureworksDataContext context = new AdventureworksDataContext())
{
return CacheGet<Person>(
() => context.Persons.Where(p => p.LastName.StartsWith(lastNameStartsWith)).FirstOrDefault(),
string.Format("Person_LastName_StartsWith:{0}", lastNameStartsWith));
}
}
protected T CacheGet<<T>(Func<T> loader, string cacheKey) where T : class
{
var cachedObject = this.Cache[cacheKey] as T;
if (cachedObject == null)
{
cachedObject = loader.Invoke();
}
return cachedObject;
}
}
If you ran this code and click the button you’ll end up with the exception
Cannot access a disposed object.
Object name: 'DataContext accessed after Dispose.'.
That’s the real problem when caching Linq to SQL entities, the fact that they have a dependency (reference) on the data context that created them and as such they can not be cached. There are ways you can work-around this problem by detaching and re-attaching the entities to the currently loaded data context. But since there is no way you can actually detach an entity from the context you can only rely on hacks to do that. A way to detach an entity is serialize it (of course entities must be declared serializable) cache it and then before retrieving it from the cache deserialize it and attach it to the context. Another is to manually detach an entity (set certain properties to null) and another to manually clone it. Each of these methods has advantages and disadvantages, like the fact that you’ll loose all object’s graph if you serialize it or the complexity if you follow the manual detaching path. One thing is for sure though, there is no easy way around it.
What’s my preffered way you ask? I guess the most painful way (;-)) but at the same time the most scalable and robust. Build my own custom POCO entities model, populate it using whatever data access technology and cache those entities.