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 !);
Exemple #2
0
        public static AsyncTypeAwaitable AsTypeAwaitable <TSource>(this ValueTask <TSource> task)
        {
            var taskTypeDescritor = AwaitableTypeDescriptor.GetTypeDescriptor(task.GetType());

            return(taskTypeDescritor.GetAwaitable(task));
        }