Asynchronous DataContext

Triggered by a blog post of Scott Hanselman I was wondering if I could create a generic extension method to asynchronously retrieve objects from a DataContext. And well I could. I ended up with two classes.

The ExecuteAsyncState:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Data.Linq;
   4:  using System.Data.SqlClient;
   5:  using System.Threading;
   6:   
   7:  namespace AsyncDataAccess {
   8:      public class ExecuteAsyncState<T> {
   9:          public ExecuteAsyncState(DataContext dataContext, SqlCommand sqlCommand, Action<IEnumerable<T>> onReady) {
  10:              this.DataContext = dataContext;
  11:              this.SqlCommand = sqlCommand;
  12:              this.OnReady = onReady;
  13:              this.WaitHandle = new AutoResetEvent(false);
  14:          }
  15:   
  16:          public DataContext DataContext { get; private set; }
  17:          public SqlCommand SqlCommand { get; private set; }
  18:          public Action<IEnumerable<T>> OnReady { get; private set; }
  19:          public AutoResetEvent WaitHandle { get; private set; }
  20:      }
  21:  }

And the DataContextUtility:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Data.Linq;
   4:  using System.Data.SqlClient;
   5:  using System.Linq;
   6:   
   7:  namespace AsyncDataAccess {
   8:      public static class DataContextUtility {
   9:          public static IAsyncResult ExecuteAsync<T>(this DataContext dataContext, IQueryable<T> query, Action<IEnumerable<T>> onReady) {
  10:              SqlCommand sqlCommand = dataContext.GetCommand(query) as SqlCommand;
  11:   
  12:              ExecuteAsyncState<T> asynchState = new ExecuteAsyncState<T>(dataContext, sqlCommand, onReady);
  13:   
  14:              AsyncCallback callback = new AsyncCallback(EndExecuteAsync<T>);
  15:              return sqlCommand.BeginExecuteReader(callback, asynchState);
  16:          }
  17:   
  18:          private static void EndExecuteAsync<T>(IAsyncResult result) {
  19:              ExecuteAsyncState<T> asynchState = result.AsyncState as ExecuteAsyncState<T>;
  20:   
  21:              DataContext dataContext = asynchState.DataContext;
  22:              SqlCommand sqlCommand = asynchState.SqlCommand;
  23:              Action<IEnumerable<T>> onReady = asynchState.OnReady;
  24:   
  25:              SqlDataReader reader = sqlCommand.EndExecuteReader(result);
  26:              var resultData = from item in dataContext.Translate<T>(reader)
  27:                               select item;
  28:   
  29:              try {
  30:                  onReady.Invoke(resultData);
  31:              }
  32:              finally {
  33:                  reader.Close();
  34:                  asynchState.WaitHandle.Set();
  35:              }
  36:          }
  37:      }
  38:  }

These two classes enable you to retrieve your object asynchronously. Like so:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Threading;
   6:   
   7:  namespace AsyncDataAccess {
   8:      class Program {
   9:          static void Main(string[] args) {
  10:              using (SampleDbContextDataContext context = new SampleDbContextDataContext()) {
  11:                  context.Connection.Open();
  12:                  
  13:                  var customerQuery = from Customer c in context.Customers
  14:                                      select c;
  15:   
  16:                  IAsyncResult customerResult =
  17:                      context.ExecuteAsync<Customer>(customerQuery, (customers) => {
  18:                                                                         foreach (var c in customers) {
  19:                                                                             Console.WriteLine(c.ToString());
  20:                                                                         }
  21:                                                                     });
  22:   
  23:                  var productQuery = from Product p in context.Products
  24:                                     select p;
  25:   
  26:                  IAsyncResult productResult =
  27:                      context.ExecuteAsync<Product>(productQuery, (products) => {
  28:                                                                       foreach (var p in products) {
  29:                                                                           Console.WriteLine(p.ToString());
  30:                                                                       }
  31:                                                                   });
  32:   
  33:                  Console.WriteLine("Before the queries are returned:");
  34:   
  35:                  ExecuteAsyncState<Customer> customerState = customerResult.AsyncState as ExecuteAsyncState<Customer>;
  36:                  ExecuteAsyncState<Product> productState = productResult.AsyncState as ExecuteAsyncState<Product>;
  37:   
  38:                  WaitHandle[] waitHandles = new[] { customerState.WaitHandle, productState.WaitHandle };
  39:                  WaitHandle.WaitAll(waitHandles);
  40:   
  41:                  Console.WriteLine("After the queries are returned:");
  42:   
  43:                  Console.ReadLine();
  44:              }
  45:   
  46:          }
  47:      }
  48:  }

And that results in a screen like this:

Where you’ll see it prints my name first, then a product name, then the rest of the customer names to finish of with the rest of the product names.

Conclusion

It was very easy to get this result and you could of course add some more methods that simply return a single object instead of an IEnumerable<T>.

Cheers and have fun,

Wesley

1 Comment

  • This won't work well for anonymous types composed of multiple complex entities. The Translate() method does simple column-to-property mapping, not supporting complex types that have non-unique property names across the set of nested types. You'd have to flatten out your anonymous type to just a set of simple-type properties and use your own naming convention to pull similarly-named columns from separate tables.

Comments have been disabled for this content.