ASP.NET CoreにMVCプロジェクトがあります。私の問題はIQueryableおよび非同期に関連しています。 _IQueryable<T>
_に検索用の次のメソッドを記述しました。
_private IQueryable<InternalOrderInfo> WhereSearchTokens(IQueryable<InternalOrderInfo> query, SearchToken[] searchTokens)
{
if (searchTokens.Length == 0)
{
return query;
}
var results = new List<InternalOrderInfo>();
foreach (var searchToken in searchTokens)
{
//search logic, intermediate results are being added to `results` using `AddRange()`
}
return results.Count != 0 ? results.Distinct().AsQueryable() : query;
}
_
これをメソッドExecuteAsync()
で呼び出します。
_public async Task<GetAllInternalOrderInfoResponse> ExecuteAsync(GetAllInternalOrderInfoRequest request)
{
//rest of the code
if (searchTokens != null && searchTokens.Any())
{
allInternalOrderInfo = WhereSearchTokens(allInternalOrderInfo, searchTokens);
}
var orders = await allInternalOrderInfo.Skip(offset).Take(limit).ToArrayAsync();
//rest of the code
}
_
これをテストすると、ToArrayAsync()
を呼び出す行にInvalidOperationExceptionが表示されます
ソースIQueryableはIAsyncEnumerableを実装していません。 Entity Frameworkの非同期操作に使用できるのは、IAsyncEnumerableを実装するソースのみです。
ToArrayAsync()
をToListAsync()
に変更しましたが、何も変更されていません。私はしばらくこの問題を検索しましたが、解決された質問は主にDbContext
およびエンティティの作成に関連しています。このプロジェクトにはEntityFrameworkがインストールされていません。アプリケーションのアーキテクチャ上、インストールしないことをお勧めします。誰かが私の状況で何をすべきか考えていることを願っています。
デザインを変更しない場合は、いくつかのオプションがあります。
1)AsQueryable
を、IQueryable
も実装するIDbAsyncEnumerable
を返す別のメソッドに変更します。たとえば、EnumerableQuery
(AsQueryable
によって返される)を拡張できます。
public class AsyncEnumerableQuery<T> : EnumerableQuery<T>, IDbAsyncEnumerable<T> {
public AsyncEnumerableQuery(IEnumerable<T> enumerable) : base(enumerable) {
}
public AsyncEnumerableQuery(Expression expression) : base(expression) {
}
public IDbAsyncEnumerator<T> GetAsyncEnumerator() {
return new InMemoryDbAsyncEnumerator<T>(((IEnumerable<T>) this).GetEnumerator());
}
IDbAsyncEnumerator IDbAsyncEnumerable.GetAsyncEnumerator() {
return GetAsyncEnumerator();
}
private class InMemoryDbAsyncEnumerator<T> : IDbAsyncEnumerator<T> {
private readonly IEnumerator<T> _enumerator;
public InMemoryDbAsyncEnumerator(IEnumerator<T> enumerator) {
_enumerator = enumerator;
}
public void Dispose() {
}
public Task<bool> MoveNextAsync(CancellationToken cancellationToken) {
return Task.FromResult(_enumerator.MoveNext());
}
public T Current => _enumerator.Current;
object IDbAsyncEnumerator.Current => Current;
}
}
その後、あなたは変わります
results.Distinct().AsQueryable()
に
new AsyncEnumerableQuery<InternalOrderInfo>(results.Distinct())
その後、ToArrayAsync
は例外をスローしなくなります(明らかに、AsQueryable
のような独自の拡張メソッドを作成できます)。
2)ToArrayAsync
部分を変更します。
public static class EfExtensions {
public static Task<TSource[]> ToArrayAsyncSafe<TSource>(this IQueryable<TSource> source) {
if (source == null)
throw new ArgumentNullException(nameof(source));
if (!(source is IDbAsyncEnumerable<TSource>))
return Task.FromResult(source.ToArray());
return source.ToArrayAsync();
}
}
そして、ToArrayAsyncSafe
がToArrayAsync
でない場合に同期列挙にフォールバックするIQueryable
の代わりにIDbAsyncEnumerable
を使用します。あなたの場合、これはクエリが実際にはメモリ内のリストであり、クエリではない場合にのみ発生するため、非同期実行はとにかく意味がありません。
@Titian Cernicova-Dragomirが述べたように、例外はList<InternalOrderInfo>
がIAsyncEnumerable
を実装していないことを意味します
しかし、これは論理/設計エラーです。メソッドがIQueryable
で動作し、IQueryable
を返す場合、コレクションはアプリのメモリにあると想定するIQueryable
ではなく、IEnumarable
で動作する必要があります。 IQueryable
とIEnumarable
の違いと、メソッドから返す必要のあるものについて、もっと読む必要があります。まず、回答を読むことをお勧めします ここ および ここ
そのため、WhereSearchTokens
メソッドまたはそれ以前にすでにdbから結果をフェッチしているため、ToArrayAsync
によって行われるdbへの非同期リクエストを実行してIQueryable
を返す理由はありません。
ここには2つのオプションがあります:
1)InternalOrderInfo
の前にWhereSearchTokens
のコレクションがdbからメモリにフェッチされる場合、すべてのアクションを同期モードで実行します。つまり、ToArray
の代わりにToArrayAsync
を呼び出し、IEnumerable
とWhereSearchTokens
の両方からTaks<IQueryable>
ではなくExecuteAsync
を返します。
2)InternalOrderInfo
のコレクションがWhereSearchTokens
内でフェッチされ、dbへの非同期リクエストを行う場合は、//search logic, intermediate results are being added to results using AddRange()
のどこかでのみ非同期EF APIを呼び出し、WhereSearchTokens
からTaks<IEnumerable>
ではなくTaks<IQueryable>
を返す必要があります。
EF Coreの場合:
public static class QueryableExtensions
{
public static IQueryable<T> AsAsyncQueryable<T>(this IEnumerable<T> input)
{
return new NotInDbSet<T>( input );
}
}
public class NotInDbSet< T > : IQueryable<T>, IAsyncEnumerable< T >, IEnumerable< T >, IEnumerable
{
private readonly List< T > _innerCollection;
public NotInDbSet( IEnumerable< T > innerCollection )
{
_innerCollection = innerCollection.ToList();
}
public IAsyncEnumerator< T > GetAsyncEnumerator( CancellationToken cancellationToken = new CancellationToken() )
{
return new AsyncEnumerator( GetEnumerator() );
}
public IEnumerator< T > GetEnumerator()
{
return _innerCollection.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public class AsyncEnumerator : IAsyncEnumerator< T >
{
private readonly IEnumerator< T > _enumerator;
public AsyncEnumerator( IEnumerator< T > enumerator )
{
_enumerator = enumerator;
}
public ValueTask DisposeAsync()
{
return new ValueTask();
}
public ValueTask< bool > MoveNextAsync()
{
return new ValueTask< bool >( _enumerator.MoveNext() );
}
public T Current => _enumerator.Current;
}
public Type ElementType => typeof( T );
public Expression Expression => Expression.Empty();
public IQueryProvider Provider => new EnumerableQuery<T>( Expression );
}
AsQueryable()
は、result
リストをEntity Framework IQueryable
に変換しません。そして、エラーが示すように、ToArrayAsync()
で使用されるIQueryable
はIAsyncEnumerable
を実装する必要がありますが、これはAsQueryable
が返すものではありません。
Enumerables here でのAsQueryable
の使用について詳しく読むことができます。