Sadly, upgrading projects is a nasty necessity. In the world of
technology, everything moves so fast that when you finish a project
using shiny, new technologies, it is already old and out-dated. I had
this experience when I wanted to upgrade a project to use Entity
Framework 6 and to implement the shiny async parts.
The issue I had was that there was some very heavy abstractions over the
Entity Framework classes to aid in unit testing. I am using the adapter
pattern to wrap a DbSet<T>, as the code only knew about
IDbSet<T>, I didn’t have access to all the async goodness and
when I tried to use it, I got the following exception:
The source IQueryable doesn't implement IDbAsyncEnumerable{0}. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations.
The issue here is that these Async methods are designed to work with
Entity Framework, not just any old IQueryable.
But wait. You said you are using Entity Framework?
Yes, I am using Entity Framework (from here on referenced as EF), but I
have created abstractions which no longer contain the relevant
interfaces to allow this to work.
Lets see some code.
Here is what I had in the original application. As you can see it is
heavily abstracted.
/// <summary>
/// Interface for a dbset wrapper.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public interface IDbSetWrapper<TEntity> : IDbSet<TEntity>
where TEntity : class
{
#region Public Methods and Operators
/// <summary>
/// Includes the specified expression.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="path">The include expression.</param>
/// <returns>
/// <see cref="IQueryable{TEntity}" />
/// </returns>
IQueryable<TEntity> Include<TProperty>(Expression<Func<TEntity, TProperty>> path);
#endregion
}
/// <summary>
/// Wrapper class for a <see cref="IDbSet{TEntity}" /> to aid with unit testing.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public class DbSetWrapper<TEntity> : IDbSetWrapper<TEntity>
where TEntity : class
{
#region Fields
/// <summary>
/// The internal set reference
/// </summary>
private readonly IDbSet<TEntity> _set;
#endregion
#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the <see cref="DbSetWrapper{TEntity}" /> class.
/// </summary>
/// <param name="set">The set.</param>
public DbSetWrapper(IDbSet<TEntity> set)
{
this._set = set;
this.Expression = set.Expression;
this.ElementType = set.ElementType;
this.Provider = set.Provider;
this.Local = set.Local;
}
#endregion
#region Public Properties
/// <summary>
/// Gets the type of the element(s) that are returned when the expression tree associated with this instance of
/// <see cref="T:System.Linq.IQueryable" /> is executed.
/// </summary>
/// <returns>
/// A <see cref="T:System.Type" /> that represents the type of the element(s) that are returned when the
/// expression tree associated with this object is executed.
/// </returns>
public Type ElementType { get; private set; }
/// <summary>
/// Gets the expression tree that is associated with the instance of <see cref="T:System.Linq.IQueryable" />.
/// </summary>
/// <returns>
/// The <see cref="T:System.Linq.Expressions.Expression" /> that is associated with this instance of
/// <see cref="T:System.Linq.IQueryable" />.
/// </returns>
public Expression Expression { get; private set; }
/// <summary>
/// Gets the local collection.
/// </summary>
/// <value>
/// The local collection.
/// </value>
public ObservableCollection<TEntity> Local { get; private set; }
/// <summary>
/// Gets the query provider that is associated with this data source.
/// </summary>
/// <returns>The <see cref="T:System.Linq.IQueryProvider" /> that is associated with this data source.</returns>
public IQueryProvider Provider { get; private set; }
#endregion
#region Public Methods and Operators
/// <summary>
/// Adds the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
public TEntity Add(TEntity entity)
{
return this._set.Add(entity);
}
/// <summary>
/// Attaches the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
public TEntity Attach(TEntity entity)
{
return this._set.Attach(entity);
}
/// <summary>
/// Creates a new instance.
/// </summary>
/// <returns></returns>
public TEntity Create()
{
return this._set.Create();
}
/// <summary>
/// Creates a new instance.
/// </summary>
/// <typeparam name="TDerivedEntity">The type of the derived entity.</typeparam>
/// <returns></returns>
public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, TEntity
{
return this._set.Create<TDerivedEntity>();
}
/// <summary>
/// Finds the specified key values.
/// </summary>
/// <param name="keyValues">The key values.</param>
/// <returns></returns>
public TEntity Find(params object[] keyValues)
{
return this._set.Find(keyValues);
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection.
/// </returns>
public IEnumerator<TEntity> GetEnumerator()
{
return ((IEnumerable<TEntity>)this._set).GetEnumerator();
}
/// <summary>
/// Includes the specified expression.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="path">The include expression.</param>
/// <returns>
/// <see cref="IQueryable{TEntity}" />
/// </returns>
[ExcludeFromCodeCoverage]
public IQueryable<TEntity> Include<TProperty>(Expression<Func<TEntity, TProperty>> path)
{
return this._set.Include(path);
}
/// <summary>
/// Removes the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
public TEntity Remove(TEntity entity)
{
return this._set.Remove(entity);
}
#endregion
#region Explicit Interface Methods
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator" /> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
#endregion
}
With the setup above, anywhere you use the IDbSetWrapper (in my case
a repository) and try to call any of the new Async linq methods, you
will get the original exception. I solved this issue by making a few
minor adjustments to the interface and class above.
/// <summary>
/// Interface for a dbset wrapper.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public interface IDbSetWrapper<TEntity> : IDbSet<TEntity>, IDbAsyncEnumerable<TEntity>
where TEntity : class
{
#region Public Methods and Operators
/// <summary>
/// Includes the specified expression.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="path">The include expression.</param>
/// <returns>
/// <see cref="IQueryable{TEntity}" />
/// </returns>
IQueryable<TEntity> Include<TProperty>(Expression<Func<TEntity, TProperty>> path);
/// <summary>
/// Finds items by key values asynchronously
/// </summary>
/// <param name="keyValues">The key values</param>
/// <returns></returns>
Task<TEntity> FindAsync(params Object[] keyValues);
#endregion
}
The main change here is to inherit from another interface
IDbAsyncEnumerable<T>. This is what the framework is looking
for to see if the async code is supprted.
I also added a FindAsync method, this is exposed on a normal DbSet,
but it not in an IDbSet, so using the adapter pattern, I have to expose
it myself.
The new wrapper class now looks like this.
/// <summary>
/// Wrapper class for a <see cref="IDbSet{TEntity}" /> to aid with unit testing.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public class DbSetWrapper<TEntity> : IDbSetWrapper<TEntity>
where TEntity : class
{
... same code as before removed for brevity ...
/// <summary>
/// Finds the specified key values asynchronously.
/// </summary>
/// <param name="keyValues">The key values</param>
/// <returns></returns>
public async Task<TEntity> FindAsync(params object[] keyValues)
{
//Todo: Review the casting to DbSet
return await ((DbSet<TEntity>)this._set).FindAsync(keyValues);
}
/// <summary>
/// Gets the async enumerator.
/// As we are dealing with an IDbSet rather than a DbSet, we need to cast it and then return the enumerator from DbQuery.
/// </summary>
/// <returns></returns>
public IDbAsyncEnumerator<TEntity> GetAsyncEnumerator()
{
return ((IDbAsyncEnumerable<TEntity>)this._set).GetAsyncEnumerator();
}
/// <summary>
/// Gets the async enumerator
/// </summary>
/// <returns></returns>
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return this.GetAsyncEnumerator();
}
}
For brevity, I have only enclosed the new methods. This now gets the
Async methods to work and I now have access to the Async goodness in
my queries.
I don’t like the amount of casting that is going on, but sadly, using
this pattern, I am struggling to see a better way to do it.
You should now be set to update your repositories/rest of your code base
to start supporting the Async linq methods.
Any comments or questions would be well received.