From f2e1e9ac29a60cf93d66b033fc31df32d83be9bd Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Thu, 6 Jan 2022 14:49:59 -0800 Subject: [PATCH] Query: Assign proper type to CollectionResultExpression We copied type from subquery itself which is always Queryable type because of conversion we do. We need to convert it back to enumerable. Issue happens only when nested level because we use element type so for 1 level queryable is gone. But for nested level we get Queryable as element type on outer which is not assignable from List The conversion doesn't cause issue with user having Queryable in the result since that throws exception much earlier. Resolves #27105 --- ...ionalProjectionBindingExpressionVisitor.cs | 13 +++++++++- .../Query/NorthwindSelectQueryCosmosTest.cs | 6 +++++ .../Query/NorthwindSelectQueryTestBase.cs | 26 +++++++++++++++++++ .../NorthwindSelectQuerySqlServerTest.cs | 16 ++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs index b3071f67dc7..b94535ec835 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs @@ -160,6 +160,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio _queryableMethodTranslatingExpressionVisitor.TranslateSubquery( materializeCollectionNavigationExpression.Subquery)!); return new CollectionResultExpression( + // expression.Type will be CLR type of the navigation here so that is fine. new ProjectionBindingExpression(_selectExpression, _clientProjections.Count - 1, expression.Type), materializeCollectionNavigationExpression.Navigation, materializeCollectionNavigationExpression.Navigation.ClrType.GetSequenceType()); @@ -176,6 +177,7 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio if (subquery != null) { _clientProjections!.Add(subquery); + // expression.Type here will be List return new CollectionResultExpression( new ProjectionBindingExpression(_selectExpression, _clientProjections.Count - 1, expression.Type), navigation: null, @@ -195,8 +197,17 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio } _clientProjections!.Add(subquery); + var type = expression.Type; + if (!(AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue27105", out var enabled) && enabled)) + { + if (type.IsGenericType + && type.GetGenericTypeDefinition() == typeof(IQueryable<>)) + { + type = typeof(IEnumerable<>).MakeGenericType(type.GetSequenceType()); + } + } var projectionBindingExpression = new ProjectionBindingExpression( - _selectExpression, _clientProjections.Count - 1, expression.Type); + _selectExpression, _clientProjections.Count - 1, type); return subquery.ResultCardinality == ResultCardinality.Enumerable ? new CollectionResultExpression( projectionBindingExpression, navigation: null, subquery.ShaperExpression.Type) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs index 53caf32a5c4..1a731c66ce9 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindSelectQueryCosmosTest.cs @@ -1326,6 +1326,12 @@ public override Task MemberInit_in_projection_without_arguments(bool async) return base.MemberInit_in_projection_without_arguments(async); } + [ConditionalTheory(Skip = "Cross collection join Issue#17246")] + public override Task List_of_list_of_anonymous_type(bool async) + { + return base.List_of_list_of_anonymous_type(async); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs index f5fd0c70f10..1b0be4d80b0 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindSelectQueryTestBase.cs @@ -2446,5 +2446,31 @@ public virtual Task MemberInit_in_projection_without_arguments(bool async) AssertEqual(e.Orders.Count(), a.Orders.Count()); }); } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task List_of_list_of_anonymous_type(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Where(c => c.CustomerID.StartsWith("F")) + .Select(c => new + { + c.CustomerID, + ListWithSubList = c.Orders.OrderBy(e => e.OrderID).Select(o => o.OrderDetails.Select(e => new + { + e.OrderID, + e.ProductID + })) + }), + elementSorter: e => e.CustomerID, + elementAsserter: (e, a) => + { + AssertEqual(e.CustomerID, a.CustomerID); + AssertCollection(e.ListWithSubList, a.ListWithSubList, ordered: true, + elementAsserter: (ee, aa) => AssertCollection(ee, aa, elementSorter: i => (i.OrderID, i.ProductID))); + }); + } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs index c8bd7504324..25511604f61 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSelectQuerySqlServerTest.cs @@ -1878,6 +1878,22 @@ WHERE [c].[CustomerID] LIKE N'F%' ORDER BY [c].[CustomerID]"); } + public override async Task List_of_list_of_anonymous_type(bool async) + { + await base.List_of_list_of_anonymous_type(async); + + AssertSql( + @"SELECT [c].[CustomerID], [t].[OrderID], [t].[OrderID0], [t].[ProductID] +FROM [Customers] AS [c] +LEFT JOIN ( + SELECT [o].[OrderID], [o0].[OrderID] AS [OrderID0], [o0].[ProductID], [o].[CustomerID] + FROM [Orders] AS [o] + LEFT JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] +) AS [t] ON [c].[CustomerID] = [t].[CustomerID] +WHERE [c].[CustomerID] LIKE N'F%' +ORDER BY [c].[CustomerID], [t].[OrderID], [t].[OrderID0]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);