Lets say I have a simple repository class, with one GetByNames method
public class MyRepo
{
private readonly MyDbContext _db;
public MyRepo(MyDbContext db)
{
_db = db;
}
public IQueryable<MyObject> GetByNames(IList<string> names)
{
if (names== null || !names.Any())
{
return Enumerable.Empty<MyObject>().AsQueryable();
}
return _db.MyObjects.Where(a => names.Contains(a.Name));
}
}
Now when I use it with async EntityFramework ToListAsync() extension
var myObjects = awawit new MyRepo(_db).GetByNames(names).ToListAsync();
It will blow up if I pass in empty list or null because Enumerable.Empty<MyObject>().AsQueryable() does not implement IDbAsyncEnumerable<MyObject> interface.
The source IQueryable doesn't implement IDbAsyncEnumerable. Only sources that implement IDbAsyncEnumerable can be used for Entity Framework asynchronous operations. For more details see http://go.microsoft.com/fwlink/?LinkId=287068.
So my question is, how can I return an empty IQueryable<> that implements IDbAsyncEnumerable, without hitting the database?
If you don't want to hit the DB, you'll most likely have to provide your own implementation of empty IQuerable that implements IDbAsyncEnumerable. But I don't think it is too hard. In all the enumerators just return null for Current and false for MoveNext. In Dispose just do nothing. Try it. Enumerable.Empty<MyObject>().AsQueryable() has nothing to do with database, it definitely does not implement IDbAsyncEnumerable. You need an implementation that does, according to this.
I ended up implementing an extension method that returns wrapper which implements IDbAsyncEnumerable. It is based on this boilerplate implementation for mocking async code.
With this extension method I can use
return Enumerable.Empty<MyObject>().AsAsyncQueryable();
which works great.
Implementation:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
namespace MyProject.MyDatabase.Extensions
{
public static class EnumerableExtensions
{
public static IQueryable<T> AsAsyncQueryable<T>(this IEnumerable<T> source)
{
return new AsyncQueryableWrapper<T>(source);
}
public static IQueryable<T> AsAsyncQueryable<T>(this IQueryable<T> source)
{
return new AsyncQueryableWrapper<T>(source);
}
}
internal class AsyncQueryableWrapper<T>: IDbAsyncEnumerable<T>, IQueryable<T>
{
private readonly IQueryable<T> _source;
public AsyncQueryableWrapper(IQueryable<T> source)
{
_source = source;
}
public AsyncQueryableWrapper(IEnumerable<T> source)
{
_source = source.AsQueryable();
}
public IDbAsyncEnumerator<T> GetAsyncEnumerator()
{
return new AsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
}
public IEnumerator<T> GetEnumerator()
{
return _source.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public Expression Expression => _source.Expression;
public Type ElementType => _source.ElementType;
public IQueryProvider Provider => new AsyncQueryProvider<T>(_source.Provider);
}
internal class AsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T>
{
public AsyncEnumerable(IEnumerable<T> enumerable)
: base(enumerable)
{ }
public AsyncEnumerable(Expression expression)
: base(expression)
{ }
public IDbAsyncEnumerator<T> GetAsyncEnumerator()
{
return new AsyncEnumerator<T>(this.AsEnumerable().GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator()
{
return GetAsyncEnumerator();
}
IQueryProvider IQueryable.Provider => new AsyncQueryProvider<T>(this);
}
internal class AsyncQueryProvider<TEntity> : IDbAsyncQueryProvider
{
private readonly IQueryProvider _inner;
internal AsyncQueryProvider(IQueryProvider inner)
{
_inner = inner;
}
public IQueryable CreateQuery(Expression expression)
{
var t = expression.Type;
if (!t.IsGenericType)
{
return new AsyncEnumerable<TEntity>(expression);
}
var genericParams = t.GetGenericArguments();
var genericParam = genericParams[0];
var enumerableType = typeof(AsyncEnumerable<>).MakeGenericType(genericParam);
return (IQueryable)Activator.CreateInstance(enumerableType, expression);
}
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new AsyncEnumerable<TElement>(expression);
}
public object Execute(Expression expression)
{
return _inner.Execute(expression);
}
public TResult Execute<TResult>(Expression expression)
{
return _inner.Execute<TResult>(expression);
}
public Task<object> ExecuteAsync(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(expression));
}
public Task<TResult> ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken)
{
return Task.FromResult(Execute<TResult>(expression));
}
}
internal class AsyncEnumerator<T> : IDbAsyncEnumerator<T>
{
private readonly IEnumerator<T> _inner;
public AsyncEnumerator(IEnumerator<T> inner)
{
_inner = inner;
}
public void Dispose()
{
_inner.Dispose();
}
public Task<bool> MoveNextAsync(CancellationToken cancellationToken)
{
return Task.FromResult(_inner.MoveNext());
}
public T Current => _inner.Current;
object IDbAsyncEnumerator.Current => Current;
}
}
来源:https://stackoverflow.com/questions/33305495/how-to-return-empty-iqueryable-in-an-async-repository-method