Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / database / SQL-Server

Asynchronous LINQ to SQL

0.00/5 (No votes)
16 Apr 2010CPOL7 min read 1   3  
How to make a responsive application by not having to wait for your LINQ to SQL to return from the server

Introduction

This is going to be part of a three part series on using Linq2Sql together with WPF.

While executing your SQL queries asynchronously may have benefits even in ASP, the main benefit that I was able to see was keeping a rich client (WPF) responsive (which is why Silverlight does not allow Linq2Sql or any other synchronous network access).

In the next two articles, I will introduce two more classes that build on this one to keep all of the functionality in the presenter layer.

  • SynchedCollection - A class that maintains its own CurrentItem
  • EntityCollection - A wrapper for the EntitySet to fix the fact that adding and deleting from an EntitySet won't show up in binding

Usage

In this article, I am introducing only one class - the AsynchronousLinq class. It can be used as is right out of the box and does not require any other classes. You can either copy the CS file right into your project or you can add a reference to the project or the DLL. Either way will work.

The class has two main properties - LinqList & ObservableList. While working with LINQ and WPF for the past few months, I realized that almost every time I wrote a LINQ statement or accessed related elements from an entity as in contact.Emails (or something like that), I was wrapping the statement in an ObservableCollection to assist in the DataBinding. However I also often wanted to be able to go back to the LINQ statement to be able to change the expressions or use it in other queries. So why not make a reusable way to maintain both the bindable collection and the original query.

So in your ViewModel, you create as many AsynchronousLinqs as you need and then you bind to the ObservableList of each one. Then when you want to fill the list, you set LinqList which in turn populates the ObservableList.

XML
<ListBox ItemsSource="{Binding Emails.ObservableList}">...</ListBox> 
C#
public AsynchronousLinq<ContactMethod> Emails  { get; set; }
C#
object SynchronizationObject = new object(); 
Emails = new AsynchronousLinq<ContactMethod> {Wait = SynchronizationObject };
C#
Emails.LinqList = from cm in contact.ContactMethods where cm.Type == 1 select cm

The Asynchronous Bit

Linq2Sql is a powerful technology. However when it boils down to it, it is passing SQL statements to your server and retrieving data. This retrieval can take a while depending on the latency of your network, the complexity of the query & the amount of data returned. While this is happening, your user interface can freeze up. This is because the main thread of your application is waiting for the data to return so it cannot process the users interactions (or anything else). However if you offloaded the data retrieval to another thread, and only when it was done did it contact the main thread and tell it to display that data. Then your main thread is free to interact with the user until the data is ready.

Now some people confuse asynchronous data loading with Linq2Sql's delayed execution. However the truth is that the delayed execution actually makes it harder to predict exactly when the data retrieval will happen. But when it does, it will halt your main thread just as much.

The delayed execution is accomplished through the IQueryable interface. As long as you are calling functions in the IQueryable interface, instead of getting the data you requested, you will be getting a new IQueryable with the necessary expressions to be able to tell the computer (in our case SQL Server) what data you want. It is only once you call a function from IEnumerable (such as .ToList() or wrapping the IQueryable in an ObservableCollection) that the expressions are translated into actual data.

If you rely on WPF binding to call one of those functions, then there is no way to know when the data retrieval will happen. However if you call them yourself in code, then you can make sure that the code is not held up by offloading it to a different thread.

Gotchas

There were a few things we needed to take care of first.

  • WPF can NOT be accessed by any thread other than the one it was initialized on.
  • Linq2Sql is inherently not Thread safe

To Explain these Ideas Further

I searched Google for asynchronous Linq and found many examples. NONE of them actually worked (at least not in WPF). Some used the Thread class, some used asynchronous delegates, but all of them had the same issues - the ones listed above.

The UI Thread

Any time you access any part of WPF, you must be in the UI Thread. Raising the PropertyChanged event in the INotifyPropertyChanged interface directly calls any WPF elements bound to that property and causes them to update. Doing this from another thread is a violation of the cross threading rules and will crash your program.

To get around this, I used a wonderful built in class call BackgroundWorker. It takes two delegates - one that executes on its own thread and the second one that gets called when the first one is done (unlike the asynchronous delegates ondone delegate) on the UI Thread.

So in the background thread, I wrap the IQueryable in an ObservableCollection (causing the data retrieval to happen) and store it in a temporary variable. And in the ondone delegate which is executing on the UI Thread, I assign the collection to ObservableList and raise the PropertyChanged event, thus updating the display.

Now to the next problem - Linq2Sql in not thread safe!

If you execute 2 Linq2Sql queries at the same time, you get loads of the strangest errors.

This could not be fixed by the BackgroundWorker because the entire point of this all was to keep the data retrieval off the main thread.

So what I needed to do was to make sure that any particular DataContext only had one query executing at any particular time. That does not mean that the program could not have other things happening - it could be updating the display, it could even be querying the same database using a different DataContext.

I could have done something with the DataContext however I did not want to tie this solution directly to a particular technology.

That is where the property Wait comes in. You can assign any object to the property Wait as long as every AsynchronousLinq containing a query to objects from the same DataContext has a reference to the same object. If your program topography is such that the AsynchronousLinqs are all accessible to an class that also has access to the DataContext, then by all means you can just use the DataContext as the Wait object. However if you do not have access to the DataContext, you can use any other object as long as it's always the same object.

What do I do with that object?

Every time I call a function on IEnumerable (like creating an ObservalbeCollection), I wrap it in a lock.

C#
  lock(Wait)
{
oc = new ObservableCollection(LinqList);
}

Now here's the catch - You must also only access the Datacontext within a lock!

That means that anytime you call a function that is going to be executing a SQL statement on the same DataContext that has an AsynchronousLinq, you must wrap that code in a lock with the same object that you assigned to the Wait property.

On the other hand, remember also to NEVER wrap a call to AsynchronousLinq.LinqList even indirectly in a call to lock with the same object that you assigned to Wait. Because if you do, you will cause a deadlock because LinqList will try to wait until you release your lock which will never happen because the call will never return because it is waiting.

Ok, I think this article has gotten long enough. It is much longer than I expected. I am much better at writing code than I am at writing articles, so just read the code and you will probably learn more from that than you will from the article.

Improvements

This class can still use some more improvements:

  • Writing a better help article, I realize that this article isn't that great and that the code deserves to be better explained
  • Add a demo project. To demonstrate using this class
  • Add paged data retrieval. This would be useful for large sets of data. It could cycle through the LinqList using .Take(pageSize)

History

  • 16th April, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)