Wednesday, November 6, 2013

Entity Framework 6, ToListAsync and PredicateBuilder

For a new personal project which uses Entity Framework 6, I needed a powerful way to play with a lot of criteria in a search request.

The query has to been done on a TPC Code First model: all the classes inherit from a common base class with other abstract classes and interfaces for some of them, etc. In conclusion, the model is pretty complex and the query possibilities are wide!

Thanks to AutoMapper, the usage of this model from the front-end to the back-end is really easy but I encounter a problem with the way to manage my criteria logic: how to correctly create some Expression<Func<TEntity, bool>> for the .Where(...) method?

For that kind of needs, PredicateBuilder is a very good helper! You just need to create a basic "true" predicate like that:

   1:  var where = PredicateBuilder.True<TEntity>();

And you add your conditions according to your logic with that kind of code:

   1:  if (condition)
   2:  {
   3:      where = where.Add(t => t.Something > 0);
   4:  }

When all your criteria have been added (OfType is used because it's a TPC model):

   1:  context.Entities.OfType<TEntity>().AsExpandable().Where(where).ToList();

AsExpandable() is a method from LinqKit that you have with the PredicateBuilder and which is necessary.

The problem that I have encountered  I am using Entity Framework 6 and I want to use the async/await capacities of this new version. So all my code in my repositories are using the new xxxAsync methods of Entity Framework. But if you use the actual version of LinqKit, and you call the AsExpandable method before the call to ToListAsync(), you will have this exception:
"System.InvalidOperationException : The source IQueryable doesn't implement IDbAsyncEnumerable..."
The source code of LinqKit is open an available here. For my project, I downloaded the source code, included the project in my solution and I have done the following modifications:
   1:  public class ExpandableQuery<T> : IQueryable<T>, IOrderedQueryable<T>, IOrderedQueryable, IDbAsyncEnumerable<T>
   2:  {
   3:      ExpandableQueryProvider<T> _provider;
   4:      IQueryable<T> _inner;
   5:   
   6:      internal IQueryable<T> InnerQuery { get { return _inner; } }
   7:   
   8:      internal ExpandableQuery (IQueryable<T> inner)
   9:      {
  10:          _inner = inner;
  11:          _provider = new ExpandableQueryProvider<T> (this);
  12:      }
  13:   
  14:      Expression IQueryable.Expression { get { return _inner.Expression; } }
  15:      Type IQueryable.ElementType { get { return typeof (T); } }
  16:      IQueryProvider IQueryable.Provider { get { return _provider; } }
  17:      public IEnumerator<T> GetEnumerator () { return _inner.GetEnumerator (); }
  18:      IEnumerator IEnumerable.GetEnumerator () { return _inner.GetEnumerator (); }
  19:      public override string ToString() { return _inner.ToString(); }
  20:      
  21:      public IDbAsyncEnumerator<T> GetAsyncEnumerator()
  22:      {
  23:          return new ExpandableDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
  24:      }
  25:      
  26:      IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
  27:      {
  28:          return GetAsyncEnumerator();
  29:      }
  30:  }

And I have added this new class:

   1:  public class ExpandableDbAsyncEnumerator<T> : IDbAsyncEnumerator<T>
   2:  {
   3:      private readonly IEnumerator<T> _inner;
   4:      public ExpandableDbAsyncEnumerator(IEnumerator<T> inner)
   5:      {
   6:          _inner = inner;
   7:      }
   8:      public void Dispose()
   9:      {
  10:          _inner.Dispose();
  11:      }
  12:      public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
  13:      {
  14:          return Task.FromResult(_inner.MoveNext());
  15:      }
  16:      public T Current
  17:      {
  18:          get { return _inner.Current; }
  19:      }
  20:      object IDbAsyncEnumerator.Current
  21:      {
  22:          get { return Current; }
  23:      }
  24:  }

Thanks to this change, you can use the excellent PredicateBuilder and you can still use the ToListAsync() methods of the new version of Entity Framework!

I hope it will help someone!

6 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. With EF 7/EF Core 1 I tried this and hit the same problem. But when I removed the .AsExpandable() all worked fine - as far as I can see.

    I am correct that everything is working fine?

    ReplyDelete
  3. As of now, you can just download the LinqKit.EntityFramework on Nuget (and also remove your current LinqKit)

    ReplyDelete

Note: Only a member of this blog may post a comment.