How can I turn an enumerable into an AsyncEnumerable?

.net c# entity-framework-core linq

Question

I have some code that uses the async extension methods available in Entity Framework Core:

public async Task<MyResult> DoQuery<T>(IQueryable<T> queryable)
{
    var count = await queryable.CountAsync();
    var firstItems = await queryable
        .Take(5)
        .ToArrayAsync();

    return new MyResult(count, firstItems);
}

This works great when the IQueryable I give the function comes straight from EF. I also wanted to reuse this code to do some logic on a LINQ-to-objects "query":

var evens = Enumerable.Range(0, 10).Where(i => i % 2 == 0);
var result = DoQuery(evens.AsQueryable());

This fails (which isn't too surprising):

System.InvalidOperationException: The provider for the source IQueryable doesn't implement IAsyncQueryProvider. Only providers that implement IEntityQueryProvider can be used for Entity Framework asynchronous operations.

It looks like a refactor is in order, but I'm curious: is there any way to turn a plain enumerable into a "dummy" AsyncEnumerable or AsyncQueryable that treats CountAsync synchronously?

1
1
3/27/2017 2:09:07 AM

Accepted Answer

You need to create an in-memory DbAsyncQueryProvider to process the async query. There is a detailed explanation on how to do that here. Scroll to the part about Testing with async queries. Below is the code copy & pasted from that link:

using System.Collections.Generic; 
using System.Data.Entity.Infrastructure; 
using System.Linq; 
using System.Linq.Expressions; 
using System.Threading; 
using System.Threading.Tasks; 

namespace TestingDemo 
{ 
    internal class TestDbAsyncQueryProvider<TEntity> : IDbAsyncQueryProvider 
    { 
        private readonly IQueryProvider _inner; 

        internal TestDbAsyncQueryProvider(IQueryProvider inner) 
        { 
            _inner = inner; 
        } 

        public IQueryable CreateQuery(Expression expression) 
        { 
            return new TestDbAsyncEnumerable<TEntity>(expression); 
        } 

        public IQueryable<TElement> CreateQuery<TElement>(Expression expression) 
        { 
            return new TestDbAsyncEnumerable<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 TestDbAsyncEnumerable<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T>, IQueryable<T> 
    { 
        public TestDbAsyncEnumerable(IEnumerable<T> enumerable) 
            : base(enumerable) 
        { } 

        public TestDbAsyncEnumerable(Expression expression) 
            : base(expression) 
        { } 

        public IDbAsyncEnumerator<T> GetAsyncEnumerator() 
        { 
            return new TestDbAsyncEnumerator<T>(this.AsEnumerable().GetEnumerator()); 
        } 

        IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() 
        { 
            return GetAsyncEnumerator(); 
        } 

        IQueryProvider IQueryable.Provider 
        { 
            get { return new TestDbAsyncQueryProvider<T>(this); } 
        } 
    } 

    internal class TestDbAsyncEnumerator<T> : IDbAsyncEnumerator<T> 
    { 
        private readonly IEnumerator<T> _inner; 

        public TestDbAsyncEnumerator(IEnumerator<T> inner) 
        { 
            _inner = inner; 
        } 

        public void Dispose() 
        { 
            _inner.Dispose(); 
        } 

        public Task<bool> MoveNextAsync(CancellationToken cancellationToken) 
        { 
            return Task.FromResult(_inner.MoveNext()); 
        } 

        public T Current 
        { 
            get { return _inner.Current; } 
        } 

        object IDbAsyncEnumerator.Current 
        { 
            get { return Current; } 
        } 
    } 
}
3
3/27/2017 2:27:52 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow