diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs index 71772163dc5..41844ab73a6 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryProjectionBindingExpressionVisitor.cs @@ -128,28 +128,35 @@ public override Expression Visit(Expression expression) && methodCallExpression.Method.DeclaringType == typeof(Enumerable) && methodCallExpression.Method.Name == nameof(Enumerable.ToList)) { - return AddCollectionProjection( - _queryableMethodTranslatingExpressionVisitor.TranslateSubquery( - methodCallExpression.Arguments[0]), - null, - methodCallExpression.Method.GetGenericArguments()[0]); - } + var subqueryTranslation = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery( + methodCallExpression.Arguments[0]); - var subquery = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression); - if (subquery != null) + if (subqueryTranslation != null) + { + return AddCollectionProjection( + subqueryTranslation, + null, + methodCallExpression.Method.GetGenericArguments()[0]); + } + } + else { - if (subquery.ResultCardinality == ResultCardinality.Enumerable) + var subquery = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression); + if (subquery != null) { - return AddCollectionProjection(subquery, null, subquery.ShaperExpression.Type); + if (subquery.ResultCardinality == ResultCardinality.Enumerable) + { + return AddCollectionProjection(subquery, null, subquery.ShaperExpression.Type); + } + + return new SingleResultShaperExpression( + new ProjectionBindingExpression( + _queryExpression, + _queryExpression.AddSubqueryProjection(subquery, out var innerShaper), + typeof(ValueBuffer)), + innerShaper, + subquery.ShaperExpression.Type); } - - return new SingleResultShaperExpression( - new ProjectionBindingExpression( - _queryExpression, - _queryExpression.AddSubqueryProjection(subquery, out var innerShaper), - typeof(ValueBuffer)), - innerShaper, - subquery.ShaperExpression.Type); } break; diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index 1d430500696..789eab9c1d8 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -167,39 +167,43 @@ public override Expression Visit(Expression expression) var result = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery( methodCallExpression.Arguments[0]); - return _selectExpression.AddCollectionProjection(result, null, elementType); - } - - var subquery = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression); - - if (subquery != null) - { - if (subquery.ResultCardinality == ResultCardinality.Enumerable) + if (result != null) { - return _selectExpression.AddCollectionProjection(subquery, null, subquery.ShaperExpression.Type); + return _selectExpression.AddCollectionProjection(result, null, elementType); } - - static bool IsAggregateResultWithCustomShaper(MethodInfo method) + } + else + { + var subquery = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression); + if (subquery != null) { - if (method.IsGenericMethod) + if (subquery.ResultCardinality == ResultCardinality.Enumerable) { - method = method.GetGenericMethodDefinition(); + return _selectExpression.AddCollectionProjection(subquery, null, subquery.ShaperExpression.Type); } - return QueryableMethods.IsAverageWithoutSelector(method) - || QueryableMethods.IsAverageWithSelector(method) - || method == QueryableMethods.MaxWithoutSelector - || method == QueryableMethods.MaxWithSelector - || method == QueryableMethods.MinWithoutSelector - || method == QueryableMethods.MinWithSelector - || QueryableMethods.IsSumWithoutSelector(method) - || QueryableMethods.IsSumWithSelector(method); - } + static bool IsAggregateResultWithCustomShaper(MethodInfo method) + { + if (method.IsGenericMethod) + { + method = method.GetGenericMethodDefinition(); + } + + return QueryableMethods.IsAverageWithoutSelector(method) + || QueryableMethods.IsAverageWithSelector(method) + || method == QueryableMethods.MaxWithoutSelector + || method == QueryableMethods.MaxWithSelector + || method == QueryableMethods.MinWithoutSelector + || method == QueryableMethods.MinWithSelector + || QueryableMethods.IsSumWithoutSelector(method) + || QueryableMethods.IsSumWithSelector(method); + } - if (!(subquery.ShaperExpression is ProjectionBindingExpression - || IsAggregateResultWithCustomShaper(methodCallExpression.Method))) - { - return _selectExpression.AddSingleProjection(subquery); + if (!(subquery.ShaperExpression is ProjectionBindingExpression + || IsAggregateResultWithCustomShaper(methodCallExpression.Method))) + { + return _selectExpression.AddSingleProjection(subquery); + } } } diff --git a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs index 7536ad54233..b80e7fd4731 100644 --- a/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore/Query/QueryableMethodTranslatingExpressionVisitor.cs @@ -630,7 +630,7 @@ public virtual ShapedQueryExpression TranslateSubquery([NotNull] Expression expr Check.NotNull(expression, nameof(expression)); var subqueryVisitor = CreateSubqueryVisitor(); - var translation = (ShapedQueryExpression)subqueryVisitor.Visit(expression); + var translation = subqueryVisitor.Visit(expression) as ShapedQueryExpression; if (translation == null && subqueryVisitor.TranslationErrorDetails != null) { AddTranslationErrorDetails(subqueryVisitor.TranslationErrorDetails); diff --git a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs index f26a36265b5..e57ec90b63e 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs @@ -5991,6 +5991,39 @@ public virtual Task Select_distinct_Select_with_client_bindings(bool async) .Select(e => new DTO { Property = ClientMethod(e) })); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task ToList_over_string(bool async) + { + return AssertQuery( + async, + ss => ss.Set().OrderBy(c => c.CustomerID).Select(e => new { Property = e.City.ToList() }), + assertOrder: true, + elementAsserter: (e,a) => Assert.True(e.Property.SequenceEqual(a.Property))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task ToArray_over_string(bool async) + { + return AssertQuery( + async, + ss => ss.Set().OrderBy(c => c.CustomerID).Select(e => new { Property = e.City.ToArray() }), + assertOrder: true, + elementAsserter: (e, a) => Assert.True(e.Property.SequenceEqual(a.Property))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task AsEnumerable_over_string(bool async) + { + return AssertQuery( + async, + ss => ss.Set().OrderBy(c => c.CustomerID).Select(e => new { Property = e.City.AsEnumerable() }), + assertOrder: true, + elementAsserter: (e, a) => Assert.True(e.Property.SequenceEqual(a.Property))); + } + private static int ClientMethod(int s) => s; } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index 525c1f8bf6e..347fd6645e4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -5133,6 +5133,36 @@ WHERE [o].[OrderID] < 10000 ) AS [t]"); } + public override async Task ToList_over_string(bool async) + { + await base.ToList_over_string(async); + + AssertSql( + @"SELECT [c].[City] +FROM [Customers] AS [c] +ORDER BY [c].[CustomerID]"); + } + + public override async Task ToArray_over_string(bool async) + { + await base.ToArray_over_string(async); + + AssertSql( + @"SELECT [c].[City] +FROM [Customers] AS [c] +ORDER BY [c].[CustomerID]"); + } + + public override async Task AsEnumerable_over_string(bool async) + { + await base.AsEnumerable_over_string(async); + + AssertSql( + @"SELECT [c].[City] +FROM [Customers] AS [c] +ORDER BY [c].[CustomerID]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);