Async Query and Batch with Entity Framework

Ancient Knowledge

This article is getting old. It was written in the ancient times and the world of software development has changed a lot since then. I'm keeping it here for historical purposes, but I recommend you check out the newer articles on my site.

When you're working with WPF or Windows forms one of the common issues that crops up is cross threading in your viewmodels. The following is a simple solution that wraps up a BackgroundWorker in some standalone classes that can be used with your DbContext. The nice thing about this solution is it doesn't require you to inherit any classes and you can stick the code in yuo project where you feel it works best.

Asynchronous Queries

Here's how we might use async queries to tidy up our viewmodels a bit and still retain that synchronization context from the Dispatcher so we don't end up with annoying cross thread issues when updating properties on our viewmodels.

Usage

AsyncQuery<DataContext>.List<Company>(results =>
{
    this.Companies = new ObservableCollection<Company>(results);
});

AsyncQuery requires the DbContext as a type, and the type of model to supply to the static method you want to call. In this case, we want a list of all companies in our database, our callback transforms this to the often needed ObservableCollection in WPF. Of course, you can do whatever you want with it. AsyncQuery will dispose of your context, so be careful when using lazy loaded properties. You'll want to use the overloads for eager loading if that's the case.

Code

public static class AsyncQuery<T> where T : DbContext, new()
{
   /// <summary>
   /// Find an entity by type and id asynchronously
   /// </summary>
   public static void Find<TModel>(object[] keyValues, Action<TModel> callback, Action<Exception> exceptionCallback = null) where TModel : class
   {
       ExecuteAsync(() =>
       {
           using (var context = new T())
           {
               return context.Set<TModel>().Find(keyValues);
           }
       }, callback, exceptionCallback);
   }

   /// <summary>
   /// Gets all entities of a given type asynchronously
   /// </summary>
   public static void List<TModel>(Action<IList<TModel>> callback, Action<Exception> exceptionCallback = null) where TModel : class
   {
       ExecuteAsync(() =>
       {
           using (var context = new T())
           {
               return context.Set<TModel>().ToList();
           }
       }, callback, exceptionCallback);
   }

   /// <summary>
   /// Gets all entities of a given type asynchronously
   /// </summary>
   public static void List<TModel, TProperty>(Expression<Func<TModel, TProperty>> includePath, Action<IList<TModel>> callback, Action<Exception> exceptionCallback = null) where TModel : class
   {
       ExecuteAsync(() =>
       {
           using (var context = new T())
           {
               return context.Set<TModel>().Include(includePath).ToList();
           }
       }, callback, exceptionCallback);
   }

   /// <summary>
   /// Get a list of filtered entities asynchronously
   /// </summary>
   public static void Where<TModel>(Func<TModel, bool> predicate, Action<IEnumerable<TModel>> callback, Action<Exception> exceptionCallback = null) where TModel : class
   {
       ExecuteAsync(() =>
       {
           using (var context = new T())
           {
               return context.Set<TModel>().Where<TModel>(predicate);
           }
       }, callback, exceptionCallback);
   }

   /// <summary>
   /// Get a list of filtered entities asynchronously
   /// </summary>
   public static void Where<TModel, TProperty>(Func<TModel, bool> predicate, Expression<Func<TModel, TProperty>> includePath, Action<IEnumerable<TModel>> callback, Action<Exception> exceptionCallback = null) where TModel : class
   {
       ExecuteAsync(() =>
       {
           using (var context = new T())
           {
               return context.Set<TModel>().Include(includePath).Where<TModel>(predicate);
           }
       }, callback, exceptionCallback);
   }

   /// <summary>
   /// Execute a background task that performs its callback on the calling thread to avoid invoke.
   /// Requires the calling thread to have a synchronization context such as a WPF application.
   /// </summary>
   /// <typeparam name="TModel">The type of the result</typeparam>
   /// <param name="task">The method to execute async</param>
   /// <param name="callback">The callback</param>
   private static void ExecuteAsync<TModel>(Func<TModel> task, Action<TModel> callback, Action<Exception> exceptionCallback = null)
   {
       var worker = new BackgroundWorker();
       worker.DoWork += (s, e) =>
       {
           e.Result = task();
       };
       worker.RunWorkerCompleted += (s, e) =>
       {
           if (e.Error == null && callback != null)
               callback((TModel)e.Result);
           else if (e.Error != null && exceptionCallback != null)
               exceptionCallback(e.Error);
       };
       worker.RunWorkerAsync();
   }
}

Asynchronous batches

Sometimes you really need to batch several updates together without having to chain the actions together.

Usage

var batch = AsyncBatch<DataContext>.Create();
batch.Queue(c => c.Companies.Add(new Company() { Name = "Bobs Burgers" }));
batch.Queue(c => c.Companies.Add(new Company() { Name = "Taco Johns" }));
batch.Queue(c => c.Companies.Add(new Company() { Name = "Wendys" }));
batch.Queue(c => c.Companies.Add(new Company() { Name = "Super Burger" }));
batch.Queue(c => c.Companies.Add(new Company() { Name = "Nacho Mamas" }));
batch.Queue(c => c.SaveChanges());

batch.ExecuteAsync(() =>
{
    using (var context = new DataContext())
    {
        this.Companies = context.Companies.ToList();
    }
});

The AsyncBatch class will create the instance of your DbContext for you, and automatically dispose of it after the batch completes. You can queue up any actions you need, then call ExecuteAsync(Action) to handle the completion. Since this executes using a BackgroundWorker, it saves you from having to call Dispatcher.Invoke when you update view model properties. Since the batch won't return any values for you, this is really only useful when doing batch updates, inserts, or deletes.

Code

public class AsyncBatch<T> where T : DbContext, new()
{
    /// <summary>
    /// Creates a new instance of <see cref="AsyncBatch"/> with the given DbContext
    /// </summary>
    public static AsyncBatch<T> Create()
    {
        var unitOfWork = new AsyncBatch<T>();
        unitOfWork.context = new T();
        unitOfWork.actions = new List<Expression<Action<T>>>();
        return unitOfWork;
    }

    /// <summary>
    /// Execute all actions that have been queued asynchronously
    /// </summary>
    /// <param name="callback">The method to execute when all actions have been completed</param>
    /// <param name="exceptionCallback">The method to exeute if there was an unhandled exception</param>
    public void ExecuteAsync(Action callback, Action<Exception> exceptionCallback = null)
    {
        var worker = new BackgroundWorker();
        worker.DoWork += (s, e) =>
        {
            foreach (var action in this.actions)
                action.Compile()(this.context);
        };
        worker.RunWorkerCompleted += (s, e) =>
        {
            context.Dispose();

            if (e.Error == null && callback != null)
                callback();
            else if (e.Error != null && exceptionCallback != null)
                exceptionCallback(e.Error);
        };
        worker.RunWorkerAsync();
    }

    /// <summary>
    /// Queue an action to be executed asynchronously
    /// </summary>
    /// <param name="action">The action</param>
    public AsyncBatch<T> Queue(Expression<Action<T>> action)
    {
        actions.Add(action);
        return this;
    }

    private List<Expression<Action<T>>> actions;
    private T context;
}