From 8e98077e83d730bc7edfb1b287011dc9614e7f00 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Tue, 20 Aug 2019 17:30:38 -0700 Subject: [PATCH] Query: Add RowNumberExpression support to convert lateral joins to joins This enables support for non-scalar single result on Sqlite. Resolves #10001 for Sqlite Resolves #17292 --- .../Query/QuerySqlGenerator.cs | 17 +++ .../Query/SqlExpressionVisitor.cs | 5 +- .../Query/SqlExpressions/CaseExpression.cs | 1 + .../SqlExpressions/RowNumberExpression.cs | 96 ++++++++++++++ .../Query/SqlExpressions/SelectExpression.cs | 52 +++++++- ...rchConditionConvertingExpressionVisitor.cs | 26 ++++ .../Query/QueryNavigationsSqlServerTest.cs | 122 ++++++++++-------- .../Query/SimpleQuerySqlServerTest.cs | 15 ++- .../Query/QueryNavigationsSqliteTest.cs | 44 ------- .../Query/SimpleQuerySqliteTest.cs | 2 +- 10 files changed, 273 insertions(+), 107 deletions(-) create mode 100644 src/EFCore.Relational/Query/SqlExpressions/RowNumberExpression.cs diff --git a/src/EFCore.Relational/Query/QuerySqlGenerator.cs b/src/EFCore.Relational/Query/QuerySqlGenerator.cs index 16f4d8c0549..8e2932d4c63 100644 --- a/src/EFCore.Relational/Query/QuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/QuerySqlGenerator.cs @@ -762,5 +762,22 @@ protected override Expression VisitSubSelect(ScalarSubqueryExpression scalarSubq return scalarSubqueryExpression; } + + protected override Expression VisitRowNumber(RowNumberExpression rowNumberExpression) + { + _relationalCommandBuilder.Append("ROW_NUMBER() OVER("); + if (rowNumberExpression.Partitions.Any()) + { + _relationalCommandBuilder.Append("PARTITION BY "); + GenerateList(rowNumberExpression.Partitions, e => Visit(e)); + _relationalCommandBuilder.Append(" "); + } + + _relationalCommandBuilder.Append("ORDER BY "); + GenerateList(rowNumberExpression.Orderings, e => Visit(e)); + _relationalCommandBuilder.Append(")"); + + return rowNumberExpression; + } } } diff --git a/src/EFCore.Relational/Query/SqlExpressionVisitor.cs b/src/EFCore.Relational/Query/SqlExpressionVisitor.cs index 831dcd21530..ca2c4c2f6e5 100644 --- a/src/EFCore.Relational/Query/SqlExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/SqlExpressionVisitor.cs @@ -3,7 +3,6 @@ using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Query.SqlExpressions; -using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Query { @@ -52,6 +51,9 @@ protected override Expression VisitExtension(Expression extensionExpression) case ProjectionExpression projectionExpression: return VisitProjection(projectionExpression); + case RowNumberExpression rowNumberExpression: + return VisitRowNumber(rowNumberExpression); + case SelectExpression selectExpression: return VisitSelect(selectExpression); @@ -83,6 +85,7 @@ protected override Expression VisitExtension(Expression extensionExpression) return base.VisitExtension(extensionExpression); } + protected abstract Expression VisitRowNumber(RowNumberExpression rowNumberExpression); protected abstract Expression VisitExists(ExistsExpression existsExpression); protected abstract Expression VisitIn(InExpression inExpression); protected abstract Expression VisitCrossJoin(CrossJoinExpression crossJoinExpression); diff --git a/src/EFCore.Relational/Query/SqlExpressions/CaseExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/CaseExpression.cs index 7abbdf61184..3653c48ac94 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/CaseExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/CaseExpression.cs @@ -122,6 +122,7 @@ private bool Equals(CaseExpression caseExpression) public override int GetHashCode() { var hash = new HashCode(); + hash.Add(base.GetHashCode()); hash.Add(Operand); for (var i = 0; i < WhenClauses.Count; i++) { diff --git a/src/EFCore.Relational/Query/SqlExpressions/RowNumberExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/RowNumberExpression.cs new file mode 100644 index 00000000000..c37df3ee147 --- /dev/null +++ b/src/EFCore.Relational/Query/SqlExpressions/RowNumberExpression.cs @@ -0,0 +1,96 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions +{ + public class RowNumberExpression : SqlExpression + { + public RowNumberExpression(IReadOnlyList partitions, IReadOnlyList orderings, RelationalTypeMapping typeMapping) + : base(typeof(long), typeMapping) + { + Check.NotEmpty(orderings, nameof(orderings)); + + Partitions = partitions; + Orderings = orderings; + } + + public virtual IReadOnlyList Partitions { get; } + public virtual IReadOnlyList Orderings { get; } + + protected override Expression VisitChildren(ExpressionVisitor visitor) + { + var changed = false; + var partitions = new List(); + foreach (var partition in Partitions) + { + var newPartition = (SqlExpression)visitor.Visit(partition); + changed |= newPartition != partition; + partitions.Add(newPartition); + } + + var orderings = new List(); + foreach (var ordering in Orderings) + { + var newOrdering = (OrderingExpression)visitor.Visit(ordering); + changed |= newOrdering != ordering; + orderings.Add(newOrdering); + } + + return changed + ? new RowNumberExpression(partitions, orderings, TypeMapping) + : this; + } + + public virtual RowNumberExpression Update(IReadOnlyList partitions, IReadOnlyList orderings) + { + return (Partitions == null ? partitions == null : Partitions.SequenceEqual(partitions)) + && Orderings.SequenceEqual(orderings) + ? this + : new RowNumberExpression(partitions, orderings, TypeMapping); + } + + public override void Print(ExpressionPrinter expressionPrinter) + { + expressionPrinter.Append("ROW_NUMBER() OVER(PARTITION BY "); + expressionPrinter.VisitList(Partitions); + expressionPrinter.Append(" ORDER BY "); + expressionPrinter.VisitList(Orderings); + expressionPrinter.Append(")"); + } + + public override bool Equals(object obj) + => obj != null + && (ReferenceEquals(this, obj) + || obj is RowNumberExpression rowNumberExpression + && Equals(rowNumberExpression)); + + private bool Equals(RowNumberExpression rowNumberExpression) + => base.Equals(rowNumberExpression) + && (Partitions == null ? rowNumberExpression.Partitions == null : Partitions.SequenceEqual(rowNumberExpression.Partitions)) + && Orderings.SequenceEqual(rowNumberExpression.Orderings); + + public override int GetHashCode() + { + var hash = new HashCode(); + hash.Add(base.GetHashCode()); + foreach (var partition in Partitions) + { + hash.Add(partition); + } + + foreach (var ordering in Orderings) + { + hash.Add(ordering); + } + + return hash.ToHashCode(); + } + } +} diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 88d625023ef..1974429f883 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -559,9 +559,9 @@ ColumnExpression addSetOperationColumnProjections( } } - private ColumnExpression GenerateOuterColumn(SqlExpression projection) + private ColumnExpression GenerateOuterColumn(SqlExpression projection, string alias = null) { - var index = AddToProjection(projection); + var index = AddToProjection(projection, alias); return new ColumnExpression(_projection[index], this); } @@ -1009,6 +1009,22 @@ public override Expression Visit(Expression expression) } } + private void GetPartitions(SqlExpression sqlExpression, List partitions) + { + if (sqlExpression is SqlBinaryExpression sqlBinaryExpression) + { + if (sqlBinaryExpression.OperatorType == ExpressionType.Equal) + { + partitions.Add(sqlBinaryExpression.Right); + } + else if (sqlBinaryExpression.OperatorType == ExpressionType.AndAlso) + { + GetPartitions(sqlBinaryExpression.Left, partitions); + GetPartitions(sqlBinaryExpression.Right, partitions); + } + } + } + private enum JoinType { InnerJoin, @@ -1027,6 +1043,10 @@ private void AddJoin( // Try to convert lateral join to normal join if (joinType == JoinType.InnerJoinLateral || joinType == JoinType.LeftJoinLateral) { + // Doing for limit only since limit + offset may need sum + var limit = innerSelectExpression.Limit; + innerSelectExpression.Limit = null; + joinPredicate = TryExtractJoinKey(innerSelectExpression); if (joinPredicate != null) { @@ -1035,14 +1055,42 @@ private void AddJoin( if (containsOuterReference) { innerSelectExpression.ApplyPredicate(joinPredicate); + innerSelectExpression.ApplyLimit(limit); } else { + if (limit != null) + { + var partitions = new List(); + GetPartitions(joinPredicate, partitions); + var orderings = innerSelectExpression.Orderings.Any() + ? innerSelectExpression.Orderings + : innerSelectExpression._identifier.Select(e => new OrderingExpression(e, true)); + var rowNumberExpression = new RowNumberExpression(partitions, orderings.ToList(), limit.TypeMapping); + innerSelectExpression.ClearOrdering(); + + var projectionMappings = innerSelectExpression.PushdownIntoSubquery(); + var subquery = (SelectExpression)innerSelectExpression.Tables[0]; + + joinPredicate = new SqlRemappingVisitor( + projectionMappings, subquery) + .Remap(joinPredicate); + + var outerColumn = subquery.GenerateOuterColumn(rowNumberExpression, "row"); + var predicate = new SqlBinaryExpression( + ExpressionType.LessThanOrEqual, outerColumn, limit, typeof(bool), joinPredicate.TypeMapping); + innerSelectExpression.ApplyPredicate(predicate); + } + AddJoin(joinType == JoinType.InnerJoinLateral ? JoinType.InnerJoin : JoinType.LeftJoin, innerSelectExpression, transparentIdentifierType, joinPredicate); return; } } + else + { + innerSelectExpression.ApplyLimit(limit); + } } // Verify what are the cases of pushdown for inner & outer both sides diff --git a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs index 2a88ebbc89f..488045538ab 100644 --- a/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SearchConditionConvertingExpressionVisitor.cs @@ -361,5 +361,31 @@ protected override Expression VisitSubSelect(ScalarSubqueryExpression scalarSubq return ApplyConversion(scalarSubqueryExpression.Update(subquery), condition: false); } + + protected override Expression VisitRowNumber(RowNumberExpression rowNumberExpression) + { + var parentSearchCondition = _isSearchCondition; + _isSearchCondition = false; + var changed = false; + var partitions = new List(); + foreach (var partition in rowNumberExpression.Partitions) + { + var newPartition = (SqlExpression)Visit(partition); + changed |= newPartition != partition; + partitions.Add(newPartition); + } + + var orderings = new List(); + foreach (var ordering in rowNumberExpression.Orderings) + { + var newOrdering = (OrderingExpression)Visit(ordering); + changed |= newOrdering != ordering; + orderings.Add(newOrdering); + } + + _isSearchCondition = parentSearchCondition; + + return ApplyConversion(rowNumberExpression.Update(partitions, orderings), condition: false); + } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryNavigationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryNavigationsSqlServerTest.cs index 0af3a753ec5..7da8714ec20 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryNavigationsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryNavigationsSqlServerTest.cs @@ -116,14 +116,16 @@ public override async Task Take_Select_Navigation(bool isAsync) SELECT TOP(@__p_0) [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] ORDER BY [c].[CustomerID] -) AS [t] -OUTER APPLY ( - SELECT TOP(1) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] - FROM [Orders] AS [o] - WHERE ([t].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL - ORDER BY [o].[OrderID] -) AS [t0] -ORDER BY [t].[CustomerID]"); +) AS [t1] +LEFT JOIN ( + SELECT [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] + FROM ( + SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o].[CustomerID] ORDER BY [o].[OrderID]) AS [row] + FROM [Orders] AS [o] + ) AS [t] + WHERE [t].[row] <= 1 +) AS [t0] ON [t1].[CustomerID] = [t0].[CustomerID] +ORDER BY [t1].[CustomerID]"); } public override async Task Select_collection_FirstOrDefault_project_single_column1(bool isAsync) @@ -170,14 +172,16 @@ public override async Task Select_collection_FirstOrDefault_project_anonymous_ty SELECT TOP(@__p_0) [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] ORDER BY [c].[CustomerID] -) AS [t] -OUTER APPLY ( - SELECT TOP(1) [o].[CustomerID], [o].[OrderID] - FROM [Orders] AS [o] - WHERE ([t].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL - ORDER BY [o].[OrderID] -) AS [t0] -ORDER BY [t].[CustomerID]"); +) AS [t1] +LEFT JOIN ( + SELECT [t].[CustomerID], [t].[OrderID] + FROM ( + SELECT [o].[CustomerID], [o].[OrderID], ROW_NUMBER() OVER(PARTITION BY [o].[CustomerID] ORDER BY [o].[OrderID]) AS [row] + FROM [Orders] AS [o] + ) AS [t] + WHERE [t].[row] <= 1 +) AS [t0] ON [t1].[CustomerID] = [t0].[CustomerID] +ORDER BY [t1].[CustomerID]"); } public override async Task Select_collection_FirstOrDefault_project_entity(bool isAsync) @@ -192,14 +196,16 @@ public override async Task Select_collection_FirstOrDefault_project_entity(bool SELECT TOP(@__p_0) [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] FROM [Customers] AS [c] ORDER BY [c].[CustomerID] -) AS [t] -OUTER APPLY ( - SELECT TOP(1) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] - FROM [Orders] AS [o] - WHERE ([t].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL - ORDER BY [o].[OrderID] -) AS [t0] -ORDER BY [t].[CustomerID]"); +) AS [t1] +LEFT JOIN ( + SELECT [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] + FROM ( + SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o].[CustomerID] ORDER BY [o].[OrderID]) AS [row] + FROM [Orders] AS [o] + ) AS [t] + WHERE [t].[row] <= 1 +) AS [t0] ON [t1].[CustomerID] = [t0].[CustomerID] +ORDER BY [t1].[CustomerID]"); } public override async Task Skip_Select_Navigation(bool isAsync) @@ -217,14 +223,16 @@ public override async Task Skip_Select_Navigation(bool isAsync) FROM [Customers] AS [c] ORDER BY [c].[CustomerID] OFFSET @__p_0 ROWS -) AS [t] -OUTER APPLY ( - SELECT TOP(1) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] - FROM [Orders] AS [o] - WHERE ([t].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL - ORDER BY [o].[OrderID] -) AS [t0] -ORDER BY [t].[CustomerID]"); +) AS [t1] +LEFT JOIN ( + SELECT [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] + FROM ( + SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o].[CustomerID] ORDER BY [o].[OrderID]) AS [row] + FROM [Orders] AS [o] + ) AS [t] + WHERE [t].[row] <= 1 +) AS [t0] ON [t1].[CustomerID] = [t0].[CustomerID] +ORDER BY [t1].[CustomerID]"); } } @@ -675,14 +683,16 @@ public override async Task Collection_select_nav_prop_first_or_default(bool isAs await base.Collection_select_nav_prop_first_or_default(isAsync); AssertSql( - @"SELECT [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate] FROM [Customers] AS [c] -OUTER APPLY ( - SELECT TOP(1) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] - FROM [Orders] AS [o] - WHERE ([c].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL - ORDER BY [o].[OrderID] -) AS [t] +LEFT JOIN ( + SELECT [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] + FROM ( + SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o].[CustomerID] ORDER BY [o].[OrderID]) AS [row] + FROM [Orders] AS [o] + ) AS [t] + WHERE [t].[row] <= 1 +) AS [t0] ON [c].[CustomerID] = [t0].[CustomerID] ORDER BY [c].[CustomerID]"); } @@ -691,14 +701,18 @@ public override async Task Collection_select_nav_prop_first_or_default_then_nav_ await base.Collection_select_nav_prop_first_or_default_then_nav_prop(isAsync); AssertSql( - @"SELECT [t].[CustomerID], [t].[Address], [t].[City], [t].[CompanyName], [t].[ContactName], [t].[ContactTitle], [t].[Country], [t].[Fax], [t].[Phone], [t].[PostalCode], [t].[Region] + @"SELECT [t0].[CustomerID], [t0].[Address], [t0].[City], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Country], [t0].[Fax], [t0].[Phone], [t0].[PostalCode], [t0].[Region] FROM [Customers] AS [c0] -OUTER APPLY ( - SELECT TOP(1) [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [o].[OrderID] - FROM [Orders] AS [o] - LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] - WHERE (([c0].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL) AND [o].[OrderID] IN (10643, 10692, 10702, 10835, 10952, 11011) -) AS [t] +LEFT JOIN ( + SELECT [t].[CustomerID], [t].[Address], [t].[City], [t].[CompanyName], [t].[ContactName], [t].[ContactTitle], [t].[Country], [t].[Fax], [t].[Phone], [t].[PostalCode], [t].[Region], [t].[OrderID], [t].[CustomerID0] + FROM ( + SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [o].[OrderID], [o].[CustomerID] AS [CustomerID0], ROW_NUMBER() OVER(PARTITION BY [o].[CustomerID] ORDER BY [o].[OrderID]) AS [row] + FROM [Orders] AS [o] + LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] + WHERE [o].[OrderID] IN (10643, 10692, 10702, 10835, 10952, 11011) + ) AS [t] + WHERE [t].[row] <= 1 +) AS [t0] ON [c0].[CustomerID] = [t0].[CustomerID0] WHERE [c0].[CustomerID] LIKE N'A%' ORDER BY [c0].[CustomerID]"); } @@ -925,14 +939,16 @@ public override async Task Project_single_entity_value_subquery_works(bool isAsy await base.Project_single_entity_value_subquery_works(isAsync); AssertSql( - @"SELECT [c].[CustomerID], [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] + @"SELECT [c].[CustomerID], [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate] FROM [Customers] AS [c] -OUTER APPLY ( - SELECT TOP(1) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] - FROM [Orders] AS [o] - WHERE ([c].[CustomerID] = [o].[CustomerID]) AND [o].[CustomerID] IS NOT NULL - ORDER BY [o].[OrderID] -) AS [t] +LEFT JOIN ( + SELECT [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] + FROM ( + SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o].[CustomerID] ORDER BY [o].[OrderID]) AS [row] + FROM [Orders] AS [o] + ) AS [t] + WHERE [t].[row] <= 1 +) AS [t0] ON [c].[CustomerID] = [t0].[CustomerID] WHERE [c].[CustomerID] LIKE N'A%' ORDER BY [c].[CustomerID]"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs index 93b0d69ad25..bc5a7cc49b6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs @@ -1820,13 +1820,16 @@ public override async Task SelectMany_Joined_Take(bool isAsync) await base.SelectMany_Joined_Take(isAsync); AssertSql( - @"SELECT [c].[ContactName], [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] + @"SELECT [c].[ContactName], [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate] FROM [Customers] AS [c] -CROSS APPLY ( - SELECT TOP(4) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] - FROM [Orders] AS [o] - WHERE ([o].[CustomerID] = [c].[CustomerID]) AND [o].[CustomerID] IS NOT NULL -) AS [t]"); +INNER JOIN ( + SELECT [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] + FROM ( + SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o].[CustomerID] ORDER BY [o].[OrderID]) AS [row] + FROM [Orders] AS [o] + ) AS [t] + WHERE [t].[row] <= 4 +) AS [t0] ON [c].[CustomerID] = [t0].[CustomerID]"); } public override async Task Take_with_single(bool isAsync) diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/QueryNavigationsSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/QueryNavigationsSqliteTest.cs index 9f37288972c..254af28e9c0 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/QueryNavigationsSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/QueryNavigationsSqliteTest.cs @@ -1,9 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.TestUtilities; -using Xunit; namespace Microsoft.EntityFrameworkCore.Query { @@ -13,47 +11,5 @@ public QueryNavigationsSqliteTest(NorthwindQuerySqliteFixture null; - [ConditionalTheory(Skip = "Issue#17292")] + [ConditionalTheory(Skip = "Issue#17324")] public override Task Project_single_element_from_collection_with_OrderBy_over_navigation_Take_and_FirstOrDefault_2(bool isAsync) { return base.Project_single_element_from_collection_with_OrderBy_over_navigation_Take_and_FirstOrDefault_2(isAsync);