public async ValueTask <TResult> ExecuteAsync <TResult>(Expression expression, CancellationToken token) { if (expression is null) { throw new ArgumentNullException(nameof(expression)); } var expressionTypeDescriptor = AwaitableTypeDescriptor.GetTypeDescriptor(expression.Type); var expressionResultType = expressionTypeDescriptor.ResultType; if (!expressionResultType.IsAssignableTo <TResult>()) { throw new ArgumentException( $"The expression type must either be assignable to {typeof(TResult)} or an awaitable type with a result type of {typeof(TResult)}.", nameof(expression)); } // Rewrite the recorded expression, in order to perform as many of the query processing in the database // engine (or the database driver) which will be highly optimized by them. Fall back to in memory processing // only if necessary as a post-processing step. var expressionVisitor = new QueryExpressionVisitor(_methodProcessor); expression = expressionVisitor.Visit(expression); // Get the untyped (typeof object) result from the expression. This does not execute the query // in the general case but only builds the 'query-processor' that provides on of possible results: // 1. A constant expression of type TranslatedQueryable when the query could be translated to be database // compatible entirely. // 2. An IAsyncQueryable<TElement> when the query could not be translated to be database compatible entirely // and includes an in-memory post-processing step. // 3. A Task<TResult> / ValueTask<TResult> or another awaitable type that produces a result of type TResult // when a query method is executed as the last step that produces a scalar result var untypedResult = expression.Evaluate(); // TODO: Make preferInterpretation param configurable if (expression.Type == typeof(TranslatedGroupByQueryable)) { if (untypedResult is not TranslatedGroupByQueryable translatedQueryable) { throw new InvalidOperationException(); // TODO } var elementType = translatedQueryable.ElementType; var keyType = translatedQueryable.KeyType; var expectedResultType = TypeHelper.GetAsyncEnumerableType(translatedQueryable.AsyncGroupingType); // Due to the check above and the fact that we only translate instances of type AsyncQueryable, // the type of the original expression must be one of the following: // object, // IAsyncQueryable, IAsyncQueryable<T>, // IOrderedAsyncQueryable, IOrderedAsyncQueryable<T>, // IAsyncEnumerable<T> // Therefore TResult MUST be of type IAsyncEnumerable<T> if (!typeof(TResult).IsAssignableTo(expectedResultType)) // TODO: Isn't IsAssignableFrom correct here? { throw new InvalidOperationException(); // TODO } // Get the queryable from the expression var queryable = translatedQueryable.QueryProvider.CreateQuery(translatedQueryable.Expression); // Evaluation the expression var result = QueryAdapter.EvaluateAsync(translatedQueryable.GroupingType, queryable, token); // Translate IAsyncEnumerable<IGrouping<TKey, TElement>> // to IAsyncEnumerable<IAsyncGrouping<TKey, TElement>> return((TResult)GroupSequenceConverter.Convert(keyType, elementType, result)); } if (expression.Type == typeof(TranslatedQueryable)) { if (untypedResult is not TranslatedQueryable translatedQueryable) { throw new InvalidOperationException(); // TODO } var elementType = translatedQueryable.ElementType; // Due to the check above and the fact that we only translate instances of type AsyncQueryable, // the type of the original expression must be one of the following: // object, // IAsyncQueryable, IAsyncQueryable<T>, // IOrderedAsyncQueryable, IOrderedAsyncQueryable<T>, // IAsyncEnumerable<T> // Therefore TResult MUST be of type IAsyncEnumerable<T> if (!typeof(TResult).IsAssignableTo(typeof(IAsyncEnumerable <>).MakeGenericType(elementType))) // TODO: Isn't IsAssignableFrom correct here? { throw new InvalidOperationException(); // TODO } // Get the queryable from the expression var queryable = translatedQueryable.QueryProvider.CreateQuery(translatedQueryable.Expression); return((TResult)QueryAdapter.EvaluateAsync(elementType, queryable, token)); } // Unpack the result if (expressionTypeDescriptor.IsAwaitable && untypedResult is not null) { untypedResult = await expressionTypeDescriptor.GetAwaitable(untypedResult); } if (untypedResult is null) { // TODO: What do we return if the types don't match? Throw an exception? // Is the type annotation of this method even correct? return(default !);
public static AsyncTypeAwaitable AsTypeAwaitable <TSource>(this ValueTask <TSource> task) { var taskTypeDescritor = AwaitableTypeDescriptor.GetTypeDescriptor(task.GetType()); return(taskTypeDescritor.GetAwaitable(task)); }