From 111b095117b1a71da807b7b3391443cc615a2eec Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Tue, 14 Mar 2017 18:23:58 -0700 Subject: [PATCH] Query: Refactor SelectExpression for referential integrity - No ad-hoc creation of ColumnExpression. When binding a property to SelectExpression, SelectExpression will generate appropriate expression for it. (BindPropertyToSelectExpression method which binds property even if it is not added to projection) - Removal of non-property ColumnExpression - ColumnExpression represent a column which is directly backed by EntityType.Property. For any other case, a different kind of expression should be used. - AliasExpression requires alias to be defined always. Hence unless column needs to aliased, it will not be made AliasExpression. - Implementation of Equals method of all extension expressions and using ExpressionEqualityComparer to compare expressions in Projection/OrderBy to avoid duplicates. This removes the complex logic in AddToProjection. - Removal of TryGetColumnExpression - All the places, which relied on assumption of having a column backed expression are changed to work with any kind of expression. - Introduction of ColumnReferenceExpression which can store reference to inner projected expression so that each expression knows its root inside the subqueries. - SelectExpression._starProjection stores list of expressions being used by outer SelectExpression. - Simplification of AddToProjection method - Method adds expression straight to projection. Bind method will create the expression to be added from property. Method also unique-fy alias if it is inside subquery. This method also update OrderBy list if a complex expression is getting added to projection and present in orderby by alias-ing it. - Removal of SourceMember/SourceExpression from AliasExpression, (which brought requirement of having every ColumnExpression as AliasExpression in projection). While visiting NewExpression in RelationalProjectionExpressionVisitor, we record SourceExpression to projected expression mapping and then we write MemberInfo to projected expression mapping in SelectExpression. Which is appropriately lifted during PushDownSubquery. This makes it possible for us to bind members after projecting into anonymous types. - Moved RelationalAnnotationsProvider to SelectExpression to find column name so that all the visitors trying to bind property with select expression does not need it to generate column name. --- .../RelationalExpressionExtensions.cs | 43 +- .../EqualityPredicateInExpressionOptimizer.cs | 54 +- .../Internal/IncludeExpressionVisitor.cs | 140 +-- .../IsNullExpressionBuildingVisitor.cs | 69 +- .../Internal/MaterializerFactory.cs | 4 +- ...ationalEntityQueryableExpressionVisitor.cs | 14 +- .../RelationalProjectionExpressionVisitor.cs | 50 +- .../SqlTranslatingExpressionVisitor.cs | 101 +- ...ranslatingExpressionVisitorDependencies.cs | 27 - .../Query/Expressions/AliasExpression.cs | 93 +- .../Query/Expressions/ColumnExpression.cs | 49 +- .../Expressions/ColumnReferenceExpression.cs | 180 +++ .../Query/Expressions/CrossJoinExpression.cs | 43 + .../Expressions/CrossJoinLateralExpression.cs | 43 + .../DiscriminatorPredicateExpression.cs | 53 +- .../Query/Expressions/ExistsExpression.cs | 48 +- .../Expressions/ExplicitCastExpression.cs | 38 + .../Query/Expressions/FromSqlExpression.cs | 47 + .../Query/Expressions/InExpression.cs | 49 +- .../Query/Expressions/InnerJoinExpression.cs | 45 + .../Query/Expressions/IsNullExpression.cs | 32 + .../Expressions/LeftOuterJoinExpression.cs | 49 +- .../Query/Expressions/LikeExpression.cs | 51 +- .../Expressions/NotNullableExpression.cs | 34 +- .../Query/Expressions/NullableExpression.cs | 34 +- .../PropertyParameterExpression.cs | 39 + .../Query/Expressions/SelectExpression.cs | 1085 ++++++++--------- .../SelectExpressionDependencies.cs | 22 +- .../Expressions/SqlFragmentExpression.cs | 32 + .../Expressions/SqlFunctionExpression.cs | 44 + .../Expressions/StringCompareExpression.cs | 46 +- .../Query/Expressions/TableExpression.cs | 47 + .../Query/Expressions/TableExpressionBase.cs | 3 - .../RelationalResultOperatorHandler.cs | 124 +- .../Query/RelationalQueryModelVisitor.cs | 169 ++- .../Query/Sql/DefaultQuerySqlGenerator.cs | 185 ++- .../Query/Sql/ISqlExpressionVisitor.cs | 9 + .../FromSqlNonComposedQuerySqlGenerator.cs | 8 +- .../QueryTestBase.cs | 61 +- .../SqlServerMigrationsSqlGenerator.cs | 3 + .../Internal/RowNumberExpression.cs | 47 +- .../Internal/SqlServerQueryModelVisitor.cs | 75 +- .../Internal/SqlServerQuerySqlGenerator.cs | 11 +- .../Internal/NullConditionalExpression.cs | 59 +- .../Internal/ExpressionEqualityComparer.cs | 17 +- .../ComplexNavigationsQuerySqlServerTest.cs | 2 +- .../GearsOfWarQuerySqlServerTest.cs | 4 +- .../IncludeSqlServerTest.cs | 36 +- .../NullSemanticsQuerySqlServerTest.cs | 20 +- .../QueryNavigationsSqlServerTest.cs | 9 - .../QuerySqlServerTest.cs | 162 ++- .../RowNumberPagingTest.cs | 28 +- 52 files changed, 2221 insertions(+), 1516 deletions(-) create mode 100644 src/EFCore.Relational/Query/Expressions/ColumnReferenceExpression.cs diff --git a/src/EFCore.Relational/Extensions/RelationalExpressionExtensions.cs b/src/EFCore.Relational/Extensions/RelationalExpressionExtensions.cs index 1367defa303..fd599a76bd7 100644 --- a/src/EFCore.Relational/Extensions/RelationalExpressionExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalExpressionExtensions.cs @@ -4,37 +4,42 @@ using System.Linq.Expressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query.Expressions; +using Microsoft.EntityFrameworkCore.Utilities; // ReSharper disable once CheckNamespace namespace Microsoft.EntityFrameworkCore.Internal { public static class RelationalExpressionExtensions { - public static ColumnExpression TryGetColumnExpression([NotNull] this Expression expression) - => expression as ColumnExpression - ?? (expression as AliasExpression)?.TryGetColumnExpression() - ?? (expression as NullableExpression)?.Operand.TryGetColumnExpression(); - - public static bool IsAliasWithColumnExpression([NotNull] this Expression expression) - => (expression as AliasExpression)?.Expression is ColumnExpression; - - public static bool IsAliasWithSelectExpression([NotNull] this Expression expression) - => (expression as AliasExpression)?.Expression is SelectExpression; - - public static bool HasColumnExpression([CanBeNull] this AliasExpression aliasExpression) - => aliasExpression?.Expression is ColumnExpression; - - public static ColumnExpression TryGetColumnExpression([NotNull] this AliasExpression aliasExpression) - => aliasExpression.Expression as ColumnExpression; - public static bool IsSimpleExpression([NotNull] this Expression expression) { + Check.NotNull(expression, nameof(expression)); + var unwrappedExpression = expression.RemoveConvert(); return unwrappedExpression is ConstantExpression || unwrappedExpression is ColumnExpression || unwrappedExpression is ParameterExpression - || unwrappedExpression.IsAliasWithColumnExpression(); + || unwrappedExpression is ColumnReferenceExpression + || unwrappedExpression is AliasExpression; + } + + public static ColumnReferenceExpression LiftExpressionFromSubquery([NotNull] this Expression expression, [NotNull] TableExpressionBase table) + { + Check.NotNull(expression, nameof(expression)); + Check.NotNull(table, nameof(table)); + + switch (expression) + { + case ColumnExpression columnExpression: + return new ColumnReferenceExpression(columnExpression, table); + case AliasExpression aliasExpression: + return new ColumnReferenceExpression(aliasExpression, table); + case ColumnReferenceExpression columnReferenceExpression: + return new ColumnReferenceExpression(columnReferenceExpression, table); + } + + return null; } } -} +} \ No newline at end of file diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/EqualityPredicateInExpressionOptimizer.cs b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/EqualityPredicateInExpressionOptimizer.cs index 01dc67af9b8..d8763a2c8ef 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/EqualityPredicateInExpressionOptimizer.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/EqualityPredicateInExpressionOptimizer.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Query.Expressions; using Microsoft.EntityFrameworkCore.Utilities; using Remotion.Linq.Parsing; @@ -21,16 +20,16 @@ public class EqualityPredicateInExpressionOptimizer : RelinqExpressionVisitor /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - protected override Expression VisitBinary(BinaryExpression node) + protected override Expression VisitBinary(BinaryExpression binaryExpression) { - Check.NotNull(node, nameof(node)); + Check.NotNull(binaryExpression, nameof(binaryExpression)); - switch (node.NodeType) + switch (binaryExpression.NodeType) { case ExpressionType.OrElse: { return Optimize( - node, + binaryExpression, equalityType: ExpressionType.Equal, inExpressionFactory: (c, vs) => new InExpression(c, vs)); } @@ -38,24 +37,23 @@ protected override Expression VisitBinary(BinaryExpression node) case ExpressionType.AndAlso: { return Optimize( - node, + binaryExpression, equalityType: ExpressionType.NotEqual, inExpressionFactory: (c, vs) => Expression.Not(new InExpression(c, vs))); } } - return base.VisitBinary(node); + return base.VisitBinary(binaryExpression); } private Expression Optimize( BinaryExpression binaryExpression, ExpressionType equalityType, - Func, Expression> inExpressionFactory) + Func, Expression> inExpressionFactory) { var leftExpression = Visit(binaryExpression.Left); var rightExpression = Visit(binaryExpression.Right); - Expression leftNonColumnExpression, rightNonColumnExpression; IReadOnlyList leftInValues = null; IReadOnlyList rightInValues = null; @@ -63,26 +61,26 @@ var leftColumnExpression = MatchEqualityExpression( leftExpression, equalityType, - out leftNonColumnExpression); + out Expression leftNonColumnExpression); var rightColumnExpression = MatchEqualityExpression( rightExpression, equalityType, - out rightNonColumnExpression); + out Expression rightNonColumnExpression); if (leftColumnExpression == null) { - leftColumnExpression = ((equalityType == ExpressionType.Equal + leftColumnExpression = equalityType == ExpressionType.Equal ? MatchInExpression(leftExpression, ref leftInValues) - : MatchNotInExpression(leftExpression, ref leftInValues))).TryGetColumnExpression(); + : MatchNotInExpression(leftExpression, ref leftInValues); } if (rightColumnExpression == null) { - rightColumnExpression = ((equalityType == ExpressionType.Equal + rightColumnExpression = equalityType == ExpressionType.Equal ? MatchInExpression(rightExpression, ref rightInValues) - : MatchNotInExpression(rightExpression, ref rightInValues))).TryGetColumnExpression(); + : MatchNotInExpression(rightExpression, ref rightInValues); } if (leftColumnExpression != null @@ -118,7 +116,7 @@ var rightColumnExpression return binaryExpression.Update(leftExpression, binaryExpression.Conversion, rightExpression); } - private static ColumnExpression MatchEqualityExpression( + private static Expression MatchEqualityExpression( Expression expression, ExpressionType equalityType, out Expression nonColumnExpression) @@ -129,16 +127,16 @@ private static ColumnExpression MatchEqualityExpression( if (binaryExpression?.NodeType == equalityType) { - nonColumnExpression - = binaryExpression.Right as ConstantExpression - ?? binaryExpression.Right as ParameterExpression - ?? (Expression)(binaryExpression.Left as ConstantExpression) - ?? binaryExpression.Left as ParameterExpression; + var left = binaryExpression.Left; + var right = binaryExpression.Right; - if (nonColumnExpression != null) + var isLeftConstantOrParameter = left is ConstantExpression || left is ParameterExpression; + + if (isLeftConstantOrParameter || right is ConstantExpression || right is ParameterExpression) { - return binaryExpression.Right.TryGetColumnExpression() - ?? binaryExpression.Left.TryGetColumnExpression(); + nonColumnExpression = isLeftConstantOrParameter ? left : right; + + return isLeftConstantOrParameter ? right : left; } } @@ -149,9 +147,7 @@ private static Expression MatchInExpression( Expression expression, ref IReadOnlyList values) { - var inExpression = expression as InExpression; - - if (inExpression != null) + if (expression is InExpression inExpression) { values = inExpression.Values; @@ -167,8 +163,8 @@ private static Expression MatchNotInExpression( { var unaryExpression = expression as UnaryExpression; - return (unaryExpression != null) - && (unaryExpression.NodeType == ExpressionType.Not) + return unaryExpression != null + && unaryExpression.NodeType == ExpressionType.Not ? MatchInExpression(unaryExpression.Operand, ref values) : null; } diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/IncludeExpressionVisitor.cs b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/IncludeExpressionVisitor.cs index c3cd4902d31..248f51c632b 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/IncludeExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/IncludeExpressionVisitor.cs @@ -156,7 +156,7 @@ var withAccessorMethodInfo .GetTypeInfo() .GetDeclaredMethod(nameof(GroupJoinInclude.WithEntityAccessor)); - var existingGroupJoinIncludeExpression + var existingGroupJoinIncludeExpression = existingGroupJoinIncludeWithAccessor != null && existingGroupJoinIncludeWithAccessor.Method.Equals(withAccessorMethodInfo) ? existingGroupJoinIncludeWithAccessor.Object @@ -266,20 +266,18 @@ var joinExpression var oldPredicate = selectExpression.Predicate; - Dictionary _; var materializer = _materializerFactory .CreateMaterializer( targetEntityType, selectExpression, (p, se) => se.AddToProjection( - new AliasExpression( - new ColumnExpression( - _relationalAnnotationProvider.For(p).ColumnName, - p, - joinedTableExpression))) - valueBufferOffset, + _relationalAnnotationProvider.For(p).ColumnName, + p, + joinedTableExpression, + querySource) - valueBufferOffset, /*querySource:*/ null, - out _); + out var _); if (selectExpression.Predicate != oldPredicate) { @@ -309,8 +307,7 @@ var materializer .CreateReferenceRelatedEntitiesLoaderMethod, Expression.Constant(valueBufferOffset), Expression.Constant(queryIndex), - materializer - ), + materializer), queryContextParameter)); } else @@ -335,9 +332,9 @@ var canGenerateExists { selectExpression .AddToOrderBy( - _relationalAnnotationProvider.For(property).ColumnName, property, principalTable, + querySource, OrderingDirection.Asc); } @@ -352,18 +349,16 @@ var canGenerateExists targetSelectExpression.AddTable(targetTableExpression); - Dictionary _; var materializer = _materializerFactory .CreateMaterializer( targetEntityType, targetSelectExpression, (p, se) => se.AddToProjection( - _relationalAnnotationProvider.For(p).ColumnName, p, querySource), /*querySource:*/ null, - out _); + out var _); if (canGenerateExists) { @@ -396,28 +391,23 @@ var subqueryTable foreach (var ordering in selectExpression.OrderBy) { - // ReSharper disable once PossibleNullReferenceException var principalKeyProperty - = ((ordering.Expression as AliasExpression)?.Expression as ColumnExpression).Property; + = TryGetProperty(ordering.Expression); var referencedForeignKeyProperty = pkPropertiesToFkPropertiesMap[principalKeyProperty]; targetSelectExpression .AddToOrderBy( - _relationalAnnotationProvider.For(referencedForeignKeyProperty).ColumnName, referencedForeignKeyProperty, targetTableExpression, + querySource, ordering.OrderingDirection); } } else { var innerJoinSelectExpression - = selectExpression.Clone( - selectExpression.OrderBy - .Select(o => o.Expression) - .Last(o => o.IsAliasWithColumnExpression()) - .TryGetColumnExpression().TableAlias); + = selectExpression.Clone(principalTable.Alias); innerJoinSelectExpression.ClearProjection(); @@ -490,61 +480,18 @@ private static void LiftOrderBy( SelectExpression targetSelectExpression, TableExpressionBase innerJoinExpression) { - var needOrderingChanges - = innerJoinSelectExpression.OrderBy - .Any(x => x.Expression is SelectExpression - || x.Expression.IsAliasWithColumnExpression() - || x.Expression.IsAliasWithSelectExpression()); - var orderings = innerJoinSelectExpression.OrderBy.ToList(); - if (needOrderingChanges) - { - innerJoinSelectExpression.ClearOrderBy(); - } foreach (var ordering in orderings) { - var orderingExpression = ordering.Expression; - - var aliasExpression = ordering.Expression as AliasExpression; - - if (aliasExpression?.Alias != null) - { - var columnExpression = aliasExpression.TryGetColumnExpression(); - if (columnExpression != null) - { - orderingExpression - = new ColumnExpression( - aliasExpression.Alias, - columnExpression.Property, - columnExpression.Table); - } - } - - var index = orderingExpression is ColumnExpression || orderingExpression.IsAliasWithColumnExpression() - ? innerJoinSelectExpression.AddToProjection(orderingExpression) - : innerJoinSelectExpression.AddToProjection( - new AliasExpression( - innerJoinSelectExpression.Alias + "_" + innerJoinSelectExpression.Projection.Count, - orderingExpression)); - - var expression = innerJoinSelectExpression.Projection[index]; - - if (needOrderingChanges) - { - innerJoinSelectExpression.AddToOrderBy(new Ordering(expression.TryGetColumnExpression() ?? expression, ordering.OrderingDirection)); - } - - var projectedAliasExpression = expression as AliasExpression; - var newExpression = projectedAliasExpression?.Alias != null - ? new ColumnExpression(projectedAliasExpression.Alias, projectedAliasExpression.Type, innerJoinExpression) - : targetSelectExpression.UpdateColumnExpression(expression, innerJoinExpression); - - targetSelectExpression.AddToOrderBy(new Ordering(newExpression, ordering.OrderingDirection)); + targetSelectExpression.AddToOrderBy( + new Ordering( + innerJoinSelectExpression.Projection[innerJoinSelectExpression.AddToProjection(ordering.Expression)] + .LiftExpressionFromSubquery(innerJoinExpression), ordering.OrderingDirection)); } - if ((innerJoinSelectExpression.Limit == null) - && (innerJoinSelectExpression.Offset == null)) + if (innerJoinSelectExpression.Limit == null + && innerJoinSelectExpression.Offset == null) { innerJoinSelectExpression.ClearOrderBy(); } @@ -620,21 +567,14 @@ private Expression BuildColumnExpression( tableExpression); } - var aliasExpressions + var candidates = projections - .OfType() - .Where(p => p.TryGetColumnExpression()?.Property == property) + .Where(p => TryGetProperty(p) == property) .ToList(); - var aliasExpression - = aliasExpressions.Count == 1 - ? aliasExpressions[0] - : aliasExpressions.Last(ae => ae.TryGetColumnExpression().Table.QuerySource == querySource); - - return new ColumnExpression( - aliasExpression.Alias ?? aliasExpression.TryGetColumnExpression().Name, - property, - tableExpression); + return candidates.Count == 1 + ? candidates[0].LiftExpressionFromSubquery(tableExpression) + : candidates.Last(c => TryGetQuerySource(c) == querySource).LiftExpressionFromSubquery(tableExpression); } private static IEnumerable ExtractProjections(TableExpressionBase tableExpression) @@ -659,7 +599,37 @@ private static IEnumerable ExtractProjections(TableExpressionBase ta private static bool IsOrderingOnNonPrincipalKeyProperties( IEnumerable orderings, IReadOnlyList properties) => orderings - .Select(ordering => ((ordering.Expression as AliasExpression)?.Expression as ColumnExpression)?.Property) - .Any(property => !properties.Contains(property)); + .Select(ordering => TryGetProperty(ordering.Expression)) + .Any(property => property == null || !properties.Contains(property)); + + private static IProperty TryGetProperty(Expression expression) + { + switch (expression) + { + case ColumnExpression columnExpression: + return columnExpression.Property; + case AliasExpression aliasExpression: + return TryGetProperty(aliasExpression.Expression); + case ColumnReferenceExpression columnReferenceExpression: + return TryGetProperty(columnReferenceExpression.Expression); + } + + return null; + } + + private IQuerySource TryGetQuerySource(Expression expression) + { + switch (expression) + { + case ColumnExpression columnExpression: + return columnExpression.Table.QuerySource; + case AliasExpression aliasExpression: + return TryGetQuerySource(aliasExpression.Expression); + case ColumnReferenceExpression columnReferenceExpression: + return columnReferenceExpression.Table.QuerySource; + } + + return null; + } } } diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/IsNullExpressionBuildingVisitor.cs b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/IsNullExpressionBuildingVisitor.cs index c293d4857a5..c854e864307 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/IsNullExpressionBuildingVisitor.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/IsNullExpressionBuildingVisitor.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Query.Expressions; using Remotion.Linq.Parsing; @@ -26,34 +25,34 @@ public class IsNullExpressionBuildingVisitor : RelinqExpressionVisitor /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - protected override Expression VisitConstant(ConstantExpression node) + protected override Expression VisitConstant(ConstantExpression constantExpression) { - if (node.Value == null + if (constantExpression.Value == null && !_nullConstantAdded) { - AddToResult(new IsNullExpression(node)); + AddToResult(new IsNullExpression(constantExpression)); _nullConstantAdded = true; } - return node; + return constantExpression; } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - protected override Expression VisitBinary(BinaryExpression node) + protected override Expression VisitBinary(BinaryExpression binaryExpression) { // a ?? b == null <-> a == null && b == null - if (node.NodeType == ExpressionType.Coalesce) + if (binaryExpression.NodeType == ExpressionType.Coalesce) { var current = ResultExpression; ResultExpression = null; - Visit(node.Left); + Visit(binaryExpression.Left); var left = ResultExpression; ResultExpression = null; - Visit(node.Right); + Visit(binaryExpression.Right); var right = ResultExpression; var coalesce = CombineExpressions(left, right, ExpressionType.AndAlso); @@ -65,60 +64,58 @@ protected override Expression VisitBinary(BinaryExpression node) // a && b == null <-> a == null && b != false || a != false && b == null // this transformation would produce a query that is too complex // so we just wrap the whole expression into IsNullExpression instead. - if ((node.NodeType == ExpressionType.AndAlso) - || (node.NodeType == ExpressionType.OrElse)) + if (binaryExpression.NodeType == ExpressionType.AndAlso + || binaryExpression.NodeType == ExpressionType.OrElse) { - AddToResult(new IsNullExpression(node)); + AddToResult(new IsNullExpression(binaryExpression)); } - return node; + return binaryExpression; } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - protected override Expression VisitExtension(Expression node) + protected override Expression VisitExtension(Expression extensionExpression) { - if (node is AliasExpression aliasExpression) + if (extensionExpression is AliasExpression aliasExpression) { return Visit(aliasExpression.Expression); } - var columnExpression = node.TryGetColumnExpression(); - - if (columnExpression != null + if (extensionExpression is ColumnExpression columnExpression && columnExpression.Property.IsNullable) { - AddToResult(new IsNullExpression(node)); + AddToResult(new IsNullExpression(extensionExpression)); - return node; + return extensionExpression; } - if (node is NullableExpression nullableExpression) + if (extensionExpression is NullableExpression nullableExpression) { AddToResult(new IsNullExpression(nullableExpression.Operand)); - return node; + return extensionExpression; } - return node; + return extensionExpression; } /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - protected override Expression VisitConditional(ConditionalExpression node) + protected override Expression VisitConditional(ConditionalExpression conditionalExpression) { var current = ResultExpression; ResultExpression = null; - Visit(node.IfTrue); + Visit(conditionalExpression.IfTrue); var ifTrue = ResultExpression; ResultExpression = null; - Visit(node.IfTrue); + Visit(conditionalExpression.IfTrue); var ifFalse = ResultExpression; ResultExpression = current; @@ -128,30 +125,28 @@ protected override Expression VisitConditional(ConditionalExpression node) // so we just wrap the whole expression into IsNullExpression instead. // // small optimization: expression can only be nullable if either (or both) of the possible results (ifTrue, ifFalse) can be nullable - if ((ifTrue != null) - || (ifFalse != null)) + if (ifTrue != null + || ifFalse != null) { - AddToResult(new IsNullExpression(node)); + AddToResult(new IsNullExpression(conditionalExpression)); } - return node; + return conditionalExpression; } private static Expression CombineExpressions( Expression left, Expression right, ExpressionType expressionType) { - if ((left == null) - && (right == null)) + if (left == null + && right == null) { return null; } - if ((left != null) - && (right != null)) + if (left != null + && right != null) { - return expressionType == ExpressionType.AndAlso - ? Expression.AndAlso(left, right) - : Expression.OrElse(left, right); + return Expression.MakeBinary(expressionType, left, right); } return left ?? right; diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/MaterializerFactory.cs b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/MaterializerFactory.cs index 6ebb9144da4..8b0a049da91 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/MaterializerFactory.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/MaterializerFactory.cs @@ -87,9 +87,7 @@ var materializer var discriminatorProperty = _relationalAnnotationProvider.For(concreteEntityTypes[0]).DiscriminatorProperty; var discriminatorColumn - = selectExpression.Projection - .OfType() - .Last(c => c.TryGetColumnExpression()?.Property == discriminatorProperty); + = selectExpression.Projection.Last(c => (c as ColumnExpression)?.Property == discriminatorProperty); var firstDiscriminatorValue = Expression.Constant( diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/RelationalEntityQueryableExpressionVisitor.cs b/src/EFCore.Relational/Query/ExpressionVisitors/RelationalEntityQueryableExpressionVisitor.cs index 9ca7e54eb0c..ed337c85d3f 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/RelationalEntityQueryableExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/RelationalEntityQueryableExpressionVisitor.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -12,7 +11,6 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.Expressions; -using Microsoft.EntityFrameworkCore.Query.Expressions.Internal; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.ResultOperators.Internal; @@ -105,7 +103,6 @@ protected override Expression VisitMember(MemberExpression node) node, (property, querySource, selectExpression) => selectExpression.AddToProjection( - _relationalAnnotationProvider.For(property).ColumnName, property, querySource), bindSubQueries: true); @@ -129,7 +126,6 @@ protected override Expression VisitMethodCall(MethodCallExpression node) node, (property, querySource, selectExpression) => selectExpression.AddToProjection( - _relationalAnnotationProvider.For(property).ColumnName, property, querySource), bindSubQueries: true); @@ -246,8 +242,6 @@ private Shaper CreateShaper(Type elementType, IEntityType entityType, SelectExpr .QuerySourceRequiresMaterialization(_querySource) || QueryModelVisitor.RequiresClientEval) { - Dictionary typeIndexMap; - var materializer = _materializerFactory .CreateMaterializer( @@ -255,11 +249,10 @@ var materializer selectExpression, (p, se) => se.AddToProjection( - _relationalAnnotationProvider.For(p).ColumnName, p, _querySource), _querySource, - out typeIndexMap).Compile(); + out var typeIndexMap).Compile(); shaper = (Shaper)_createEntityShaperMethodInfo.MakeGenericMethod(elementType) @@ -301,10 +294,9 @@ var discriminatorProperty .For(concreteEntityTypes[0]).DiscriminatorProperty; var discriminatorColumn - = new ColumnExpression( - _relationalAnnotationProvider.For(discriminatorProperty).ColumnName, + = selectExpression.BindPropertyToSelectExpression( discriminatorProperty, - selectExpression.GetTableForQuerySource(querySource)); + querySource); var firstDiscriminatorValue = Expression.Constant( diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/RelationalProjectionExpressionVisitor.cs b/src/EFCore.Relational/Query/ExpressionVisitors/RelationalProjectionExpressionVisitor.cs index d610d4318dc..4a12c2f6247 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/RelationalProjectionExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/RelationalProjectionExpressionVisitor.cs @@ -1,10 +1,9 @@ // 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.Linq; +using System.Collections.Generic; using System.Linq.Expressions; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.Expressions; using Microsoft.EntityFrameworkCore.Query.Internal; @@ -25,6 +24,8 @@ public class RelationalProjectionExpressionVisitor : ProjectionExpressionVisitor private readonly IEntityMaterializerSource _entityMaterializerSource; private readonly IQuerySource _querySource; + private readonly Dictionary _sourceExpressionProjectionMapping = new Dictionary(); + /// /// Creates a new instance of . /// @@ -101,16 +102,19 @@ protected override Expression VisitNew(NewExpression expression) { for (var i = 0; i < expression.Arguments.Count; i++) { - var aliasExpression - = selectExpression.Projection - .OfType() - .SingleOrDefault(ae => ae.SourceExpression == expression.Arguments[i]); + var sourceExpression = expression.Arguments[i]; - if (aliasExpression != null) + if (_sourceExpressionProjectionMapping.ContainsKey(sourceExpression)) { - aliasExpression.SourceMember - = expression.Members?[i] - ?? (expression.Arguments[i] as MemberExpression)?.Member; + var memberInfo = expression.Members?[i] + ?? (expression.Arguments[i] as MemberExpression)?.Member; + + if (memberInfo != null) + { + selectExpression.SetProjectionForMemberInfo( + memberInfo, + _sourceExpressionProjectionMapping[sourceExpression]); + } } } } @@ -162,10 +166,6 @@ var sqlExpression if (!(node is NewExpression)) { - AliasExpression aliasExpression; - - int index; - if (!(node is QuerySourceReferenceExpression)) { if (sqlExpression is NullableExpression nullableExpression) @@ -173,18 +173,11 @@ var sqlExpression sqlExpression = nullableExpression.Operand; } - var columnExpression = sqlExpression.TryGetColumnExpression(); - - if (columnExpression != null) + if (sqlExpression is ColumnExpression) { - index = selectExpression.AddToProjection(sqlExpression); + selectExpression.AddToProjection(sqlExpression); - aliasExpression = selectExpression.Projection[index] as AliasExpression; - - if (aliasExpression != null) - { - aliasExpression.SourceExpression = node; - } + _sourceExpressionProjectionMapping[node] = sqlExpression; return node; } @@ -198,14 +191,9 @@ var targetExpression if (targetExpression.Type == typeof(ValueBuffer)) { - index = selectExpression.AddToProjection(sqlExpression); + var index = selectExpression.AddToProjection(sqlExpression); - aliasExpression = selectExpression.Projection[index] as AliasExpression; - - if (aliasExpression != null) - { - aliasExpression.SourceExpression = node; - } + _sourceExpressionProjectionMapping[node] = sqlExpression; var readValueExpression = _entityMaterializerSource diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitor.cs index 40e81a0d01e..8a0393937c3 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitor.cs @@ -41,7 +41,6 @@ private static readonly Dictionary _inverseOpera { ExpressionType.NotEqual, ExpressionType.Equal } }; - private readonly IRelationalAnnotationProvider _relationalAnnotationProvider; private readonly IExpressionFragmentTranslator _compositeExpressionFragmentTranslator; private readonly IMethodCallTranslator _methodCallTranslator; private readonly IMemberTranslator _memberTranslator; @@ -70,7 +69,6 @@ public SqlTranslatingExpressionVisitor( Check.NotNull(dependencies, nameof(dependencies)); Check.NotNull(queryModelVisitor, nameof(queryModelVisitor)); - _relationalAnnotationProvider = dependencies.RelationalAnnotationProvider; _compositeExpressionFragmentTranslator = dependencies.CompositeExpressionFragmentTranslator; _methodCallTranslator = dependencies.MethodCallTranslator; _memberTranslator = dependencies.MemberTranslator; @@ -130,7 +128,7 @@ protected override Expression VisitBinary(BinaryExpression expression) var right = Visit(expression.Right); return left != null && right != null - ? new AliasExpression(expression.Update(left, expression.Conversion, right)) + ? expression.Update(left, expression.Conversion, right) : null; } @@ -231,7 +229,7 @@ protected override Expression VisitConditional(ConditionalExpression expression) } var test = Visit(expression.Test); - if (test.IsSimpleExpression()) + if (test?.IsSimpleExpression() == true) { test = Expression.Equal(test, Expression.Constant(true, typeof(bool))); } @@ -239,7 +237,6 @@ protected override Expression VisitConditional(ConditionalExpression expression) var ifTrue = Visit(expression.IfTrue); var ifFalse = Visit(expression.IfFalse); - // ReSharper disable once ConditionIsAlwaysTrueOrFalse if (test != null && ifTrue != null && ifFalse != null) @@ -262,8 +259,7 @@ private static Expression Invert(Expression test) { if (test.IsComparisonOperation()) { - var binaryOperation = test as BinaryExpression; - if (binaryOperation != null) + if (test is BinaryExpression binaryOperation) { var nodeType = binaryOperation.NodeType; @@ -360,8 +356,7 @@ public override Expression Visit(Expression node) private void AnalyzeTestExpression(Expression expression) { - var querySourceReferenceExpression = expression as QuerySourceReferenceExpression; - if (querySourceReferenceExpression != null) + if (expression is QuerySourceReferenceExpression querySourceReferenceExpression) { _querySource = querySourceReferenceExpression.ReferencedQuerySource; _propertyName = null; @@ -370,26 +365,22 @@ private void AnalyzeTestExpression(Expression expression) } var memberExpression = expression as MemberExpression; - var querySourceInstance = memberExpression?.Expression as QuerySourceReferenceExpression; - if (querySourceInstance != null) + if (memberExpression?.Expression is QuerySourceReferenceExpression querySourceInstance) { _querySource = querySourceInstance.ReferencedQuerySource; + // ReSharper disable once PossibleNullReferenceException _propertyName = memberExpression.Member.Name; return; } - var methodCallExpression = expression as MethodCallExpression; - if (methodCallExpression != null - && EntityQueryModelVisitor.IsPropertyMethod(methodCallExpression.Method)) + if (expression is MethodCallExpression methodCallExpression && EntityQueryModelVisitor.IsPropertyMethod(methodCallExpression.Method)) { - var querySourceCaller = methodCallExpression.Arguments[0] as QuerySourceReferenceExpression; - if (querySourceCaller != null) + if (methodCallExpression.Arguments[0] is QuerySourceReferenceExpression querySourceCaller) { - var propertyNameExpression = methodCallExpression.Arguments[1] as ConstantExpression; - if (propertyNameExpression != null) + if (methodCallExpression.Arguments[1] is ConstantExpression propertyNameExpression) { _querySource = querySourceCaller.ReferencedQuerySource; _propertyName = (string)propertyNameExpression.Value; @@ -415,8 +406,7 @@ protected override Expression VisitMember(MemberExpression node) { if (node.Member.Name == _propertyName) { - var querySource = node.Expression as QuerySourceReferenceExpression; - if (querySource != null) + if (node.Expression is QuerySourceReferenceExpression querySource) { _canRemoveNullCheck = querySource.ReferencedQuerySource == _querySource; @@ -431,12 +421,9 @@ protected override Expression VisitMethodCall(MethodCallExpression node) { if (EntityQueryModelVisitor.IsPropertyMethod(node.Method)) { - var propertyNameExpression = node.Arguments[1] as ConstantExpression; - if (propertyNameExpression != null - && (string)propertyNameExpression.Value == _propertyName) + if (node.Arguments[1] is ConstantExpression propertyNameExpression && (string)propertyNameExpression.Value == _propertyName) { - var querySource = node.Arguments[0] as QuerySourceReferenceExpression; - if (querySource != null) + if (node.Arguments[0] is QuerySourceReferenceExpression querySource) { _canRemoveNullCheck = querySource.ReferencedQuerySource == _querySource; @@ -563,14 +550,9 @@ private static Expression TransformNullComparison( nonNullExpression = nullableExpression.Operand.RemoveConvert(); } - var columnExpression = nonNullExpression.TryGetColumnExpression(); - - if (columnExpression != null) - { - return expressionType == ExpressionType.Equal - ? (Expression)new IsNullExpression(columnExpression) - : Expression.Not(new IsNullExpression(columnExpression)); - } + return expressionType == ExpressionType.Equal + ? (Expression)new IsNullExpression(nonNullExpression) + : Expression.Not(new IsNullExpression(nonNullExpression)); } } @@ -670,12 +652,7 @@ private Expression TryBindQuerySourcePropertyExpression(MemberExpression memberE { var selectExpression = _queryModelVisitor.TryGetQuery(qsre.ReferencedQuerySource); - if (selectExpression != null) - { - return selectExpression.Projection - .OfType() - .SingleOrDefault(ae => Equals(ae.SourceMember, memberExpression.Member)); - } + return selectExpression?.GetProjectionForMemberInfo(memberExpression.Member); } return null; @@ -685,17 +662,15 @@ private Expression TryBindMemberOrMethodToAliasExpression( TExpression sourceExpression, Func, Expression> binder) { - AliasExpression CreateAliasExpression( + Expression BindPropertyToSelectExpression( IProperty property, IQuerySource querySource, SelectExpression selectExpression) - => new AliasExpression( - new ColumnExpression( - _relationalAnnotationProvider.For(property).ColumnName, + => selectExpression.BindPropertyToSelectExpression( property, - selectExpression.GetTableForQuerySource(querySource))); + querySource); var boundExpression = binder(sourceExpression, _queryModelVisitor, (property, querySource, selectExpression) => { - var aliasExpression = CreateAliasExpression(property, querySource, selectExpression); + var aliasExpression = BindPropertyToSelectExpression(property, querySource, selectExpression); if (_targetSelectExpression != null && selectExpression != _targetSelectExpression) { @@ -716,7 +691,7 @@ AliasExpression CreateAliasExpression( while (outerQueryModelVisitor != null && canBindToOuterQueryModelVisitor) { - boundExpression = binder(sourceExpression, outerQueryModelVisitor, CreateAliasExpression); + boundExpression = binder(sourceExpression, outerQueryModelVisitor, BindPropertyToSelectExpression); if (boundExpression != null) { @@ -964,9 +939,7 @@ protected override Expression VisitParameter(ParameterExpression expression) /// protected override Expression VisitExtension(Expression expression) { - var stringCompare = expression as StringCompareExpression; - - if (stringCompare != null) + if (expression is StringCompareExpression stringCompare) { var newLeft = Visit(stringCompare.Left); var newRight = Visit(stringCompare.Right); @@ -983,9 +956,7 @@ protected override Expression VisitExtension(Expression expression) : expression; } - var explicitCast = expression as ExplicitCastExpression; - - if (explicitCast != null) + if (expression is ExplicitCastExpression explicitCast) { var newOperand = Visit(explicitCast.Operand); @@ -994,10 +965,7 @@ protected override Expression VisitExtension(Expression expression) : expression; } - var nullConditionalExpression - = expression as NullConditionalExpression; - - if (nullConditionalExpression != null) + if (expression is NullConditionalExpression nullConditionalExpression) { var newAccessOperation = Visit(nullConditionalExpression.AccessOperation); @@ -1028,10 +996,7 @@ protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpr if (!_inProjection) { - var joinClause - = expression.ReferencedQuerySource as JoinClause; - - if (joinClause != null) + if (expression.ReferencedQuerySource is JoinClause joinClause) { var entityType = _queryModelVisitor.QueryCompilationContext.Model @@ -1058,22 +1023,10 @@ var entityType { var subquery = selectExpression.Tables.FirstOrDefault() as SelectExpression; - var innerProjectionExpression = subquery?.Projection.FirstOrDefault() as AliasExpression; + var innerProjectionExpression = subquery?.Projection.FirstOrDefault(); if (innerProjectionExpression != null) { - if (innerProjectionExpression.Alias != null) - { - return new ColumnExpression( - innerProjectionExpression.Alias, - innerProjectionExpression.Type, - subquery); - } - - var newExpression = selectExpression.UpdateColumnExpression(innerProjectionExpression.Expression, subquery); - return new AliasExpression(newExpression) - { - SourceMember = innerProjectionExpression.SourceMember - }; + return innerProjectionExpression.LiftExpressionFromSubquery(subquery); } } } diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitorDependencies.cs b/src/EFCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitorDependencies.cs index 1ee1cec9cdf..23af760da02 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitorDependencies.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitorDependencies.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query.ExpressionTranslators; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; @@ -45,36 +44,27 @@ public sealed class SqlTranslatingExpressionVisitorDependencies /// the constructor at any point in this process. /// /// - /// The relational annotation provider. /// The composite expression fragment translator. /// The method call translator. /// The member translator. /// The relational type mapper. public SqlTranslatingExpressionVisitorDependencies( - [NotNull] IRelationalAnnotationProvider relationalAnnotationProvider, [NotNull] IExpressionFragmentTranslator compositeExpressionFragmentTranslator, [NotNull] IMethodCallTranslator methodCallTranslator, [NotNull] IMemberTranslator memberTranslator, [NotNull] IRelationalTypeMapper relationalTypeMapper) { - Check.NotNull(relationalAnnotationProvider, nameof(relationalAnnotationProvider)); Check.NotNull(compositeExpressionFragmentTranslator, nameof(compositeExpressionFragmentTranslator)); Check.NotNull(methodCallTranslator, nameof(methodCallTranslator)); Check.NotNull(memberTranslator, nameof(memberTranslator)); Check.NotNull(relationalTypeMapper, nameof(relationalTypeMapper)); - RelationalAnnotationProvider = relationalAnnotationProvider; CompositeExpressionFragmentTranslator = compositeExpressionFragmentTranslator; MethodCallTranslator = methodCallTranslator; MemberTranslator = memberTranslator; RelationalTypeMapper = relationalTypeMapper; } - /// - /// The relational annotation provider. - /// - public IRelationalAnnotationProvider RelationalAnnotationProvider { get; } - /// /// The composite expression fragment translator. /// @@ -95,19 +85,6 @@ public SqlTranslatingExpressionVisitorDependencies( /// public IRelationalTypeMapper RelationalTypeMapper { get; } - /// - /// Clones this dependency parameter object with one service replaced. - /// - /// A replacement for the current dependency of this type. - /// A new parameter object with the given service replaced. - public SqlTranslatingExpressionVisitorDependencies With([NotNull] IRelationalAnnotationProvider relationalAnnotationProvider) - => new SqlTranslatingExpressionVisitorDependencies( - relationalAnnotationProvider, - CompositeExpressionFragmentTranslator, - MethodCallTranslator, - MemberTranslator, - RelationalTypeMapper); - /// /// Clones this dependency parameter object with one service replaced. /// @@ -115,7 +92,6 @@ public SqlTranslatingExpressionVisitorDependencies With([NotNull] IRelationalAnn /// A new parameter object with the given service replaced. public SqlTranslatingExpressionVisitorDependencies With([NotNull] IExpressionFragmentTranslator compositeExpressionFragmentTranslator) => new SqlTranslatingExpressionVisitorDependencies( - RelationalAnnotationProvider, compositeExpressionFragmentTranslator, MethodCallTranslator, MemberTranslator, @@ -128,7 +104,6 @@ public SqlTranslatingExpressionVisitorDependencies With([NotNull] IExpressionFra /// A new parameter object with the given service replaced. public SqlTranslatingExpressionVisitorDependencies With([NotNull] IMethodCallTranslator methodCallTranslator) => new SqlTranslatingExpressionVisitorDependencies( - RelationalAnnotationProvider, CompositeExpressionFragmentTranslator, methodCallTranslator, MemberTranslator, @@ -141,7 +116,6 @@ public SqlTranslatingExpressionVisitorDependencies With([NotNull] IMethodCallTra /// A new parameter object with the given service replaced. public SqlTranslatingExpressionVisitorDependencies With([NotNull] IMemberTranslator memberTranslator) => new SqlTranslatingExpressionVisitorDependencies( - RelationalAnnotationProvider, CompositeExpressionFragmentTranslator, MethodCallTranslator, memberTranslator, @@ -154,7 +128,6 @@ public SqlTranslatingExpressionVisitorDependencies With([NotNull] IMemberTransla /// A new parameter object with the given service replaced. public SqlTranslatingExpressionVisitorDependencies With([NotNull] IRelationalTypeMapper relationalTypeMapper) => new SqlTranslatingExpressionVisitorDependencies( - RelationalAnnotationProvider, CompositeExpressionFragmentTranslator, MethodCallTranslator, MemberTranslator, diff --git a/src/EFCore.Relational/Query/Expressions/AliasExpression.cs b/src/EFCore.Relational/Query/Expressions/AliasExpression.cs index e030d03c8dc..377e43dcd2c 100644 --- a/src/EFCore.Relational/Query/Expressions/AliasExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/AliasExpression.cs @@ -3,7 +3,6 @@ using System; using System.Linq.Expressions; -using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query.Sql; using Microsoft.EntityFrameworkCore.Utilities; @@ -17,30 +16,16 @@ public class AliasExpression : Expression { private readonly Expression _expression; - private string _alias; - - private Expression _sourceExpression; - - /// - /// Creates a new instance of an AliasExpression. - /// - /// The expression being aliased. - public AliasExpression([NotNull] Expression expression) - { - Check.NotNull(expression, nameof(expression)); - - _expression = expression; - } - - // TODO: Revisit the design here, "alias" should really be required. + private readonly string _alias; /// /// Creates a new instance of an AliasExpression. /// /// The alias. /// The expression being aliased. - public AliasExpression([CanBeNull] string alias, [NotNull] Expression expression) + public AliasExpression([NotNull] string alias, [NotNull] Expression expression) { + Check.NotEmpty(alias, nameof(alias)); Check.NotNull(expression, nameof(expression)); _alias = alias; @@ -48,39 +33,18 @@ public AliasExpression([CanBeNull] string alias, [NotNull] Expression expression } /// - /// Gets or sets the alias. + /// Gets the alias. /// /// /// The alias. /// - public virtual string Alias - { - get { return _alias; } - [param: NotNull] - // TODO: Remove mutability here - set - { - Check.NotNull(value, nameof(value)); - - _alias = value; - } - } + public virtual string Alias => _alias; /// /// The expression being aliased. /// public virtual Expression Expression => _expression; - // TODO: Revisit why we need this. Try and remove - - /// - /// Gets or sets a value indicating whether the expression is being projected. - /// - /// - /// true if projected, false if not. - /// - public virtual bool IsProjected { get; set; } = false; - /// /// Returns the node type of this . (Inherited from .) /// @@ -93,32 +57,6 @@ public virtual string Alias /// The that represents the static type of the expression. public override Type Type => _expression.Type; - /// - /// Gets or sets the source expression. - /// - /// - /// The source expression. - /// - public virtual Expression SourceExpression - { - get { return _sourceExpression; } - [param: NotNull] - set - { - Check.NotNull(value, nameof(value)); - - _sourceExpression = value; - } - } - - /// - /// Gets or sets the source member. - /// - /// - /// The source member. - /// - public virtual MemberInfo SourceMember { get; [param: CanBeNull] set; } - /// /// Dispatches to the specific visit method for this node type. /// @@ -155,13 +93,6 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) : this; } - /// - /// Creates a representation of the Expression. - /// - /// A representation of the Expression. - public override string ToString() - => Alias != null ? "(" + _expression + ") AS " + Alias : _expression.ToString(); - /// /// Tests if this object is considered equal to another. /// @@ -186,8 +117,8 @@ public override bool Equals(object obj) } private bool Equals(AliasExpression other) - => Equals(_expression, other._expression) - && string.Equals(_alias, other._alias); + => string.Equals(_alias, other._alias) + && Equals(_expression, other._expression); /// /// Returns a hash code for this object. @@ -199,9 +130,15 @@ public override int GetHashCode() { unchecked { - // ReSharper disable once NonReadonlyMemberInGetHashCode - return (_expression.GetHashCode() * 397) ^ (_alias?.GetHashCode() ?? 0); + return (_expression.GetHashCode() * 397) ^ _alias.GetHashCode(); } } + + /// + /// Creates a representation of the Expression. + /// + /// A representation of the Expression. + public override string ToString() + => Alias != null ? "(" + _expression + ") AS " + Alias : _expression.ToString(); } } diff --git a/src/EFCore.Relational/Query/Expressions/ColumnExpression.cs b/src/EFCore.Relational/Query/Expressions/ColumnExpression.cs index c295c7d605c..bee6ce80d2c 100644 --- a/src/EFCore.Relational/Query/Expressions/ColumnExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/ColumnExpression.cs @@ -28,28 +28,13 @@ public ColumnExpression( [NotNull] string name, [NotNull] IProperty property, [NotNull] TableExpressionBase tableExpression) - : this(name, Check.NotNull(property, nameof(property)).ClrType, tableExpression) - { - _property = property; - } - - /// - /// Creates a new instance of a ColumnExpression. - /// - /// The column name. - /// The column type. - /// The target table expression. - public ColumnExpression( - [NotNull] string name, - [NotNull] Type type, - [NotNull] TableExpressionBase tableExpression) { Check.NotEmpty(name, nameof(name)); - Check.NotNull(type, nameof(type)); + Check.NotNull(property, nameof(property)); Check.NotNull(tableExpression, nameof(tableExpression)); Name = name; - Type = type; + _property = property; _tableExpression = tableExpression; } @@ -58,11 +43,6 @@ public ColumnExpression( /// public virtual TableExpressionBase Table => _tableExpression; - /// - /// The target table alias. - /// - public virtual string TableAlias => _tableExpression.Alias; - #pragma warning disable 108 /// @@ -89,7 +69,7 @@ public ColumnExpression( /// Gets the static type of the expression that this represents. (Inherited from .) /// /// The that represents the static type of the expression. - public override Type Type { get; } + public override Type Type => _property.ClrType; /// /// Dispatches to the specific visit method for this node type. @@ -120,12 +100,6 @@ protected override Expression Accept(ExpressionVisitor visitor) /// protected override Expression VisitChildren(ExpressionVisitor visitor) => this; - private bool Equals([NotNull] ColumnExpression other) - => ((_property == null && other._property == null && Name == other.Name) - || (_property != null && _property.Equals(other._property))) - && Type == other.Type - && _tableExpression.Equals(other._tableExpression); - /// /// Tests if this object is considered equal to another. /// @@ -133,7 +107,7 @@ private bool Equals([NotNull] ColumnExpression other) /// /// true if the objects are considered equal, false if they are not. /// - public override bool Equals([CanBeNull] object obj) + public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { @@ -145,10 +119,16 @@ public override bool Equals([CanBeNull] object obj) return true; } - return (obj.GetType() == GetType()) + return obj.GetType() == GetType() && Equals((ColumnExpression)obj); } + private bool Equals([NotNull] ColumnExpression other) + // Compare on names only because multiple properties can map to same column in inheritance scenario + => string.Equals(Name, other.Name) + && Type == other.Type + && Equals(Table, other.Table); + /// /// Returns a hash code for this object. /// @@ -159,8 +139,11 @@ public override int GetHashCode() { unchecked { - return (_property.GetHashCode() * 397) - ^ _tableExpression.GetHashCode(); + var hashCode = Type.GetHashCode(); + hashCode = (hashCode * 397) ^ _tableExpression.GetHashCode(); + hashCode = (hashCode * 397) ^ Name.GetHashCode(); + + return hashCode; } } diff --git a/src/EFCore.Relational/Query/Expressions/ColumnReferenceExpression.cs b/src/EFCore.Relational/Query/Expressions/ColumnReferenceExpression.cs new file mode 100644 index 00000000000..13be5355a9e --- /dev/null +++ b/src/EFCore.Relational/Query/Expressions/ColumnReferenceExpression.cs @@ -0,0 +1,180 @@ +// 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.Linq.Expressions; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Query.Sql; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.Query.Expressions +{ + /// + /// A column reference expression. + /// + public class ColumnReferenceExpression : Expression + { + private readonly Expression _expression; + private readonly TableExpressionBase _tableExpression; + + /// + /// Creates a new instance of a ColumnReferenceExpression. + /// + /// The referenced AliasExpression. + /// The target table expression. + public ColumnReferenceExpression( + [NotNull] AliasExpression aliasExpression, + [NotNull] TableExpressionBase tableExpression) + : this( + Check.NotNull(aliasExpression, nameof(aliasExpression)).Alias, + aliasExpression, + Check.NotNull(tableExpression, nameof(tableExpression))) + { + } + + /// + /// Creates a new instance of a ColumnReferenceExpression. + /// + /// The referenced ColumnExpression. + /// The target table expression. + public ColumnReferenceExpression( + [NotNull] ColumnExpression columnExpression, + [NotNull] TableExpressionBase tableExpression) + : this( + Check.NotNull(columnExpression, nameof(columnExpression)).Name, + columnExpression, + Check.NotNull(tableExpression, nameof(tableExpression))) + { + } + + /// + /// Creates a new instance of a ColumnReferenceExpression. + /// + /// The referenced ColumnReferenceExpression. + /// The target table expression. + public ColumnReferenceExpression( + [NotNull] ColumnReferenceExpression columnReferenceExpression, + [NotNull] TableExpressionBase tableExpression) + : this( + Check.NotNull(columnReferenceExpression, nameof(columnReferenceExpression)).Name, + columnReferenceExpression, + Check.NotNull(tableExpression, nameof(tableExpression))) + { + } + + private ColumnReferenceExpression(string name, Expression expression, TableExpressionBase tableExpression) + { + Name = name; + _expression = expression; + _tableExpression = tableExpression; + } + + /// + /// The target table. + /// + public virtual TableExpressionBase Table => _tableExpression; + + /// + /// The referenced expression. + /// + public virtual Expression Expression => _expression; + + /// + /// Gets the column name. + /// + /// + /// The column name. + /// + public virtual string Name { get; } + + /// + /// Gets the static type of the expression that this represents. (Inherited from .) + /// + /// The that represents the static type of the expression. + public override Type Type => _expression.Type; + + /// + /// Returns the node type of this . (Inherited from .) + /// + /// The that represents this expression. + public override ExpressionType NodeType => ExpressionType.Extension; + + /// + /// Dispatches to the specific visit method for this node type. + /// + protected override Expression Accept(ExpressionVisitor visitor) + { + Check.NotNull(visitor, nameof(visitor)); + + var specificVisitor = visitor as ISqlExpressionVisitor; + + return specificVisitor != null + ? specificVisitor.VisitColumnReference(this) + : base.Accept(visitor); + } + + /// + /// Reduces the node and then calls the method passing the + /// reduced expression. + /// Throws an exception if the node isn't reducible. + /// + /// An instance of . + /// The expression being visited, or an expression which should replace it in the tree. + /// + /// Override this method to provide logic to walk the node's children. + /// A typical implementation will call visitor.Visit on each of its + /// children, and if any of them change, should return a new copy of + /// itself with the modified children. + /// + protected override Expression VisitChildren(ExpressionVisitor visitor) => this; + + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((ColumnReferenceExpression)obj); + } + + private bool Equals([NotNull] ColumnReferenceExpression other) + => Equals(_expression, other._expression) + && Equals(_tableExpression, other._tableExpression); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = _expression.GetHashCode(); + hashCode = (hashCode * 397) ^ _tableExpression.GetHashCode(); + + return hashCode; + } + } + + /// + /// Creates a representation of the Expression. + /// + /// A representation of the Expression. + public override string ToString() => _tableExpression.Alias + "." + Name; + } +} diff --git a/src/EFCore.Relational/Query/Expressions/CrossJoinExpression.cs b/src/EFCore.Relational/Query/Expressions/CrossJoinExpression.cs index 12d258122a5..8b19263fd8f 100644 --- a/src/EFCore.Relational/Query/Expressions/CrossJoinExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/CrossJoinExpression.cs @@ -36,6 +36,49 @@ protected override Expression Accept(ExpressionVisitor visitor) : base.Accept(visitor); } + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((CrossJoinExpression)obj); + } + + private bool Equals(CrossJoinExpression other) + => string.Equals(Alias, other.Alias) + && Equals(QuerySource, other.QuerySource); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = Alias?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (QuerySource?.GetHashCode() ?? 0); + + return hashCode; + } + } + /// /// Creates a representation of the Expression. /// diff --git a/src/EFCore.Relational/Query/Expressions/CrossJoinLateralExpression.cs b/src/EFCore.Relational/Query/Expressions/CrossJoinLateralExpression.cs index b09af26fb88..cd792cd590e 100644 --- a/src/EFCore.Relational/Query/Expressions/CrossJoinLateralExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/CrossJoinLateralExpression.cs @@ -36,6 +36,49 @@ protected override Expression Accept(ExpressionVisitor visitor) : base.Accept(visitor); } + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((CrossJoinLateralExpression)obj); + } + + private bool Equals(CrossJoinLateralExpression other) + => string.Equals(Alias, other.Alias) + && Equals(QuerySource, other.QuerySource); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = Alias?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (QuerySource?.GetHashCode() ?? 0); + + return hashCode; + } + } + /// /// Creates a representation of the Expression. /// diff --git a/src/EFCore.Relational/Query/Expressions/DiscriminatorPredicateExpression.cs b/src/EFCore.Relational/Query/Expressions/DiscriminatorPredicateExpression.cs index a1b69d05b99..e4292f6fb24 100644 --- a/src/EFCore.Relational/Query/Expressions/DiscriminatorPredicateExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/DiscriminatorPredicateExpression.cs @@ -65,12 +65,6 @@ public DiscriminatorPredicateExpression( /// The reduced expression. public override Expression Reduce() => _predicate; - /// - /// Creates a representation of the Expression. - /// - /// A representation of the Expression. - public override string ToString() => _predicate.ToString(); - /// /// Reduces the node and then calls the method passing the /// reduced expression. @@ -92,5 +86,52 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) ? new DiscriminatorPredicateExpression(newPredicate, QuerySource) : this; } + + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((DiscriminatorPredicateExpression)obj); + } + + private bool Equals(DiscriminatorPredicateExpression other) + { + return Equals(_predicate, other._predicate) && Equals(QuerySource, other.QuerySource); + } + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + return (_predicate.GetHashCode() * 397) ^ (QuerySource?.GetHashCode() ?? 0); + } + } + + /// + /// Creates a representation of the Expression. + /// + /// A representation of the Expression. + public override string ToString() => _predicate.ToString(); } } diff --git a/src/EFCore.Relational/Query/Expressions/ExistsExpression.cs b/src/EFCore.Relational/Query/Expressions/ExistsExpression.cs index b5c4b95bba1..646d8d71392 100644 --- a/src/EFCore.Relational/Query/Expressions/ExistsExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/ExistsExpression.cs @@ -17,12 +17,12 @@ public class ExistsExpression : Expression /// /// Creates a new instance of a ExistsExpression.. /// - /// The subquery operand of the EXISTS expression. - public ExistsExpression([NotNull] Expression expression) + /// The subquery operand of the EXISTS expression. + public ExistsExpression([NotNull] SelectExpression subquery) { - Check.NotNull(expression, nameof(expression)); + Check.NotNull(subquery, nameof(subquery)); - Expression = expression; + Subquery = subquery; } /// @@ -31,7 +31,7 @@ public ExistsExpression([NotNull] Expression expression) /// /// The subquery operand of the EXISTS expression. /// - public virtual Expression Expression { get; } + public virtual SelectExpression Subquery { get; } /// /// Returns the node type of this . (Inherited from .) @@ -74,11 +74,43 @@ protected override Expression Accept(ExpressionVisitor visitor) /// protected override Expression VisitChildren(ExpressionVisitor visitor) { - var expression = visitor.Visit(Expression); + var subquery = (SelectExpression)visitor.Visit(Subquery); - return expression != Expression - ? new ExistsExpression(expression) + return subquery != Subquery + ? new ExistsExpression(subquery) : this; } + + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((ExistsExpression)obj); + } + + private bool Equals(ExistsExpression other) => Equals(Subquery, other.Subquery); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() => Subquery.GetHashCode(); } } diff --git a/src/EFCore.Relational/Query/Expressions/ExplicitCastExpression.cs b/src/EFCore.Relational/Query/Expressions/ExplicitCastExpression.cs index 7e2df757923..bdef2973e7b 100644 --- a/src/EFCore.Relational/Query/Expressions/ExplicitCastExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/ExplicitCastExpression.cs @@ -86,6 +86,44 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) : this; } + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((ExplicitCastExpression)obj); + } + + private bool Equals(ExplicitCastExpression other) => _type == other._type && Equals(Operand, other.Operand); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + return (_type.GetHashCode() * 397) ^ Operand.GetHashCode(); + } + } + /// /// Creates a representation of the Expression. /// diff --git a/src/EFCore.Relational/Query/Expressions/FromSqlExpression.cs b/src/EFCore.Relational/Query/Expressions/FromSqlExpression.cs index e3f3a0a8083..5ceabba314f 100644 --- a/src/EFCore.Relational/Query/Expressions/FromSqlExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/FromSqlExpression.cs @@ -67,6 +67,53 @@ protected override Expression Accept(ExpressionVisitor visitor) : base.Accept(visitor); } + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((FromSqlExpression)obj); + } + + private bool Equals(FromSqlExpression other) + => string.Equals(Alias, other.Alias) + && Equals(QuerySource, other.QuerySource) + && string.Equals(Sql, other.Sql) + && Equals(Arguments, other.Arguments); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = Alias?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (QuerySource?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ Sql.GetHashCode(); + hashCode = (hashCode * 397) ^ Arguments.GetHashCode(); + + return hashCode; + } + } + /// /// Creates a representation of the Expression. /// diff --git a/src/EFCore.Relational/Query/Expressions/InExpression.cs b/src/EFCore.Relational/Query/Expressions/InExpression.cs index b12fe954084..1bd458752f6 100644 --- a/src/EFCore.Relational/Query/Expressions/InExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/InExpression.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query.Sql; @@ -150,11 +151,57 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) : this; } + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((InExpression)obj); + } + + private bool Equals(InExpression other) + => Operand.Equals(other.Operand) + && Values.SequenceEqual(other.Values) + && SubQuery.Equals(other.SubQuery); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = Operand.GetHashCode(); + hashCode = (hashCode * 397) ^ (Values != null + ? Values.Aggregate(0, (current, value) => current + ((current * 397) ^ value.GetHashCode())) + : 0); + hashCode = (hashCode * 397) ^ (SubQuery?.GetHashCode() ?? 0); + return hashCode; + } + } + /// /// Creates a representation of the Expression. /// /// A representation of the Expression. public override string ToString() - => Operand + " IN (" + string.Join(", ", Values) + ")"; + => Operand + " IN (" + (Values != null ? string.Join(", ", Values) : SubQuery.ToString()) + ")"; } } diff --git a/src/EFCore.Relational/Query/Expressions/InnerJoinExpression.cs b/src/EFCore.Relational/Query/Expressions/InnerJoinExpression.cs index 4d50c52557e..59532798a35 100644 --- a/src/EFCore.Relational/Query/Expressions/InnerJoinExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/InnerJoinExpression.cs @@ -36,6 +36,51 @@ protected override Expression Accept(ExpressionVisitor visitor) : base.Accept(visitor); } + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((InnerJoinExpression)obj); + } + + private bool Equals(InnerJoinExpression other) + => string.Equals(Alias, other.Alias) + && Equals(QuerySource, other.QuerySource) + && Equals(Predicate, other.Predicate); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = Alias?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (QuerySource?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (Predicate?.GetHashCode() ?? 0); + + return hashCode; + } + } + /// /// Creates a representation of the Expression. /// diff --git a/src/EFCore.Relational/Query/Expressions/IsNullExpression.cs b/src/EFCore.Relational/Query/Expressions/IsNullExpression.cs index 5c50f472ddd..72ab9aef5ab 100644 --- a/src/EFCore.Relational/Query/Expressions/IsNullExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/IsNullExpression.cs @@ -78,6 +78,38 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) : this; } + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((IsNullExpression)obj); + } + + private bool Equals(IsNullExpression other) => Equals(_operand, other._operand); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() => _operand.GetHashCode(); + /// /// Creates a representation of the Expression. /// diff --git a/src/EFCore.Relational/Query/Expressions/LeftOuterJoinExpression.cs b/src/EFCore.Relational/Query/Expressions/LeftOuterJoinExpression.cs index 783c25c13c4..4e39a565e83 100644 --- a/src/EFCore.Relational/Query/Expressions/LeftOuterJoinExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/LeftOuterJoinExpression.cs @@ -1,14 +1,10 @@ // 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.Linq; using System.Linq.Expressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query.Sql; using Microsoft.EntityFrameworkCore.Utilities; -using Remotion.Linq.Clauses; -using Remotion.Linq.Clauses.Expressions; -using Remotion.Linq.Clauses.ResultOperators; namespace Microsoft.EntityFrameworkCore.Query.Expressions { @@ -40,6 +36,51 @@ protected override Expression Accept(ExpressionVisitor visitor) : base.Accept(visitor); } + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((LeftOuterJoinExpression)obj); + } + + private bool Equals(LeftOuterJoinExpression other) + => string.Equals(Alias, other.Alias) + && Equals(QuerySource, other.QuerySource) + && Equals(Predicate, other.Predicate); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = Alias?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (QuerySource?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (Predicate?.GetHashCode() ?? 0); + + return hashCode; + } + } + /// /// Creates a representation of the Expression. /// diff --git a/src/EFCore.Relational/Query/Expressions/LikeExpression.cs b/src/EFCore.Relational/Query/Expressions/LikeExpression.cs index 36f11b0e343..64b6f200b72 100644 --- a/src/EFCore.Relational/Query/Expressions/LikeExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/LikeExpression.cs @@ -114,13 +114,58 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) var newPatternExpression = visitor.Visit(Pattern); var newEscapeCharExpression = EscapeChar == null ? null : visitor.Visit(EscapeChar); - return (newMatchExpression != Match) - || (newPatternExpression != Pattern) - || (newEscapeCharExpression != EscapeChar) + return newMatchExpression != Match + || newPatternExpression != Pattern + || newEscapeCharExpression != EscapeChar ? new LikeExpression(newMatchExpression, newPatternExpression, newEscapeCharExpression) : this; } + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((LikeExpression)obj); + } + + private bool Equals(LikeExpression other) + => Equals(Match, other.Match) + && Equals(Pattern, other.Pattern) + && Equals(EscapeChar, other.EscapeChar); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = Match.GetHashCode(); + hashCode = (hashCode * 397) ^ Pattern.GetHashCode(); + hashCode = (hashCode * 397) ^ (EscapeChar?.GetHashCode() ?? 0); + + return hashCode; + } + } + /// /// Creates a representation of the Expression. /// diff --git a/src/EFCore.Relational/Query/Expressions/NotNullableExpression.cs b/src/EFCore.Relational/Query/Expressions/NotNullableExpression.cs index 68686438565..f435df8d273 100644 --- a/src/EFCore.Relational/Query/Expressions/NotNullableExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/NotNullableExpression.cs @@ -24,7 +24,7 @@ public NotNullableExpression([NotNull] Expression operand) Check.NotNull(operand, nameof(operand)); _operand = operand; - Type = _operand.Type?.UnwrapNullableType(); + Type = _operand.Type.UnwrapNullableType(); } /// @@ -70,5 +70,37 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) /// /// The reduced expression. public override Expression Reduce() => _operand; + + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((NotNullableExpression)obj); + } + + private bool Equals([NotNull] NotNullableExpression other) => Equals(_operand, other._operand); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() => _operand.GetHashCode(); } } diff --git a/src/EFCore.Relational/Query/Expressions/NullableExpression.cs b/src/EFCore.Relational/Query/Expressions/NullableExpression.cs index 795fa3b4ecb..63a1b21e013 100644 --- a/src/EFCore.Relational/Query/Expressions/NullableExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/NullableExpression.cs @@ -24,7 +24,7 @@ public NullableExpression([NotNull] Expression operand) Check.NotNull(operand, nameof(operand)); _operand = operand; - Type = _operand.Type?.MakeNullable(); + Type = _operand.Type.MakeNullable(); } /// @@ -70,5 +70,37 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) /// /// The reduced expression. public override Expression Reduce() => _operand; + + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((NullableExpression)obj); + } + + private bool Equals([NotNull] NullableExpression other) => Equals(_operand, other._operand); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() => _operand.GetHashCode(); } } diff --git a/src/EFCore.Relational/Query/Expressions/PropertyParameterExpression.cs b/src/EFCore.Relational/Query/Expressions/PropertyParameterExpression.cs index f1105aa1b49..6f51c190524 100644 --- a/src/EFCore.Relational/Query/Expressions/PropertyParameterExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/PropertyParameterExpression.cs @@ -99,5 +99,44 @@ protected override Expression Accept(ExpressionVisitor visitor) ? specificVisitor.VisitPropertyParameter(this) : base.Accept(visitor); } + + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((PropertyParameterExpression)obj); + } + + private bool Equals(PropertyParameterExpression other) + => string.Equals(Name, other.Name) && Equals(Property, other.Property); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + return (Name.GetHashCode() * 397) ^ Property.GetHashCode(); + } + } } } diff --git a/src/EFCore.Relational/Query/Expressions/SelectExpression.cs b/src/EFCore.Relational/Query/Expressions/SelectExpression.cs index f5a150566ee..ec14c5af3b3 100644 --- a/src/EFCore.Relational/Query/Expressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/SelectExpression.cs @@ -5,9 +5,11 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.Sql; using Microsoft.EntityFrameworkCore.Utilities; using Remotion.Linq.Clauses; @@ -19,23 +21,27 @@ namespace Microsoft.EntityFrameworkCore.Query.Expressions /// public class SelectExpression : TableExpressionBase { - private const string SystemAliasPrefix = "t"; + private const string SubqueryAliasPrefix = "t"; + private const string ColumnAliasPrefix = "c"; #if DEBUG internal string DebugView => ToString(); #endif + private static readonly ExpressionEqualityComparer _expressionEqualityComparer = new ExpressionEqualityComparer(); private readonly RelationalQueryCompilationContext _queryCompilationContext; + private readonly IRelationalAnnotationProvider _relationalAnnotationProvider; private readonly List _projection = new List(); private readonly List _tables = new List(); private readonly List _orderBy = new List(); + private readonly Dictionary _memberInfoProjectionMapping = new Dictionary(); + private readonly List _starProjection = new List(); private Expression _limit; private Expression _offset; private TableExpressionBase _projectStarTable; - private int _subqueryDepth = -1; - private bool _isDistinct; + private bool _isProjectStar; /// /// Creates a new instance of SelectExpression. @@ -51,6 +57,7 @@ public SelectExpression( Check.NotNull(queryCompilationContext, nameof(queryCompilationContext)); Dependencies = dependencies; + _relationalAnnotationProvider = dependencies.RelationalAnnotationProvider; _queryCompilationContext = queryCompilationContext; } @@ -69,6 +76,7 @@ public SelectExpression( Check.NotNull(alias, nameof(alias)); // When assigning alias to select expression make it unique + // ReSharper disable once VirtualMemberCallInConstructor Alias = queryCompilationContext.CreateUniqueTableAlias(alias); } @@ -94,7 +102,8 @@ public SelectExpression( public virtual TableExpressionBase ProjectStarTable { get { return _projectStarTable ?? (_tables.Count == 1 ? _tables.Single() : null); } - [param: CanBeNull] set { _projectStarTable = value; } + [param: CanBeNull] + set { _projectStarTable = value; } } /// @@ -104,39 +113,6 @@ public virtual TableExpressionBase ProjectStarTable ? _projection[0].Type : base.Type; - /// - /// Makes a copy of this SelectExpression. - /// - /// The alias. - /// - /// A copy of this SelectExpression. - /// - public virtual SelectExpression Clone([CanBeNull] string alias = null) - { - var selectExpression - = new SelectExpression(Dependencies, _queryCompilationContext) - { - _limit = _limit, - _offset = _offset, - _isDistinct = _isDistinct, - _subqueryDepth = _subqueryDepth, - IsProjectStar = IsProjectStar, - Predicate = Predicate - }; - - if (alias != null) - { - selectExpression.Alias = _queryCompilationContext.CreateUniqueTableAlias(alias); - } - - selectExpression._projection.AddRange(_projection); - - selectExpression.AddTables(_tables); - selectExpression.AddToOrderBy(_orderBy); - - return selectExpression; - } - /// /// The tables making up the FROM part of the SELECT expression. /// @@ -148,130 +124,24 @@ var selectExpression /// /// true if this SelectExpression is project star, false if not. /// - public virtual bool IsProjectStar { get; set; } - - /// - /// Determines whether this SelectExpression is an identity query. An identity query - /// has a single table, and returns all of the rows from that table, unmodified. - /// - /// - /// true if this SelectExpression is an identity query, false if not. - /// - public virtual bool IsIdentityQuery() - => !IsProjectStar - && !IsDistinct - && Predicate == null - && Limit == null - && Offset == null - && Projection.Count == 0 - && OrderBy.Count == 0 - && Tables.Count == 1; - - /// - /// Adds a table to this SelectExpression. - /// - /// The table expression. - public virtual void AddTable([NotNull] TableExpressionBase tableExpression) - { - Check.NotNull(tableExpression, nameof(tableExpression)); - - _tables.Add(tableExpression); - } - - /// - /// Adds tables to this SelectExprssion. - /// - /// The table expressions. - private void AddTables([NotNull] IEnumerable tableExpressions) - { - Check.NotNull(tableExpressions, nameof(tableExpressions)); - - foreach (var tableExpression in tableExpressions.ToList()) - { - AddTable(tableExpression); - } - } - - /// - /// Removes any tables added to this SelectExpression. - /// - public virtual void ClearTables() => _tables.Clear(); - - /// - /// Determines if this SelectExpression contains any correlated subqueries. - /// - /// - /// true if correlated, false if not. - /// - public virtual bool IsCorrelated() => new CorrelationFindingExpressionVisitor().IsCorrelated(this); - - private class CorrelationFindingExpressionVisitor : ExpressionVisitor + public virtual bool IsProjectStar { - private SelectExpression _selectExpression; - private bool _correlated; - - public bool IsCorrelated(SelectExpression selectExpression) + get => _isProjectStar; + set { - _selectExpression = selectExpression; - - Visit(_selectExpression); - - return _correlated; - } + _isProjectStar = value; - public override Expression Visit(Expression expression) - { - if (!_correlated) + if (value) { - var columnExpression = expression as ColumnExpression; - - if (columnExpression?.Table.QuerySource != null - && !_selectExpression.HandlesQuerySource(columnExpression.Table.QuerySource)) - { - _correlated = true; - } - else - { - return base.Visit(expression); - } + _starProjection.AddRange(_projection); + } + else + { + _starProjection.Clear(); } - - return expression; } } - /// - /// Determines whether or not this SelectExpression handles the given query source. - /// - /// The query source. - /// - /// true if the supplied query source is handled by this SelectExpression; otherwise false. - /// - public override bool HandlesQuerySource(IQuerySource querySource) - { - Check.NotNull(querySource, nameof(querySource)); - - var processedQuerySource = PreProcessQuerySource(querySource); - - return _tables.Any(te => te.QuerySource == processedQuerySource || te.HandlesQuerySource(processedQuerySource)) - || base.HandlesQuerySource(querySource); - } - - /// - /// Gets the table corresponding to the supplied query source. - /// - /// The query source. - /// - /// The table for query source. - /// - public virtual TableExpressionBase GetTableForQuerySource([NotNull] IQuerySource querySource) - { - Check.NotNull(querySource, nameof(querySource)); - - return _tables.FirstOrDefault(te => te.QuerySource == querySource || te.HandlesQuerySource(querySource)) - ?? ProjectStarTable; - } - /// /// Gets or sets a value indicating whether this SelectExpression is DISTINCT. /// @@ -280,12 +150,16 @@ public virtual TableExpressionBase GetTableForQuerySource([NotNull] IQuerySource /// public virtual bool IsDistinct { - get { return _isDistinct; } + get => _isDistinct; set { if (_offset != null) { PushDownSubquery(); + } + + if (value && _orderBy.Any(o => !_projection.Contains(o.Expression, _expressionEqualityComparer))) + { ClearOrderBy(); } @@ -301,13 +175,13 @@ public virtual bool IsDistinct /// public virtual Expression Limit { - get { return _limit; } + get => _limit; [param: CanBeNull] set { - if (value != null) + if (value != null && _limit != null) { - PushDownIfLimit(); + PushDownSubquery(); } _limit = value; @@ -322,34 +196,126 @@ public virtual Expression Limit /// public virtual Expression Offset { - get { return _offset; } + get => _offset; [param: CanBeNull] set { if (_limit != null && value != null) { - var subquery = PushDownSubquery(); + PushDownSubquery(); } _offset = value; } } - private void PushDownIfLimit() + /// + /// The projection of this SelectExpression. + /// + public virtual IReadOnlyList Projection => _projection; + + /// + /// The SQL ORDER BY of this SelectExpression. + /// + public virtual IReadOnlyList OrderBy => _orderBy; + + /// + /// Makes a copy of this SelectExpression. + /// + /// The alias. + /// + /// A copy of this SelectExpression. + /// + public virtual SelectExpression Clone([CanBeNull] string alias = null) { - if (_limit != null) + var selectExpression + = new SelectExpression(Dependencies, _queryCompilationContext) + { + _limit = _limit, + _offset = _offset, + _isDistinct = _isDistinct, + Predicate = Predicate, + ProjectStarTable = ProjectStarTable, + IsProjectStar = IsProjectStar + }; + + if (alias != null) { - PushDownSubquery(); + selectExpression.Alias = _queryCompilationContext.CreateUniqueTableAlias(alias); } + + selectExpression._tables.AddRange(_tables); + selectExpression._projection.AddRange(_projection); + selectExpression._orderBy.AddRange(_orderBy); + + return selectExpression; } - private void PushDownIfDistinct() + /// + /// Clears all elements of this SelectExpression. + /// + public virtual void Clear() { - if (_isDistinct) - { - PushDownSubquery(); - } + _tables.Clear(); + _projection.Clear(); + _starProjection.Clear(); + _orderBy.Clear(); + _limit = null; + _offset = null; + _isDistinct = false; + Predicate = null; + ProjectStarTable = null; + IsProjectStar = false; + } + + /// + /// Determines whether this SelectExpression is an identity query. An identity query + /// has a single table, and returns all of the rows from that table, unmodified. + /// + /// + /// true if this SelectExpression is an identity query, false if not. + /// + public virtual bool IsIdentityQuery() + => !IsProjectStar + && !IsDistinct + && Predicate == null + && Limit == null + && Offset == null + && Projection.Count == 0 + && OrderBy.Count == 0 + && Tables.Count == 1; + + /// + /// Determines whether or not this SelectExpression handles the given query source. + /// + /// The query source. + /// + /// true if the supplied query source is handled by this SelectExpression; otherwise false. + /// + public override bool HandlesQuerySource(IQuerySource querySource) + { + Check.NotNull(querySource, nameof(querySource)); + + var processedQuerySource = PreProcessQuerySource(querySource); + + return _tables.Any(te => te.QuerySource == processedQuerySource || te.HandlesQuerySource(processedQuerySource)) + || base.HandlesQuerySource(querySource); + } + + /// + /// Gets the table corresponding to the supplied query source. + /// + /// The query source. + /// + /// The table for query source. + /// + public virtual TableExpressionBase GetTableForQuerySource([NotNull] IQuerySource querySource) + { + Check.NotNull(querySource, nameof(querySource)); + + return _tables.FirstOrDefault(te => te.QuerySource == querySource || te.HandlesQuerySource(querySource)) + ?? ProjectStarTable; } /// @@ -361,63 +327,82 @@ private void PushDownIfDistinct() /// public virtual SelectExpression PushDownSubquery() { - _subqueryDepth++; - - var subquery = new SelectExpression(Dependencies, _queryCompilationContext, SystemAliasPrefix); - - var columnAliasCounter = 0; + var subquery = new SelectExpression(Dependencies, _queryCompilationContext, SubqueryAliasPrefix); foreach (var expression in _projection) { - var aliasExpression = expression as AliasExpression; + var expressionToAdd = expression; - if (aliasExpression != null) + switch (expressionToAdd) { - var columnExpression = aliasExpression.TryGetColumnExpression(); + case AliasExpression aliasExpression: + { + expressionToAdd = new AliasExpression( + subquery.CreateUniqueProjectionAlias(aliasExpression.Alias, useColumnAliasPrefix: true), + aliasExpression.Expression); + + break; + } + case ColumnExpression columnExpression: + { + var uniqueAlias = subquery.CreateUniqueProjectionAlias(columnExpression.Name, useColumnAliasPrefix: true); + + if (!string.Equals(columnExpression.Name, uniqueAlias, StringComparison.OrdinalIgnoreCase)) + { + expressionToAdd = new AliasExpression(uniqueAlias, columnExpression); + } + + break; + } + + case ColumnReferenceExpression columnReferenceExpression: + { + var uniqueAlias = subquery.CreateUniqueProjectionAlias(columnReferenceExpression.Name, useColumnAliasPrefix: true); + + if (!string.Equals(columnReferenceExpression.Name, uniqueAlias, StringComparison.OrdinalIgnoreCase)) + { + expressionToAdd = new AliasExpression(uniqueAlias, columnReferenceExpression); + } - if ((columnExpression != null - && subquery._projection.OfType() - .Any(ae => (ae.Alias ?? ae.TryGetColumnExpression()?.Name) == (aliasExpression.Alias ?? columnExpression.Name))) - || columnExpression == null) + break; + } + default: { - aliasExpression.Alias = "c" + columnAliasCounter++; + expressionToAdd = new AliasExpression(subquery.CreateUniqueProjectionAlias(ColumnAliasPrefix), expression); + break; } } - else + + var memberInfo = _memberInfoProjectionMapping.FirstOrDefault(kvp => _expressionEqualityComparer.Equals(kvp.Value, expression)).Key; + + if (memberInfo != null) { - aliasExpression = new AliasExpression("c" + columnAliasCounter++, expression); + _memberInfoProjectionMapping[memberInfo] = expressionToAdd.LiftExpressionFromSubquery(subquery); } - subquery._projection.Add(aliasExpression); + subquery._projection.Add(expressionToAdd); } - subquery.AddTables(_tables); - subquery.AddToOrderBy(_orderBy); + subquery._tables.AddRange(_tables); + subquery._orderBy.AddRange(_orderBy); subquery.Predicate = Predicate; subquery._limit = _limit; subquery._offset = _offset; subquery._isDistinct = _isDistinct; - subquery._subqueryDepth = _subqueryDepth; subquery.ProjectStarTable = ProjectStarTable; subquery.IsProjectStar = IsProjectStar || !subquery._projection.Any(); - _limit = null; - _offset = null; - _isDistinct = false; - ProjectStarTable = null; - - Predicate = null; - - ClearTables(); - ClearProjection(); - ClearOrderBy(); + Clear(); _tables.Add(subquery); ProjectStarTable = subquery; - foreach (var ordering in subquery.OrderBy) + IsProjectStar = true; + + // This code is to preserve the ordering in the result when we add extra ordering like we do for grouping/include + foreach (var ordering in subquery.OrderBy.ToList()) { var expression = ordering.Expression; @@ -426,38 +411,11 @@ public virtual SelectExpression PushDownSubquery() expression = nullableExpression.Operand; } - var aliasExpression = expression as AliasExpression; - if (aliasExpression != null) - { - if (aliasExpression.Alias != null) - { - _orderBy.Add( - new Ordering( - new ColumnExpression(aliasExpression.Alias, aliasExpression.Type, subquery), - ordering.OrderingDirection)); - } - else - { - var newExpression = UpdateColumnExpression(aliasExpression.Expression, subquery); - - _orderBy.Add( - new Ordering( - new AliasExpression(newExpression), ordering.OrderingDirection)); - } - } - else - { - if (!subquery.IsProjectStar) - { - subquery.AddToProjection(expression); - } - - var newExpression = UpdateColumnExpression(expression, subquery); + var expressionToAdd + = expression.LiftExpressionFromSubquery(subquery) + ?? subquery.Projection[subquery.AddToProjection(expression, resetProjectStar: false)].LiftExpressionFromSubquery(subquery); - _orderBy.Add( - new Ordering( - new AliasExpression(newExpression), ordering.OrderingDirection)); - } + _orderBy.Add(new Ordering(expressionToAdd, ordering.OrderingDirection)); } if (subquery.Limit == null @@ -470,51 +428,96 @@ public virtual SelectExpression PushDownSubquery() } /// - /// The projection of this SelectExpression. + /// Adds a table to this SelectExpression. /// - public virtual IReadOnlyList Projection => _projection; + /// The table expression. + public virtual void AddTable([NotNull] TableExpressionBase tableExpression) + { + Check.NotNull(tableExpression, nameof(tableExpression)); + + _tables.Add(tableExpression); + } /// - /// Adds a column to the projection. + /// Removes a table from this SelectExpression. + /// + /// The table expression. + public virtual void RemoveTable([NotNull] TableExpressionBase tableExpression) + { + Check.NotNull(tableExpression, nameof(tableExpression)); + + _tables.Remove(tableExpression); + } + + /// + /// Removes any tables added to this SelectExpression. + /// + public virtual void ClearTables() => _tables.Clear(); + + /// + /// Generates an expression bound to this select expression for the supplied property. /// - /// The column name. /// The corresponding EF property. /// The originating query source. /// - /// The corresponding index of the added expression in . + /// The bound expression which can be used to refer column from this select expression. /// - public virtual int AddToProjection( - [NotNull] string column, + public virtual Expression BindPropertyToSelectExpression( [NotNull] IProperty property, [NotNull] IQuerySource querySource) { - Check.NotEmpty(column, nameof(column)); Check.NotNull(property, nameof(property)); Check.NotNull(querySource, nameof(querySource)); - var projectionIndex = GetProjectionIndex(property, querySource); + var table = GetTableForQuerySource(querySource); + + if (table is JoinExpressionBase joinTable) + { + table = joinTable.TableExpression; + } + + var projectedExpressionToSearch = table is SelectExpression subquerySelectExpression + ? (Expression)subquerySelectExpression.BindPropertyToSelectExpression(property, querySource) + .LiftExpressionFromSubquery(table) + : new ColumnExpression(_relationalAnnotationProvider.For(property).ColumnName, property, table); - if (projectionIndex == -1) + return IsProjectStar + ? GetOrAddToStarProjection(projectedExpressionToSearch) + : (_projection.Find(e => _expressionEqualityComparer.Equals(e, projectedExpressionToSearch)) ?? projectedExpressionToSearch); + } + + private Expression GetOrAddToStarProjection([NotNull] Expression expression) + { + var projectedExpression = _starProjection.Find(e => _expressionEqualityComparer.Equals(e, expression)); + + if (projectedExpression != null) { - projectionIndex = AddToProjection( - new ColumnExpression( - column, - property, - GetTableForQuerySource(querySource))); + return projectedExpression; } - return projectionIndex; + _starProjection.Add(expression); + + return expression; } /// - /// Adds an expression to the projection. + /// Adds a column to the projection. /// - /// The expression. + /// The corresponding EF property. + /// The originating query source. /// /// The corresponding index of the added expression in . /// - public virtual int AddToProjection([NotNull] Expression expression) - => AddToProjection(expression, true); + public virtual int AddToProjection( + [NotNull] IProperty property, + [NotNull] IQuerySource querySource) + { + Check.NotNull(property, nameof(property)); + Check.NotNull(querySource, nameof(querySource)); + + return AddToProjection( + BindPropertyToSelectExpression(property, querySource)); + } /// /// Adds an expression to the projection. @@ -524,7 +527,7 @@ public virtual int AddToProjection([NotNull] Expression expression) /// /// The corresponding index of the added expression in . /// - public virtual int AddToProjection([NotNull] Expression expression, bool resetProjectStar) + public virtual int AddToProjection([NotNull] Expression expression, bool resetProjectStar = true) { Check.NotNull(expression, nameof(expression)); @@ -539,147 +542,80 @@ public virtual int AddToProjection([NotNull] Expression expression, bool resetPr } } - var columnExpression = expression as ColumnExpression; - - if (columnExpression != null) - { - return AddToProjection(columnExpression); - } - - var aliasExpression = expression as AliasExpression; + var projectionIndex + = _projection.FindIndex(e => _expressionEqualityComparer.Equals(e, expression)); - if (aliasExpression != null) + if (projectionIndex != -1) { - return AddToProjection(aliasExpression); + return projectionIndex; } - _projection.Add(expression); - - if (resetProjectStar) + if (!(expression is ColumnExpression || expression is ColumnReferenceExpression)) { - IsProjectStar = false; - } - - return _projection.Count - 1; - } - - /// - /// Adds an to the projection. - /// - /// The alias expression. - /// - /// The corresponding index of the added expression in . - /// - public virtual int AddToProjection([NotNull] AliasExpression aliasExpression) - { - Check.NotNull(aliasExpression, nameof(aliasExpression)); - - var alias = aliasExpression.Alias; - var expression = aliasExpression.Expression; - var columnExpression = expression as ColumnExpression; - - var projectionIndex - = _projection - .FindIndex(e => - { - var ae = e as AliasExpression; - var ce = e.TryGetColumnExpression(); + var indexInOrderBy = _orderBy.FindIndex(o => _expressionEqualityComparer.Equals(o.Expression, expression)); - return (ce != null - && columnExpression != null - && ce.Name == columnExpression.Name - && ce.TableAlias == columnExpression.TableAlias) - || ae?.Expression == expression; - }); - - if (projectionIndex == -1) - { - // Alias != null means SelectExpression in subquery which needs projections to have unique aliases - if (Alias != null) + if (indexInOrderBy != -1) { - var currentAlias = alias ?? columnExpression?.Name ?? expression.NodeType.ToString(); - var uniqueAlias = CreateUniqueProjectionAlias(currentAlias); + expression = new AliasExpression(CreateUniqueProjectionAlias(ColumnAliasPrefix), expression); + var updatedOrdering = new Ordering(expression, _orderBy[indexInOrderBy].OrderingDirection); - if (columnExpression == null - || !string.Equals(currentAlias, uniqueAlias, StringComparison.OrdinalIgnoreCase)) - { - alias = uniqueAlias; - } + _orderBy.RemoveAt(indexInOrderBy); + _orderBy.Insert(indexInOrderBy, updatedOrdering); } + } - projectionIndex = _projection.Count; - - if (alias != null) + // Alias != null means SelectExpression in subquery which needs projections to have unique aliases + if (Alias != null) + { + switch (expression) { - foreach (var orderByAliasExpression - in _orderBy.Select(o => o.Expression).OfType()) + case ColumnExpression columnExpression: { - if (orderByAliasExpression.TryGetColumnExpression() == null) - { - // TODO: This seems bad - if (orderByAliasExpression.Expression.ToString() == expression.ToString()) - { - orderByAliasExpression.Alias = alias; - orderByAliasExpression.IsProjected = true; - } - } - } - } - - _projection.Add(new AliasExpression(alias, expression)); + var currentAlias = columnExpression.Name; + var uniqueAlias = CreateUniqueProjectionAlias(currentAlias); - IsProjectStar = false; - } - - return projectionIndex; - } - - /// - /// Adds a ColumnExpression to the projection. - /// - /// The column expression. - /// - /// The corresponding index of the added expression in . - /// - public virtual int AddToProjection([NotNull] ColumnExpression columnExpression) - { - Check.NotNull(columnExpression, nameof(columnExpression)); + expression + = !string.Equals(currentAlias, uniqueAlias, StringComparison.OrdinalIgnoreCase) + ? (Expression)new AliasExpression(uniqueAlias, columnExpression) + : columnExpression; - var projectionIndex - = _projection - .FindIndex(e => + break; + } + case AliasExpression aliasExpression: { - var ce = e.TryGetColumnExpression(); + var currentAlias = aliasExpression.Alias; + var uniqueAlias = CreateUniqueProjectionAlias(currentAlias); - return ce != null - && ce.Name == columnExpression.Name - && ce.TableAlias == columnExpression.TableAlias; - }); + if (!string.Equals(currentAlias, uniqueAlias, StringComparison.OrdinalIgnoreCase)) + { + expression = new AliasExpression(uniqueAlias, aliasExpression.Expression); + } - if (projectionIndex == -1) - { - var aliasExpression = new AliasExpression(columnExpression); + break; + } + case ColumnReferenceExpression columnReferenceExpression: + { + var currentAlias = columnReferenceExpression.Name; + var uniqueAlias = CreateUniqueProjectionAlias(currentAlias); - // Alias != null means SelectExpression in subquery which needs projections to have unique aliases - if (Alias != null) - { - var currentAlias = columnExpression.Name; - var uniqueAlias = CreateUniqueProjectionAlias(currentAlias); + expression + = !string.Equals(currentAlias, uniqueAlias, StringComparison.OrdinalIgnoreCase) + ? (Expression)new AliasExpression(uniqueAlias, columnReferenceExpression) + : columnReferenceExpression; - if (!string.Equals(currentAlias, uniqueAlias, StringComparison.OrdinalIgnoreCase)) - { - aliasExpression.Alias = uniqueAlias; + break; } } + } - projectionIndex = _projection.Count; - - _projection.Add(aliasExpression); + _projection.Add(expression); + if (resetProjectStar) + { IsProjectStar = false; } - return projectionIndex; + return _projection.Count - 1; } /// @@ -703,19 +639,6 @@ public virtual IEnumerable GetProjectionTypes() return _tables.OfType().SelectMany(e => e.GetProjectionTypes()); } - /// - /// Sets a as the single projected expression - /// in this SelectExpression. - /// - /// The conditional expression. - public virtual void SetProjectionConditionalExpression([NotNull] ConditionalExpression conditionalExpression) - { - Check.NotNull(conditionalExpression, nameof(conditionalExpression)); - - ClearProjection(); - AddToProjection(conditionalExpression); - } - /// /// Sets an expression as the single projected expression in this SelectExpression. /// @@ -724,8 +647,10 @@ public virtual void SetProjectionExpression([NotNull] Expression expression) { Check.NotNull(expression, nameof(expression)); - PushDownIfLimit(); - PushDownIfDistinct(); + if (_limit != null || _isDistinct) + { + PushDownSubquery(); + } ClearProjection(); AddToProjection(expression); @@ -734,31 +659,7 @@ public virtual void SetProjectionExpression([NotNull] Expression expression) /// /// Clears the projection. /// - public virtual void ClearProjection() - { - _projection.Clear(); - IsProjectStar = true; - } - - /// - /// Clears the column expressions from the projection. - /// - public virtual void ClearColumnProjections() - { - for (var i = _projection.Count - 1; i >= 0; i--) - { - var aliasExpression = _projection[i] as AliasExpression; - if (aliasExpression?.Expression is ColumnExpression) - { - _projection.RemoveAt(i); - } - } - - if (_projection.Count == 0) - { - IsProjectStar = true; - } - } + public virtual void ClearProjection() => _projection.Clear(); /// /// Removes a range from the projection. @@ -772,18 +673,6 @@ public virtual void RemoveRangeFromProjection(int index) } } - /// - /// Removes expressions from the projection corresponding to the - /// supplied expressions. - /// - /// The Orderings to remove from the projection. - public virtual void RemoveFromProjection([NotNull] IEnumerable orderBy) - { - Check.NotNull(orderBy, nameof(orderBy)); - - _projection.RemoveAll(ce => orderBy.Any(o => ReferenceEquals(o.Expression, ce))); - } - /// /// Computes the index in corresponding to the supplied property and query source. /// @@ -793,95 +682,119 @@ public virtual void RemoveFromProjection([NotNull] IEnumerable orderBy /// The projection index. /// public virtual int GetProjectionIndex( - [NotNull] IProperty property, [NotNull] IQuerySource querySource) + [NotNull] IProperty property, + [NotNull] IQuerySource querySource) { Check.NotNull(property, nameof(property)); Check.NotNull(querySource, nameof(querySource)); - var table = GetTableForQuerySource(querySource); + var projectedExpressionToSearch = BindPropertyToSelectExpression(property, querySource); return _projection - .FindIndex(e => - { - var ce = e.TryGetColumnExpression(); - - return ce?.Property == property - && ce.TableAlias == table.Alias; - }); + .FindIndex(e => _expressionEqualityComparer.Equals(e, projectedExpressionToSearch)); } /// - /// Adds a column to the ORDER BY of this SelectExpression. + /// Transforms the projection of this SelectExpression by expanding the wildcard ('*') projection + /// into individual explicit projection expressions. /// - /// The column name. - /// The corresponding EF property. - /// The target table. - /// The ordering direction. - /// - /// An AliasExpression corresponding to the expression added to the ORDER BY. - /// - public virtual AliasExpression AddToOrderBy( - [NotNull] string column, - [NotNull] IProperty property, - [NotNull] TableExpressionBase table, - OrderingDirection orderingDirection) + public virtual void ExplodeStarProjection() { - Check.NotEmpty(column, nameof(column)); - Check.NotNull(property, nameof(property)); - Check.NotNull(table, nameof(table)); + if (IsProjectStar) + { + var subquery = (SelectExpression)_tables.Single(); + + foreach (var projectedExpression in subquery._projection) + { + _projection.Add(projectedExpression.LiftExpressionFromSubquery(subquery)); + } + + IsProjectStar = false; + } + } + + private string CreateUniqueProjectionAlias(string currentAlias, bool useColumnAliasPrefix = false) + { + var uniqueAlias = currentAlias ?? ColumnAliasPrefix; - var columnExpression = new ColumnExpression(column, property, table); - var aliasExpression = new AliasExpression(columnExpression); + if (useColumnAliasPrefix) + { + currentAlias = ColumnAliasPrefix; + } + + var counter = 0; - if (_orderBy.FindIndex(o => o.Expression.TryGetColumnExpression()?.Equals(columnExpression) ?? false) == -1) + while (_projection.Select(e => e is ColumnExpression ce ? ce.Name : (e is ColumnReferenceExpression cre ? cre.Name : (e is AliasExpression ae ? ae.Alias : null))) + .Any(p => string.Equals(p, uniqueAlias, StringComparison.OrdinalIgnoreCase))) { - _orderBy.Add(new Ordering(aliasExpression, orderingDirection)); + uniqueAlias = currentAlias + counter++; } - return aliasExpression; + return uniqueAlias; } /// - /// Adds multiple expressions to the ORDER BY of this SelectExpression. + /// Gets the projection corresponding to supplied member info. /// - /// The orderings expressions. - public virtual void AddToOrderBy([NotNull] IEnumerable orderings) + /// The corresponding member info. + /// + /// The projection. + /// + public virtual Expression GetProjectionForMemberInfo([NotNull] MemberInfo memberInfo) { - Check.NotNull(orderings, nameof(orderings)); + Check.NotNull(memberInfo, nameof(memberInfo)); - foreach (var ordering in orderings) - { - var aliasExpression = ordering.Expression as AliasExpression; - var columnExpression = ordering.Expression as ColumnExpression; + return _memberInfoProjectionMapping.ContainsKey(memberInfo) + ? _memberInfoProjectionMapping[memberInfo] + : null; + } - if (aliasExpression != null) - { - var newAlias = new AliasExpression(aliasExpression.Alias, aliasExpression.Expression); - _orderBy.Add(new Ordering(newAlias, ordering.OrderingDirection)); - } - else if (columnExpression != null) - { - _orderBy.Add(new Ordering(new AliasExpression(columnExpression), ordering.OrderingDirection)); - } - else - { - _orderBy.Add(ordering); - } - } + /// + /// Sets the supplied expression as the projection for the supplied member info. + /// + /// The member info. + /// The corresponding projection. + public virtual void SetProjectionForMemberInfo([NotNull] MemberInfo memberInfo, [NotNull] Expression projection) + { + Check.NotNull(memberInfo, nameof(memberInfo)); + Check.NotNull(projection, nameof(projection)); + + _memberInfoProjectionMapping[memberInfo] = projection; + } + + /// + /// Adds a predicate expression to this SelectExpression, combining it with + /// any existing predicate if necessary. + /// + /// The predicate expression to add. + public virtual void AddToPredicate([NotNull] Expression predicate) + { + Check.NotNull(predicate, nameof(predicate)); + + Predicate = Predicate != null ? AndAlso(Predicate, predicate) : predicate; } /// /// Adds a single to the order by. /// /// The ordering. - public virtual void AddToOrderBy([NotNull] Ordering ordering) + /// + /// The ordering added to select expression. + /// + public virtual Ordering AddToOrderBy([NotNull] Ordering ordering) { Check.NotNull(ordering, nameof(ordering)); - if (_orderBy.FindIndex(o => o.Expression.Equals(ordering.Expression)) == -1) + var existingOrdering = _orderBy.Find(o => _expressionEqualityComparer.Equals(o.Expression, ordering.Expression) && o.OrderingDirection == ordering.OrderingDirection); + + if (existingOrdering != null) { - _orderBy.Add(ordering); + return existingOrdering; } + + _orderBy.Add(ordering); + + return ordering; } /// @@ -903,64 +816,11 @@ public virtual void PrependToOrderBy([NotNull] IEnumerable orderings) } } - /// - /// The SQL ORDER BY of this SelectExpression. - /// - public virtual IReadOnlyList OrderBy => _orderBy; - /// /// Clears the ORDER BY of this SelectExpression. /// public virtual void ClearOrderBy() => _orderBy.Clear(); - /// - /// Removes a range from list of order by elements. - /// - /// Zero-based index of the start of the range to remove. - public virtual void RemoveRangeFromOrderBy(int index) - { - if (index < _orderBy.Count) - { - _orderBy.RemoveRange(index, _orderBy.Count - index); - } - } - - /// - /// Transforms the projection of this SelectExpression by expanding the wildcard ('*') projection - /// into individual explicit projection expressions. - /// - public virtual void ExplodeStarProjection() - { - if (IsProjectStar) - { - var subquery = (SelectExpression)_tables.Single(); - - foreach (var aliasExpression in subquery._projection.Cast()) - { - if (aliasExpression.Alias != null) - { - _projection.Add( - new ColumnExpression( - aliasExpression.Alias, - aliasExpression.Type, - subquery)); - } - else - { - var expression = UpdateColumnExpression(aliasExpression.Expression, subquery); - - _projection.Add( - new AliasExpression(expression) - { - SourceMember = aliasExpression.SourceMember - }); - } - } - - IsProjectStar = false; - } - } - /// /// Adds a SQL CROSS JOIN to this SelectExpression. /// @@ -1070,42 +930,47 @@ public virtual PredicateJoinExpressionBase AddLeftOuterJoin( return outerJoinExpression; } - private string CreateUniqueProjectionAlias(string currentAlias) + /// + /// Determines if this SelectExpression contains any correlated subqueries. + /// + /// + /// true if correlated, false if not. + /// + public virtual bool IsCorrelated() => new CorrelationFindingExpressionVisitor().IsCorrelated(this); + + private class CorrelationFindingExpressionVisitor : ExpressionVisitor { - var uniqueAlias = currentAlias ?? "A"; - var counter = 0; + private SelectExpression _selectExpression; + private bool _correlated; - while (_projection - .OfType() - .Any(p => string.Equals(p.Alias ?? p.TryGetColumnExpression()?.Name, uniqueAlias, StringComparison.OrdinalIgnoreCase))) + public bool IsCorrelated(SelectExpression selectExpression) { - uniqueAlias = currentAlias + counter++; - } + _selectExpression = selectExpression; - return uniqueAlias; - } + Visit(_selectExpression); - /// - /// Removes a table from this SelectExpression. - /// - /// The table expression. - public virtual void RemoveTable([NotNull] TableExpressionBase tableExpression) - { - Check.NotNull(tableExpression, nameof(tableExpression)); + return _correlated; + } - _tables.Remove(tableExpression); - } + public override Expression Visit(Expression expression) + { + if (!_correlated) + { + var columnExpression = expression as ColumnExpression; - /// - /// Adds a predicate expression to this SelectExpression, combining it with - /// any existing predicate if necessary. - /// - /// The predicate expression to add. - public virtual void AddToPredicate([NotNull] Expression predicate) - { - Check.NotNull(predicate, nameof(predicate)); + if (columnExpression?.Table.QuerySource != null + && !_selectExpression.HandlesQuerySource(columnExpression.Table.QuerySource)) + { + _correlated = true; + } + else + { + return base.Visit(expression); + } + } - Predicate = Predicate != null ? AndAlso(Predicate, predicate) : predicate; + return expression; + } } /// @@ -1194,70 +1059,84 @@ public override string ToString() .GenerateSql(new Dictionary()) .CommandText; - // TODO: Make polymorphic + #region Temporary Functions To Support Include + // TODO: Remove whole region when IncludeExpressionVisitor is removed /// - /// Updates the table expression of any column expressions in the target expression. + /// This method is available temporily to support current include pipeline. It will be removed when new include pipeline is fully working. /// - /// The target expression. - /// The new table expression. - /// - /// An updated expression. - /// - public virtual Expression UpdateColumnExpression( - [NotNull] Expression expression, - [NotNull] TableExpressionBase tableExpression) + /// + /// + /// + /// + /// + public virtual int AddToProjection( + [NotNull] string column, + [NotNull] IProperty property, + [NotNull] TableExpressionBase table, + [NotNull] IQuerySource querySource) { - Check.NotNull(expression, nameof(expression)); - Check.NotNull(tableExpression, nameof(tableExpression)); + Check.NotEmpty(column, nameof(column)); + Check.NotNull(property, nameof(property)); + Check.NotNull(table, nameof(table)); + Check.NotNull(querySource, nameof(querySource)); + + return AddToProjection( + BindPropertyToSelectExpression(property, table, querySource)); + } - var columnExpression = expression as ColumnExpression; + /// + /// This method is available temporily to support current include pipeline. It will be removed when new include pipeline is fully working. + /// + /// + /// + /// + /// + public virtual Expression BindPropertyToSelectExpression( + [NotNull] IProperty property, + [NotNull] TableExpressionBase table, + [NotNull] IQuerySource querySource) + { + Check.NotNull(property, nameof(property)); + Check.NotNull(querySource, nameof(querySource)); - if (columnExpression != null) + if (table is JoinExpressionBase joinTable) { - return columnExpression.Property == null - ? new ColumnExpression(columnExpression.Name, columnExpression.Type, tableExpression) - : new ColumnExpression(columnExpression.Name, columnExpression.Property, tableExpression); + table = joinTable.TableExpression; } - var aliasExpression = expression as AliasExpression; + var projectedExpressionToSearch = table is SelectExpression subquerySelectExpression + ? (Expression)subquerySelectExpression.BindPropertyToSelectExpression(property, querySource) + .LiftExpressionFromSubquery(table) + : new ColumnExpression(_relationalAnnotationProvider.For(property).ColumnName, property, table); - if (aliasExpression != null) - { - var selectExpression = aliasExpression.Expression as SelectExpression; - if (selectExpression != null) - { - return new ColumnExpression(aliasExpression.Alias, selectExpression.Type, tableExpression); - } + return IsProjectStar + ? GetOrAddToStarProjection(projectedExpressionToSearch) + : (_projection.Find(e => _expressionEqualityComparer.Equals(e, projectedExpressionToSearch)) ?? projectedExpressionToSearch); + } - return new AliasExpression( - aliasExpression.Alias, - UpdateColumnExpression(aliasExpression.Expression, tableExpression)) - { - SourceMember = aliasExpression.SourceMember - }; - } + /// + /// This method is available temporily to support current include pipeline. It will be removed when new include pipeline is fully working. + /// + /// + /// + /// + /// + /// + public virtual Ordering AddToOrderBy( + [NotNull] IProperty property, + [NotNull] TableExpressionBase table, + [NotNull] IQuerySource querySource, + OrderingDirection orderingDirection) + { + Check.NotNull(property, nameof(property)); + Check.NotNull(table, nameof(table)); - switch (expression.NodeType) - { - case ExpressionType.Coalesce: - { - var binaryExpression = (BinaryExpression)expression; - var left = UpdateColumnExpression(binaryExpression.Left, tableExpression); - var right = UpdateColumnExpression(binaryExpression.Right, tableExpression); - return binaryExpression.Update(left, binaryExpression.Conversion, right); - } - case ExpressionType.Conditional: - { - var conditionalExpression = (ConditionalExpression)expression; - var test = UpdateColumnExpression(conditionalExpression.Test, tableExpression); - var ifTrue = UpdateColumnExpression(conditionalExpression.IfTrue, tableExpression); - var ifFalse = UpdateColumnExpression(conditionalExpression.IfFalse, tableExpression); - return conditionalExpression.Update(test, ifTrue, ifFalse); - } - } + var orderingExpression = BindPropertyToSelectExpression(property, table, querySource); - return expression; + return AddToOrderBy(new Ordering(orderingExpression, orderingDirection)); } + + #endregion } } diff --git a/src/EFCore.Relational/Query/Expressions/SelectExpressionDependencies.cs b/src/EFCore.Relational/Query/Expressions/SelectExpressionDependencies.cs index 091649b78be..8e4a355deb6 100644 --- a/src/EFCore.Relational/Query/Expressions/SelectExpressionDependencies.cs +++ b/src/EFCore.Relational/Query/Expressions/SelectExpressionDependencies.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query.Sql; using Microsoft.EntityFrameworkCore.Utilities; @@ -40,11 +41,15 @@ public sealed class SelectExpressionDependencies /// /// /// The query SQL generator factory. - public SelectExpressionDependencies([NotNull] IQuerySqlGeneratorFactory querySqlGeneratorFactory) + /// + public SelectExpressionDependencies([NotNull] IQuerySqlGeneratorFactory querySqlGeneratorFactory, + [NotNull] IRelationalAnnotationProvider relationalAnnotationProvider) { Check.NotNull(querySqlGeneratorFactory, nameof(querySqlGeneratorFactory)); + Check.NotNull(relationalAnnotationProvider, nameof(relationalAnnotationProvider)); QuerySqlGeneratorFactory = querySqlGeneratorFactory; + RelationalAnnotationProvider = relationalAnnotationProvider; } /// @@ -52,12 +57,25 @@ public SelectExpressionDependencies([NotNull] IQuerySqlGeneratorFactory querySql /// public IQuerySqlGeneratorFactory QuerySqlGeneratorFactory { get; } + /// + /// The relational annotation provider. + /// + public IRelationalAnnotationProvider RelationalAnnotationProvider { get; } + /// /// Clones this dependency parameter object with one service replaced. /// /// A replacement for the current dependency of this type. /// A new parameter object with the given service replaced. public SelectExpressionDependencies With([NotNull] IQuerySqlGeneratorFactory querySqlGeneratorFactory) - => new SelectExpressionDependencies(Check.NotNull(querySqlGeneratorFactory, nameof(querySqlGeneratorFactory))); + => new SelectExpressionDependencies(Check.NotNull(querySqlGeneratorFactory, nameof(querySqlGeneratorFactory)), RelationalAnnotationProvider); + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// A replacement for the current dependency of this type. + /// A new parameter object with the given service replaced. + public SelectExpressionDependencies With([NotNull] IRelationalAnnotationProvider relationalAnnotationProvider) + => new SelectExpressionDependencies(QuerySqlGeneratorFactory, Check.NotNull(relationalAnnotationProvider, nameof(relationalAnnotationProvider))); } } diff --git a/src/EFCore.Relational/Query/Expressions/SqlFragmentExpression.cs b/src/EFCore.Relational/Query/Expressions/SqlFragmentExpression.cs index 4ca507d3c22..b81718e3a92 100644 --- a/src/EFCore.Relational/Query/Expressions/SqlFragmentExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/SqlFragmentExpression.cs @@ -70,5 +70,37 @@ protected override Expression Accept(ExpressionVisitor visitor) /// itself with the modified children. /// protected override Expression VisitChildren(ExpressionVisitor visitor) => this; + + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((SqlFragmentExpression)obj); + } + + private bool Equals(SqlFragmentExpression other) => string.Equals(Sql, other.Sql); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() => Sql.GetHashCode(); } } diff --git a/src/EFCore.Relational/Query/Expressions/SqlFunctionExpression.cs b/src/EFCore.Relational/Query/Expressions/SqlFunctionExpression.cs index 3045aba2c81..62f445eeb73 100644 --- a/src/EFCore.Relational/Query/Expressions/SqlFunctionExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/SqlFunctionExpression.cs @@ -110,5 +110,49 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) ? new SqlFunctionExpression(FunctionName, Type, newArguments) : this; } + + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((SqlFunctionExpression)obj); + } + + private bool Equals(SqlFunctionExpression other) + => Type == other.Type + && string.Equals(FunctionName, other.FunctionName) + && _arguments.SequenceEqual(other._arguments); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = _arguments.Aggregate(0, (current, argument) => current + ((current * 397) ^ argument.GetHashCode())); + hashCode = (hashCode * 397) ^ FunctionName.GetHashCode(); + hashCode = (hashCode * 397) ^ Type.GetHashCode(); + return hashCode; + } + } } } diff --git a/src/EFCore.Relational/Query/Expressions/StringCompareExpression.cs b/src/EFCore.Relational/Query/Expressions/StringCompareExpression.cs index bf8cd69db0b..e0076be4d3a 100644 --- a/src/EFCore.Relational/Query/Expressions/StringCompareExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/StringCompareExpression.cs @@ -95,9 +95,53 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) var newLeft = visitor.Visit(Left); var newRight = visitor.Visit(Right); - return (newLeft != Left) || (newRight != Right) + return newLeft != Left || newRight != Right ? new StringCompareExpression(Operator, newLeft, newRight) : this; } + + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((StringCompareExpression)obj); + } + + private bool Equals(StringCompareExpression other) + => Operator == other.Operator + && Equals(Left, other.Left) + && Equals(Right, other.Right); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = (int)Operator; + hashCode = (hashCode * 397) ^ Left.GetHashCode(); + hashCode = (hashCode * 397) ^ Right.GetHashCode(); + return hashCode; + } + } } } diff --git a/src/EFCore.Relational/Query/Expressions/TableExpression.cs b/src/EFCore.Relational/Query/Expressions/TableExpression.cs index ac8d917ff2a..e41fa7b98d5 100644 --- a/src/EFCore.Relational/Query/Expressions/TableExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/TableExpression.cs @@ -66,6 +66,53 @@ protected override Expression Accept(ExpressionVisitor visitor) : base.Accept(visitor); } + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((TableExpression)obj); + } + + private bool Equals(TableExpression other) + => string.Equals(Table, other.Table) + && string.Equals(Schema, other.Schema) + && string.Equals(Alias, other.Alias) + && Equals(QuerySource, other.QuerySource); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = Alias?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (QuerySource?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ Table.GetHashCode(); + hashCode = (hashCode * 397) ^ (Schema?.GetHashCode() ?? 0); + + return hashCode; + } + } + /// /// Creates a representation of the Expression. /// diff --git a/src/EFCore.Relational/Query/Expressions/TableExpressionBase.cs b/src/EFCore.Relational/Query/Expressions/TableExpressionBase.cs index 15594ab18da..92de1bf3bfa 100644 --- a/src/EFCore.Relational/Query/Expressions/TableExpressionBase.cs +++ b/src/EFCore.Relational/Query/Expressions/TableExpressionBase.cs @@ -2,13 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Utilities; using Remotion.Linq.Clauses; -using Remotion.Linq.Clauses.Expressions; -using Remotion.Linq.Clauses.ResultOperators; using Microsoft.EntityFrameworkCore.Query.Internal; namespace Microsoft.EntityFrameworkCore.Query.Expressions diff --git a/src/EFCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs b/src/EFCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs index fadb8f3fcab..c55cc726d45 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs @@ -144,14 +144,13 @@ var handlerContext queryModel, selectExpression); - Func resultHandler; if (relationalQueryModelVisitor.RequiresClientEval || relationalQueryModelVisitor.RequiresClientSelectMany || relationalQueryModelVisitor.RequiresClientJoin || relationalQueryModelVisitor.RequiresClientFilter || relationalQueryModelVisitor.RequiresClientOrderBy || relationalQueryModelVisitor.RequiresClientResultOperator - || !_resultHandlers.TryGetValue(resultOperator.GetType(), out resultHandler) + || !_resultHandlers.TryGetValue(resultOperator.GetType(), out var resultHandler) || selectExpression == null) { return handlerContext.EvalOnClient(); @@ -183,13 +182,9 @@ var predicate innerSelectExpression.ClearOrderBy(); } - SetProjectionConditionalExpression( + SetConditionAsProjection( handlerContext, - Expression.Condition( - Expression.Not(new ExistsExpression(innerSelectExpression)), - Expression.Constant(true), - Expression.Constant(false), - typeof(bool))); + Expression.Not(new ExistsExpression(innerSelectExpression))); return TransformClientExpression(handlerContext); } @@ -210,13 +205,9 @@ private static Expression HandleAny(HandlerContext handlerContext) innerSelectExpression.ClearOrderBy(); } - SetProjectionConditionalExpression( + SetConditionAsProjection( handlerContext, - Expression.Condition( - new ExistsExpression(innerSelectExpression), - Expression.Constant(true), - Expression.Constant(false), - typeof(bool))); + new ExistsExpression(innerSelectExpression)); return TransformClientExpression(handlerContext); } @@ -266,16 +257,13 @@ private static Expression HandleContains(HandlerContext handlerContext) var item = filteringVisitor.Visit(itemResultOperator.Item); if (item != null) { - var itemSelectExpression = item as SelectExpression; - - if (itemSelectExpression != null) + if (item is SelectExpression itemSelectExpression) { var entityType = handlerContext.Model.FindEntityType(handlerContext.QueryModel.MainFromClause.ItemType); if (entityType != null) { var outerSelectExpression = handlerContext.SelectExpressionFactory.Create(handlerContext.QueryModelVisitor.QueryCompilationContext); - outerSelectExpression.SetProjectionExpression(Expression.Constant(1)); var collectionSelectExpression = handlerContext.SelectExpression.Clone(handlerContext.QueryModelVisitor.QueryCompilationContext.CreateUniqueTableAlias()); @@ -286,27 +274,21 @@ var collectionSelectExpression foreach (var property in entityType.FindPrimaryKey().Properties) { - itemSelectExpression.AddToProjection( - new ColumnExpression( - property.Name, - property, - itemSelectExpression.Tables.First())); - - collectionSelectExpression.AddToProjection( - new ColumnExpression( - property.Name, - property, - collectionSelectExpression.Tables.First())); + var itemProperty = itemSelectExpression.BindPropertyToSelectExpression( + property, + itemSelectExpression.ProjectStarTable.QuerySource); + + itemSelectExpression.AddToProjection(itemProperty); + + var collectionProperty = collectionSelectExpression.BindPropertyToSelectExpression( + property, + collectionSelectExpression.ProjectStarTable.QuerySource); + + collectionSelectExpression.AddToProjection(collectionProperty); var predicate = Expression.Equal( - new ColumnExpression( - property.Name, - property, - collectionSelectExpression), - new ColumnExpression( - property.Name, - property, - itemSelectExpression)); + collectionProperty.LiftExpressionFromSubquery(collectionSelectExpression), + itemProperty.LiftExpressionFromSubquery(itemSelectExpression)); joinExpression.Predicate = joinExpression.Predicate == null @@ -316,27 +298,19 @@ var collectionSelectExpression predicate); } - SetProjectionConditionalExpression( + SetConditionAsProjection( handlerContext, - Expression.Condition( - new ExistsExpression(outerSelectExpression), - Expression.Constant(true), - Expression.Constant(false), - typeof(bool))); + new ExistsExpression(outerSelectExpression)); return TransformClientExpression(handlerContext); } } - SetProjectionConditionalExpression( + SetConditionAsProjection( handlerContext, - Expression.Condition( - new InExpression( - item, - handlerContext.SelectExpression.Clone("")), - Expression.Constant(true), - Expression.Constant(false), - typeof(bool))); + new InExpression( + item, + handlerContext.SelectExpression.Clone(""))); return TransformClientExpression(handlerContext); } @@ -444,30 +418,7 @@ private static Expression HandleDistinct(HandlerContext handlerContext) { if (!handlerContext.QueryModelVisitor.RequiresClientProjection) { - var selectExpression = handlerContext.SelectExpression; - - selectExpression.IsDistinct = true; - - if (selectExpression.OrderBy.Any(o => - { - var orderByColumnExpression = o.Expression.TryGetColumnExpression(); - - if (orderByColumnExpression == null) - { - return true; - } - - return !selectExpression.Projection.Any(e => - { - var projectionColumnExpression = e.TryGetColumnExpression(); - - return projectionColumnExpression != null - && projectionColumnExpression.Equals(orderByColumnExpression); - }); - })) - { - handlerContext.SelectExpression.ClearOrderBy(); - } + handlerContext.SelectExpression.IsDistinct = true; return handlerContext.EvalOnServer; } @@ -634,8 +585,8 @@ private static Expression HandleLast(HandlerContext handlerContext) requiresClientResultOperator = requiresClientResultOperator - || (!((LastResultOperator)handlerContext.ResultOperator).ReturnDefaultWhenEmpty - && handlerContext.QueryModelVisitor.ParentQueryModelVisitor != null); + || !((LastResultOperator)handlerContext.ResultOperator).ReturnDefaultWhenEmpty + && handlerContext.QueryModelVisitor.ParentQueryModelVisitor != null; return handlerContext.EvalOnClient(requiresClientResultOperator: requiresClientResultOperator); } @@ -707,7 +658,7 @@ private static Expression HandleSingle(HandlerContext handlerContext) { handlerContext.SelectExpression.Limit = Expression.Constant(2); - var returnExpression = handlerContext.EvalOnClient(requiresClientResultOperator: true); + var returnExpression = handlerContext.EvalOnClient(); // For top level single, we do not require client eval if (handlerContext.QueryModelVisitor.ParentQueryModelVisitor == null) @@ -777,15 +728,16 @@ var sqlTranslatingExpressionVisitor return handlerContext.EvalOnClient(); } - private static void SetProjectionConditionalExpression( - HandlerContext handlerContext, ConditionalExpression conditionalExpression) + private static void SetConditionAsProjection( + HandlerContext handlerContext, Expression condition) { - handlerContext.SelectExpression.SetProjectionConditionalExpression(conditionalExpression); - handlerContext.SelectExpression.ClearTables(); - handlerContext.SelectExpression.ClearOrderBy(); - handlerContext.SelectExpression.Offset = null; - handlerContext.SelectExpression.Limit = null; - handlerContext.SelectExpression.Predicate = null; + handlerContext.SelectExpression.Clear(); + + handlerContext.SelectExpression.AddToProjection( + Expression.Condition(condition, + Expression.Constant(true), + Expression.Constant(false), + typeof(bool))); } private static readonly MethodInfo _transformClientExpressionMethodInfo diff --git a/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs index 7d7c233c855..558a5f3c956 100644 --- a/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs @@ -60,7 +60,7 @@ private readonly Dictionary _subQuery private bool _requiresClientResultOperator; private Dictionary> _navigationIndexMap = new Dictionary>(); - private List _unflattenedGroupJoinClauses = new List(); + private readonly List _unflattenedGroupJoinClauses = new List(); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used @@ -182,25 +182,20 @@ public virtual bool RequiresClientResultOperator /// true if the query model visitor can bind to its parent's properties, false if not. /// public virtual bool CanBindToParentQueryModel { get; protected set; } - + /// /// Gets a value indicating whether query model visitor's resulting expression /// can be lifted into the parent query. Liftable queries contain a single SelectExpression. /// public virtual bool IsLiftable - { - get - { - return Queries.Count == 1 - && !RequiresClientEval - && !RequiresClientSelectMany - && !RequiresClientJoin - && !RequiresClientFilter - && !RequiresClientProjection - && !RequiresClientOrderBy - && !RequiresClientResultOperator; - } - } + => Queries.Count == 1 + && !RequiresClientEval + && !RequiresClientSelectMany + && !RequiresClientJoin + && !RequiresClientFilter + && !RequiresClientProjection + && !RequiresClientOrderBy + && !RequiresClientResultOperator; /// /// Context for the query compilation. @@ -259,8 +254,7 @@ public virtual SelectExpression TryGetQuery([NotNull] IQuerySource querySource) { Check.NotNull(querySource, nameof(querySource)); - SelectExpression selectExpression; - return QueriesBySource.TryGetValue(querySource, out selectExpression) + return QueriesBySource.TryGetValue(querySource, out SelectExpression selectExpression) ? selectExpression : QueriesBySource.Values.LastOrDefault(se => se.HandlesQuerySource(querySource)); } @@ -385,8 +379,7 @@ protected override Expression CompileMainFromClauseExpression( Check.NotNull(queryModel, nameof(queryModel)); Expression expression = null; - var subQueryExpression = mainFromClause.FromExpression as SubQueryExpression; - if (subQueryExpression != null) + if (mainFromClause.FromExpression is SubQueryExpression subQueryExpression) { expression = LiftSubQuery(mainFromClause, subQueryExpression); } @@ -457,8 +450,7 @@ protected override Expression CompileAdditionalFromClauseExpression( Check.NotNull(queryModel, nameof(queryModel)); Expression expression = null; - var subQueryExpression = additionalFromClause.FromExpression as SubQueryExpression; - if (subQueryExpression != null) + if (additionalFromClause.FromExpression is SubQueryExpression subQueryExpression) { expression = LiftSubQuery(additionalFromClause, subQueryExpression); } @@ -512,8 +504,7 @@ protected override Expression CompileJoinClauseInnerSequenceExpression( Check.NotNull(queryModel, nameof(queryModel)); Expression expression = null; - var subQueryExpression = joinClause.InnerSequence as SubQueryExpression; - if (subQueryExpression != null) + if (joinClause.InnerSequence is SubQueryExpression subQueryExpression) { expression = LiftSubQuery(joinClause, subQueryExpression); } @@ -582,10 +573,8 @@ var previousMapping = QueryCompilationContext.QuerySourceMapping .GetExpression(querySource); - var groupJoinClause = querySource as GroupJoinClause; - if (groupJoinClause != null - && QueryCompilationContext.QuerySourceMapping + if (querySource is GroupJoinClause groupJoinClause && QueryCompilationContext.QuerySourceMapping .ContainsMapping(groupJoinClause.JoinClause)) { previousMapping.Add( @@ -610,37 +599,27 @@ private class OuterJoinOrderingExtractor : ExpressionVisitor private IForeignKey _matchingCandidate; private List _matchingCandidateProperties; - public override Expression Visit(Expression expression) - { - var binaryExpression = expression as BinaryExpression; - - if (binaryExpression != null) - { - return VisitBinary(binaryExpression); - } - - return expression; - } - - protected override Expression VisitBinary(BinaryExpression node) + protected override Expression VisitBinary(BinaryExpression binaryExpression) { if (DependentToPrincipalFound) { - return node; + return binaryExpression; } - if (node.NodeType == ExpressionType.Equal) + if (binaryExpression.NodeType == ExpressionType.Equal) { - var leftProperty = node.Left.RemoveConvert().TryGetColumnExpression()?.Property; - var rightProperty = node.Right.RemoveConvert().TryGetColumnExpression()?.Property; + var leftExpression = binaryExpression.Left.RemoveConvert(); + var rightExpression = binaryExpression.Right.RemoveConvert(); + var leftProperty = (((leftExpression as NullableExpression)?.Operand ?? leftExpression) as ColumnExpression)?.Property; + var rightProperty = (((rightExpression as NullableExpression)?.Operand ?? rightExpression) as ColumnExpression)?.Property; if (leftProperty != null && rightProperty != null && leftProperty.IsForeignKey() && rightProperty.IsKey()) { var keyDeclaringEntityType = rightProperty.GetContainingKeys().First().DeclaringEntityType; - var matchingForeignKeys = leftProperty.GetContainingForeignKeys().Where(k => k.PrincipalKey.DeclaringEntityType == keyDeclaringEntityType); - if (matchingForeignKeys.Count() == 1) + var matchingForeignKeys = leftProperty.GetContainingForeignKeys().Where(k => k.PrincipalKey.DeclaringEntityType == keyDeclaringEntityType).ToList(); + if (matchingForeignKeys.Count == 1) { var matchingKey = matchingForeignKeys.Single(); if (rightProperty.GetContainingKeys().Contains(matchingKey.PrincipalKey)) @@ -659,23 +638,20 @@ protected override Expression VisitBinary(BinaryExpression node) if (_matchingCandidate.Properties.All(p => _matchingCandidateProperties.Contains(p))) { DependentToPrincipalFound = true; - return node; + return binaryExpression; } } } } - _expressions.Add(node.Left.RemoveConvert()); + _expressions.Add(leftExpression); - return node; - } - - if (node.NodeType == ExpressionType.AndAlso) - { - return base.VisitBinary(node); + return binaryExpression; } - return node; + return binaryExpression.NodeType == ExpressionType.AndAlso + ? base.VisitBinary(binaryExpression) + : binaryExpression; } } @@ -711,8 +687,7 @@ protected override Expression CompileGroupJoinInnerSequenceExpression( Check.NotNull(queryModel, nameof(queryModel)); Expression expression = null; - var subQueryExpression = groupJoinClause.JoinClause.InnerSequence as SubQueryExpression; - if (subQueryExpression != null) + if (groupJoinClause.JoinClause.InnerSequence is SubQueryExpression subQueryExpression) { expression = LiftSubQuery(groupJoinClause.JoinClause, subQueryExpression); } @@ -793,9 +768,7 @@ public QuerySourceUpdater( protected override Expression VisitConstant(ConstantExpression constantExpression) { - var shaper = constantExpression.Value as Shaper; - - if (shaper != null) + if (constantExpression.Value is Shaper shaper) { foreach (var queryAnnotation in _relationalQueryCompilationContext.QueryAnnotations @@ -889,7 +862,7 @@ var sqlTranslatingExpressionVisitor _conditionalRemovingExpressionVisitorFactory .Create() .Visit(sqlPredicateExpression); - + selectExpression.AddToPredicate(sqlPredicateExpression); } else @@ -1033,9 +1006,9 @@ where oldShaper.IsShaperForQuerySource(i.QuerySource) { newShaper = oldShaper.Unwrap(querySourceReferenceExpression.ReferencedQuerySource); } - + newShaper = newShaper ?? ProjectionShaper.Create(oldShaper, materializer); - + Expression = Expression.Call( shapedQuery.Method @@ -1177,7 +1150,7 @@ private bool IsShapedQueryExpression(Expression expression) var linqMethods = QueryCompilationContext.LinqOperatorProvider; - if (methodCallExpression.Method.MethodIsClosedFormOf(linqMethods.DefaultIfEmpty) + if (methodCallExpression.Method.MethodIsClosedFormOf(linqMethods.DefaultIfEmpty) || methodCallExpression.Method.MethodIsClosedFormOf(linqMethods.DefaultIfEmptyArg)) { methodCallExpression = methodCallExpression.Arguments[0] as MethodCallExpression; @@ -1190,7 +1163,7 @@ private bool IsShapedQueryExpression(Expression expression) var queryMethods = QueryCompilationContext.QueryMethodProvider; - if (methodCallExpression.Method.MethodIsClosedFormOf(queryMethods.ShapedQueryMethod) + if (methodCallExpression.Method.MethodIsClosedFormOf(queryMethods.ShapedQueryMethod) || methodCallExpression.Method.MethodIsClosedFormOf(queryMethods.DefaultIfEmptyShapedQueryMethod)) { return true; @@ -1218,8 +1191,8 @@ private Shaper ExtractShaper(MethodCallExpression shapedQueryExpression, int off } private bool TryFlattenSelectMany( - AdditionalFromClause fromClause, - QueryModel queryModel, + AdditionalFromClause fromClause, + QueryModel queryModel, int index, int previousProjectionCount) { @@ -1261,7 +1234,7 @@ var innerShapedQuery if (selectManyMethodCallExpression == null || !selectManyMethodCallExpression.Method.MethodIsClosedFormOf(LinqOperatorProvider.SelectMany) - || !IsShapedQueryExpression(outerShapedQuery) + || !IsShapedQueryExpression(outerShapedQuery) || !IsShapedQueryExpression(innerShapedQuery)) { return false; @@ -1300,6 +1273,7 @@ var compositeShaper Expression = Expression.Call( + // ReSharper disable once PossibleNullReferenceException outerShapedQuery.Method .GetGenericMethodDefinition() .MakeGenericMethod(materializerLambda.ReturnType), @@ -1311,9 +1285,9 @@ var compositeShaper } private bool TryFlattenJoin( - JoinClause joinClause, - QueryModel queryModel, - int index, + JoinClause joinClause, + QueryModel queryModel, + int index, int previousProjectionCount) { if (RequiresClientJoin || RequiresClientSelectMany) @@ -1331,7 +1305,7 @@ var innerShapedQuery if (joinMethodCallExpression == null || !joinMethodCallExpression.Method.MethodIsClosedFormOf(LinqOperatorProvider.Join) - || !IsShapedQueryExpression(outerShapedQuery) + || !IsShapedQueryExpression(outerShapedQuery) || !IsShapedQueryExpression(innerShapedQuery)) { return false; @@ -1391,6 +1365,7 @@ var compositeShaper Expression = Expression.Call( + // ReSharper disable once PossibleNullReferenceException outerShapedQuery.Method .GetGenericMethodDefinition() .MakeGenericMethod(materializerLambda.ReturnType), @@ -1457,11 +1432,11 @@ var predicate { var subSelectExpression = innerSelectExpression.PushDownSubquery(); innerSelectExpression.ExplodeStarProjection(); - subSelectExpression.ClearProjection(); subSelectExpression.IsProjectStar = true; + subSelectExpression.ClearProjection(); subSelectExpression.QuerySource = joinClause; - predicate + predicate = sqlTranslatingExpressionVisitor.Visit( Expression.Equal(joinClause.OuterKeySelector, joinClause.InnerKeySelector)); } @@ -1470,7 +1445,7 @@ var predicate outerSelectExpression.RemoveRangeFromProjection(previousProjectionCount); - var projection + var projections = QueryCompilationContext.QuerySourceRequiresMaterialization(joinClause) ? innerSelectExpression.Projection : Enumerable.Empty(); @@ -1478,17 +1453,17 @@ var projection var joinExpression = outerSelectExpression.AddLeftOuterJoin( innerSelectExpression.Tables.Single(), - projection); + projections); joinExpression.Predicate = predicate; joinExpression.QuerySource = joinClause; if (TryFlattenGroupJoinDefaultIfEmpty( - groupJoinClause, - queryModel, - index, - previousProjectionCount, - previousParameter, + groupJoinClause, + queryModel, + index, + previousProjectionCount, + previousParameter, previousMapping)) { return true; @@ -1505,7 +1480,7 @@ var joinExpression new Ordering(expression, OrderingDirection.Asc)); } } - + var outerShaper = ExtractShaper(outerShapedQuery, 0); var innerShaper = ExtractShaper(innerShapedQuery, previousProjectionCount); @@ -1523,6 +1498,7 @@ var groupJoinMethod var newShapedQueryMethod = Expression.Call( queryMethodProvider.QueryMethod, + // ReSharper disable once PossibleNullReferenceException outerShapedQuery.Arguments[0], outerShapedQuery.Arguments[1], Expression.Default(typeof(int?))); @@ -1549,7 +1525,7 @@ var defaultGroupJoinInclude } private bool TryFlattenGroupJoinDefaultIfEmpty( - GroupJoinClause groupJoinClause, + [NotNull] GroupJoinClause groupJoinClause, QueryModel queryModel, int index, int previousProjectionCount, @@ -1602,13 +1578,13 @@ var referencedQuerySource .Where(a => a.QuerySource == additionalFromClause)) { annotation.QuerySource = joinClause; - annotation.PathFromQuerySource + annotation.PathFromQuerySource = ReferenceReplacingExpressionVisitor.ReplaceClauseReferences( annotation.PathFromQuerySource, querySourceMapping, throwOnUnmappedReferences: false); } - + var groupJoinMethodCallExpression = (MethodCallExpression)Expression; var outerShapedQuery = (MethodCallExpression)groupJoinMethodCallExpression.Arguments[0]; @@ -1632,9 +1608,9 @@ var innerItemParameter AddOrUpdateMapping(joinClause, innerItemParameter); - var transparentIdentifierType + var transparentIdentifierType = CreateTransparentIdentifierType( - previousParameter.Type, + previousParameter.Type, innerShaper.Type); var materializer @@ -1811,16 +1787,12 @@ public virtual Expression BindLocalMethodCallExpression( return base.BindMethodCallExpression(methodCallExpression, null, (property, qs) => { - var parameterExpression = methodCallExpression.Arguments[0] as ParameterExpression; - - if (parameterExpression != null) + if (methodCallExpression.Arguments[0] is ParameterExpression parameterExpression) { return new PropertyParameterExpression(parameterExpression.Name, property); } - var constantExpression = methodCallExpression.Arguments[0] as ConstantExpression; - - if (constantExpression != null) + if (methodCallExpression.Arguments[0] is ConstantExpression constantExpression) { return Expression.Constant( property.GetGetter().GetClrValue(constantExpression.Value), @@ -1855,8 +1827,7 @@ private TResult BindMemberOrMethod( if (selectExpression == null && bindSubQueries) { - RelationalQueryModelVisitor subQueryModelVisitor; - if (_subQueryModelVisitorsBySource.TryGetValue(querySource, out subQueryModelVisitor)) + if (_subQueryModelVisitorsBySource.TryGetValue(querySource, out RelationalQueryModelVisitor subQueryModelVisitor)) { if (!subQueryModelVisitor.RequiresClientProjection) { @@ -1864,7 +1835,6 @@ private TResult BindMemberOrMethod( selectExpression? .AddToProjection( - _relationalAnnotationProvider.For(property).ColumnName, property, querySource); } @@ -1880,7 +1850,6 @@ private TResult BindMemberOrMethod( = ParentQueryModelVisitor?.TryGetQuery(querySource); selectExpression?.AddToProjection( - _relationalAnnotationProvider.For(property).ColumnName, property, querySource); } @@ -1921,7 +1890,7 @@ var parameterWithSamePrefixCount } QueryCompilationContext.ParentQueryReferenceParameters.Add(parameterName); - + var querySourceReference = new QuerySourceReferenceExpression(querySource); var propertyExpression = isMemberExpression ? Expression.Property(querySourceReference, property.PropertyInfo) @@ -1934,9 +1903,9 @@ var parameterWithSamePrefixCount _injectedParameters[parameterName] = propertyExpression; - Expression + Expression = CreateInjectParametersExpression( - Expression, + Expression, new Dictionary { [parameterName] = propertyExpression }); return Expression.Parameter( @@ -1965,7 +1934,7 @@ private Expression CreateInjectParametersExpression(Expression expression, Dicti expression = methodCallExpression.Arguments[1]; } - parameterNameExpressions.AddRange(parameters.Keys.Select(k => Expression.Constant(k))); + parameterNameExpressions.AddRange(parameters.Keys.Select(Expression.Constant)); parameterValueExpressions.AddRange(parameters.Values); var elementType = expression.Type.GetTypeInfo().GenericTypeArguments.Single(); diff --git a/src/EFCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs b/src/EFCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs index dc15bfcef61..2c92570d7b2 100644 --- a/src/EFCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs @@ -29,6 +29,7 @@ public class DefaultQuerySqlGenerator : ThrowingExpressionVisitor, ISqlExpressio private IReadOnlyDictionary _parametersValues; private ParameterNameGenerator _parameterNameGenerator; private RelationalTypeMapping _typeMapping; + private NullComparisonTransformingVisitor _nullComparisonTransformingVisitor; private RelationalNullsExpandingVisitor _relationalNullsExpandingVisitor; private PredicateReductionExpressionOptimizer _predicateReductionExpressionOptimizer; private PredicateNegationExpressionOptimizer _predicateNegationExpressionOptimizer; @@ -123,6 +124,7 @@ public virtual IRelationalCommand GenerateSql(IReadOnlyDictionary ProcessInExpressionValues( foreach (var inValue in inExpressionValues) { - var inConstant = inValue as ConstantExpression; - - if (inConstant != null) + if (inValue is ConstantExpression inConstant) { AddInExpressionValues(inConstant.Value, inConstants, inConstant); } else { - var inParameter = inValue as ParameterExpression; - - if (inParameter != null) + if (inValue is ParameterExpression inParameter) { - object parameterValue; - if (_parametersValues.TryGetValue(inParameter.Name, out parameterValue)) + if (_parametersValues.TryGetValue(inParameter.Name, out object parameterValue)) { AddInExpressionValues(parameterValue, inConstants, inParameter); @@ -813,18 +790,14 @@ protected virtual IReadOnlyList ProcessInExpressionValues( } else { - var inListInit = inValue as ListInitExpression; - - if (inListInit != null) + if (inValue is ListInitExpression inListInit) { inConstants.AddRange(ProcessInExpressionValues( inListInit.Initializers.SelectMany(i => i.Arguments))); } else { - var newArray = inValue as NewArrayExpression; - - if (newArray != null) + if (inValue is NewArrayExpression newArray) { inConstants.AddRange(ProcessInExpressionValues(newArray.Expressions)); } @@ -839,10 +812,7 @@ protected virtual IReadOnlyList ProcessInExpressionValues( private static void AddInExpressionValues( object value, List inConstants, Expression expression) { - var valuesEnumerable = value as IEnumerable; - - if (valuesEnumerable != null - && value.GetType() != typeof(string) + if (value is IEnumerable valuesEnumerable && value.GetType() != typeof(string) && value.GetType() != typeof(byte[])) { inConstants.AddRange(valuesEnumerable.Cast().Select(Expression.Constant)); @@ -876,13 +846,9 @@ protected virtual IReadOnlyList ExtractNonNullExpressionValues( continue; } - var inParameter = inValue as ParameterExpression; - - if (inParameter != null) + if (inValue is ParameterExpression inParameter) { - object parameterValue; - - if (_parametersValues.TryGetValue(inParameter.Name, out parameterValue)) + if (_parametersValues.TryGetValue(inParameter.Name, out object parameterValue)) { if (parameterValue != null) { @@ -1008,10 +974,7 @@ protected override Expression VisitConditional(ConditionalExpression conditional _relationalCommandBuilder.AppendLine(); _relationalCommandBuilder.Append("THEN "); - var constantIfTrue = conditionalExpression.IfTrue as ConstantExpression; - - if (constantIfTrue != null - && constantIfTrue.Type == typeof(bool)) + if (conditionalExpression.IfTrue is ConstantExpression constantIfTrue && constantIfTrue.Type == typeof(bool)) { _relationalCommandBuilder.Append((bool)constantIfTrue.Value ? TypedTrueLiteral : TypedFalseLiteral); } @@ -1022,10 +985,7 @@ protected override Expression VisitConditional(ConditionalExpression conditional _relationalCommandBuilder.Append(" ELSE "); - var constantIfFalse = conditionalExpression.IfFalse as ConstantExpression; - - if (constantIfFalse != null - && constantIfFalse.Type == typeof(bool)) + if (conditionalExpression.IfFalse is ConstantExpression constantIfFalse && constantIfFalse.Type == typeof(bool)) { _relationalCommandBuilder.Append((bool)constantIfFalse.Value ? TypedTrueLiteral : TypedFalseLiteral); } @@ -1057,7 +1017,7 @@ public virtual Expression VisitExists(ExistsExpression existsExpression) using (_relationalCommandBuilder.Indent()) { - Visit(existsExpression.Expression); + Visit(existsExpression.Subquery); } _relationalCommandBuilder.Append(")"); @@ -1097,11 +1057,12 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) { _typeMapping = InferTypeMappingFromColumn(binaryExpression.Left) - ?? InferTypeMappingFromColumn(binaryExpression.Right) - ?? parentTypeMapping; + ?? InferTypeMappingFromColumn(binaryExpression.Right) + ?? parentTypeMapping; } - var needParens = binaryExpression.Left.RemoveConvert() is BinaryExpression; + var needParens = binaryExpression.Left.RemoveConvert() is BinaryExpression leftBinaryExpression + && leftBinaryExpression.NodeType != ExpressionType.Coalesce; if (needParens) { @@ -1117,7 +1078,8 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) _relationalCommandBuilder.Append(GenerateOperator(binaryExpression)); - needParens = binaryExpression.Right.RemoveConvert() is BinaryExpression; + needParens = binaryExpression.Right.RemoveConvert() is BinaryExpression rightBinaryExpression + && rightBinaryExpression.NodeType != ExpressionType.Coalesce; if (needParens) { @@ -1151,13 +1113,31 @@ public virtual Expression VisitColumn(ColumnExpression columnExpression) { Check.NotNull(columnExpression, nameof(columnExpression)); - _relationalCommandBuilder.Append(SqlGenerator.DelimitIdentifier(columnExpression.TableAlias)) + _relationalCommandBuilder.Append(SqlGenerator.DelimitIdentifier(columnExpression.Table.Alias)) .Append(".") .Append(SqlGenerator.DelimitIdentifier(columnExpression.Name)); return columnExpression; } + /// + /// Visits a ColumnReferenceExpression. + /// + /// The column reference expression. + /// + /// An Expression. + /// + public virtual Expression VisitColumnReference(ColumnReferenceExpression columnReferenceExpression) + { + Check.NotNull(columnReferenceExpression, nameof(columnReferenceExpression)); + + _relationalCommandBuilder.Append(SqlGenerator.DelimitIdentifier(columnReferenceExpression.Table.Alias)) + .Append(".") + .Append(SqlGenerator.DelimitIdentifier(columnReferenceExpression.Name)); + + return columnReferenceExpression; + } + /// /// Visits an AliasExpression. /// @@ -1169,14 +1149,11 @@ public virtual Expression VisitAlias(AliasExpression aliasExpression) { Check.NotNull(aliasExpression, nameof(aliasExpression)); - if (!aliasExpression.IsProjected) - { - Visit(aliasExpression.Expression); + Visit(aliasExpression.Expression); - if (aliasExpression.Alias != null) - { - _relationalCommandBuilder.Append(" AS "); - } + if (aliasExpression.Alias != null) + { + _relationalCommandBuilder.Append(" AS "); } if (aliasExpression.Alias != null) @@ -1337,16 +1314,12 @@ protected override Expression VisitUnary(UnaryExpression expression) { case ExpressionType.Not: { - var inExpression = expression.Operand as InExpression; - - if (inExpression != null) + if (expression.Operand is InExpression inExpression) { return GenerateNotIn(inExpression); } - var isNullExpression = expression.Operand as IsNullExpression; - - if (isNullExpression != null) + if (expression.Operand is IsNullExpression isNullExpression) { return GenerateIsNotNull(isNullExpression); } @@ -1470,10 +1443,17 @@ var parameterName /// protected virtual RelationalTypeMapping InferTypeMappingFromColumn([NotNull] Expression expression) { - var column = expression.TryGetColumnExpression(); - return column?.Property != null - ? Dependencies.RelationalTypeMapper.FindMapping(column.Property) - : null; + switch (expression) + { + case ColumnExpression columnExpression: + return Dependencies.RelationalTypeMapper.FindMapping(columnExpression.Property); + case ColumnReferenceExpression columnReferenceExpression: + return InferTypeMappingFromColumn(columnReferenceExpression.Expression); + case AliasExpression aliasExpression: + return InferTypeMappingFromColumn(aliasExpression.Expression); + default: + return null; + } } /// @@ -1509,8 +1489,7 @@ protected virtual string GenerateOperator([NotNull] Expression expression) { case ExpressionType.Extension: { - var asStringCompareExpression = expression as StringCompareExpression; - if (asStringCompareExpression != null) + if (expression is StringCompareExpression asStringCompareExpression) { return GenerateBinaryOperator(asStringCompareExpression.Operator); } @@ -1565,34 +1544,15 @@ protected override Expression VisitBinary(BinaryExpression expression) var leftExpression = expression.Left.RemoveConvert(); var rightExpression = expression.Right.RemoveConvert(); - var parameter - = rightExpression as ParameterExpression - ?? leftExpression as ParameterExpression; + var parameterExpression = leftExpression as ParameterExpression + ?? rightExpression as ParameterExpression; - object parameterValue; - if (parameter != null - && _parameterValues.TryGetValue(parameter.Name, out parameterValue)) + if (parameterExpression != null + && _parameterValues.TryGetValue(parameterExpression.Name, out object parameterValue)) { - if (parameterValue == null) - { - var columnExpression - = leftExpression.TryGetColumnExpression() - ?? rightExpression.TryGetColumnExpression(); - - if (columnExpression != null) - { - return - expression.NodeType == ExpressionType.Equal - ? (Expression)new IsNullExpression(columnExpression) - : Expression.Not(new IsNullExpression(columnExpression)); - } - } + var nonParameterExpression = leftExpression is ParameterExpression ? rightExpression : leftExpression; - var constantExpression - = leftExpression as ConstantExpression - ?? rightExpression as ConstantExpression; - - if (constantExpression != null) + if (nonParameterExpression is ConstantExpression constantExpression) { if (parameterValue == null && constantExpression.Value == null) @@ -1612,6 +1572,14 @@ var constantExpression : Expression.Constant(true); } } + + if (parameterValue == null) + { + return + expression.NodeType == ExpressionType.Equal + ? (Expression)new IsNullExpression(nonParameterExpression) + : Expression.Not(new IsNullExpression(nonParameterExpression)); + } } } @@ -1644,10 +1612,7 @@ public Expression Translate(Expression expression, bool searchCondition) // Top-level check for condition/value if (_isSearchCondition && !IsSearchCondition(newExpression)) { - var constantExpression = newExpression as ConstantExpression; - - if (constantExpression != null - && (bool)constantExpression.Value) + if (newExpression is ConstantExpression constantExpression && (bool)constantExpression.Value) { // Absorb top level True node return null; diff --git a/src/EFCore.Relational/Query/Sql/ISqlExpressionVisitor.cs b/src/EFCore.Relational/Query/Sql/ISqlExpressionVisitor.cs index 809025624c3..b26f659e82a 100644 --- a/src/EFCore.Relational/Query/Sql/ISqlExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Sql/ISqlExpressionVisitor.cs @@ -173,5 +173,14 @@ public interface ISqlExpressionVisitor /// An Expression. /// Expression VisitSqlFragment([NotNull] SqlFragmentExpression sqlFragmentExpression); + + /// + /// Visit a ColumnReferenceExpression. + /// + /// The ColumnReferenceExpression expression. + /// + /// An Expression. + /// + Expression VisitColumnReference([NotNull] ColumnReferenceExpression columnReferenceExpression); } } \ No newline at end of file diff --git a/src/EFCore.Relational/Query/Sql/Internal/FromSqlNonComposedQuerySqlGenerator.cs b/src/EFCore.Relational/Query/Sql/Internal/FromSqlNonComposedQuerySqlGenerator.cs index cd022b08a0a..e58a6ba9086 100644 --- a/src/EFCore.Relational/Query/Sql/Internal/FromSqlNonComposedQuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/Sql/Internal/FromSqlNonComposedQuerySqlGenerator.cs @@ -72,13 +72,9 @@ var readerColumns for (var i = 0; i < SelectExpression.Projection.Count; i++) { - var aliasExpression = SelectExpression.Projection[i] as AliasExpression; - - if (aliasExpression != null) + if (SelectExpression.Projection[i] is ColumnExpression columnExpression) { - var columnName - = aliasExpression.Alias - ?? aliasExpression.TryGetColumnExpression()?.Name; + var columnName = columnExpression.Name; if (columnName != null) { diff --git a/src/EFCore.Specification.Tests/QueryTestBase.cs b/src/EFCore.Specification.Tests/QueryTestBase.cs index c8d4278bd1b..1d0202c078a 100644 --- a/src/EFCore.Specification.Tests/QueryTestBase.cs +++ b/src/EFCore.Specification.Tests/QueryTestBase.cs @@ -6113,7 +6113,7 @@ public virtual void Select_take_null_coalesce_operator() cs => cs.Select(c => new { c.CustomerID, c.CompanyName, Region = c.Region ?? "ZZ" }).OrderBy(c => c.Region).Take(5)); } - [ConditionalFact(Skip = "The order by inside subquery needs to be aliased to be copied outside. Invalid query generated otherwise.")] + [ConditionalFact] public virtual void Select_take_skip_null_coalesce_operator() { AssertQuery( @@ -7219,6 +7219,65 @@ from o in lo.Where(x => x.OrderID > 5).OrderBy(x => x.OrderDate).DefaultIfEmpty( select new { c.ContactName, o }); } + [ConditionalFact] + public virtual void Anonymous_member_distinct_where() + { + AssertQuery( + cs => cs.Select(c => new { c.CustomerID }).Distinct().Where(n => n.CustomerID == "ALFKI")); + } + + [ConditionalFact] + public virtual void Anonymous_member_distinct_orderby() + { + AssertQuery( + cs => cs.Select(c => new { c.CustomerID }).Distinct().OrderBy(n => n.CustomerID)); + } + + [ConditionalFact] + public virtual void Anonymous_member_distinct_result() + { + AssertQuery( + cs => cs.Select(c => new { c.CustomerID }).Distinct().Count(n => n.CustomerID.StartsWith("A"))); + } + + [ConditionalFact] + public virtual void Anonymous_complex_distinct_where() + { + AssertQuery( + cs => cs.Select(c => new { A = c.CustomerID + c.City }).Distinct().Where(n => n.A == "ALFKIBerlin")); + } + + [ConditionalFact] + public virtual void Anonymous_complex_distinct_orderby() + { + AssertQuery( + cs => cs.Select(c => new { A = c.CustomerID + c.City }).Distinct().OrderBy(n => n.A)); + } + + [ConditionalFact] + public virtual void Anonymous_complex_distinct_result() + { + AssertQuery( + cs => cs.Select(c => new { A = c.CustomerID + c.City }).Distinct().Count(n => n.A.StartsWith("A"))); + } + + [ConditionalFact] + public virtual void Anonymous_complex_orderby() + { + AssertQuery( + cs => cs.Select(c => new { A = c.CustomerID + c.City }).OrderBy(n => n.A)); + } + + [ConditionalFact] + public virtual void Anonymous_subquery_orderby() + { + AssertQuery( + cs => cs.Where(c => c.Orders.Count > 1).Select(c => new + { + A = c.Orders.OrderByDescending(o => o.OrderID).FirstOrDefault().OrderDate + }).OrderBy(n => n.A)); + } + private static IEnumerable ClientDefaultIfEmpty(IEnumerable source) { return source?.Count() == 0 ? new[] { default(TElement) } : source; diff --git a/src/EFCore.SqlServer/Migrations/Internal/SqlServerMigrationsSqlGenerator.cs b/src/EFCore.SqlServer/Migrations/Internal/SqlServerMigrationsSqlGenerator.cs index 31abc05628c..e805751053a 100644 --- a/src/EFCore.SqlServer/Migrations/Internal/SqlServerMigrationsSqlGenerator.cs +++ b/src/EFCore.SqlServer/Migrations/Internal/SqlServerMigrationsSqlGenerator.cs @@ -541,7 +541,10 @@ private static string ExpandFileName(string fileName) { var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory") as string; if (string.IsNullOrEmpty(dataDirectory)) + { dataDirectory = AppDomain.CurrentDomain.BaseDirectory; + } + fileName = Path.Combine(dataDirectory, fileName.Substring("|DataDirectory|".Length)); } #elif NETSTANDARD1_3 diff --git a/src/EFCore.SqlServer/Query/Expressions/Internal/RowNumberExpression.cs b/src/EFCore.SqlServer/Query/Expressions/Internal/RowNumberExpression.cs index d428580560d..959e24c8322 100644 --- a/src/EFCore.SqlServer/Query/Expressions/Internal/RowNumberExpression.cs +++ b/src/EFCore.SqlServer/Query/Expressions/Internal/RowNumberExpression.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query.Sql.Internal; @@ -23,23 +24,13 @@ public class RowNumberExpression : Expression /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public RowNumberExpression( - [NotNull] ColumnExpression columnExpression, - [NotNull] IReadOnlyList orderings) + public RowNumberExpression([NotNull] IReadOnlyList orderings) { - Check.NotNull(columnExpression, nameof(columnExpression)); Check.NotNull(orderings, nameof(orderings)); - ColumnExpression = columnExpression; _orderings.AddRange(orderings); } - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public virtual ColumnExpression ColumnExpression { get; } - /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -56,7 +47,7 @@ public RowNumberExpression( /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public override Type Type => ColumnExpression.Type; + public override Type Type => typeof(int); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used @@ -85,8 +76,6 @@ protected override Expression Accept(ExpressionVisitor visitor) /// protected override Expression VisitChildren(ExpressionVisitor visitor) { - var newColumnExpression = visitor.Visit(ColumnExpression) as ColumnExpression; - var newOrderings = new List(); var recreate = false; foreach (var ordering in _orderings) @@ -96,13 +85,35 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) recreate |= newOrdering.Expression != ordering.Expression; } - if (recreate || - ((newColumnExpression != null) && (ColumnExpression != newColumnExpression))) + return recreate ? new RowNumberExpression(newOrderings) : this; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) { - return new RowNumberExpression(newColumnExpression ?? ColumnExpression, newOrderings); + return true; } - return this; + return obj.GetType() == GetType() && Equals((RowNumberExpression)obj); } + + private bool Equals([NotNull] RowNumberExpression other) => _orderings.SequenceEqual(other._orderings); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override int GetHashCode() + => _orderings.Aggregate(0, (current, ordering) => current + ((current * 397) ^ ordering.GetHashCode())); } } diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryModelVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryModelVisitor.cs index c7b65e5c1b6..3b78099fe0d 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryModelVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryModelVisitor.cs @@ -1,6 +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; using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; @@ -48,18 +49,16 @@ public override void VisitQueryModel(QueryModel queryModel) { var visitor = new RowNumberPagingExpressionVisitor(); - SelectExpression mainSelectExpression; - if (QueriesBySource.TryGetValue(queryModel.MainFromClause, out mainSelectExpression)) + if (QueriesBySource.TryGetValue(queryModel.MainFromClause, out SelectExpression mainSelectExpression)) { visitor.Visit(mainSelectExpression); } foreach (var additionalSource in queryModel.BodyClauses.OfType()) { - SelectExpression additionalFromExpression; - if (QueriesBySource.TryGetValue(additionalSource, out additionalFromExpression)) + if (QueriesBySource.TryGetValue(additionalSource, out SelectExpression additionalFromExpression)) { - visitor.Visit(mainSelectExpression); + visitor.Visit(additionalFromExpression); } } } @@ -67,17 +66,16 @@ public override void VisitQueryModel(QueryModel queryModel) private class RowNumberPagingExpressionVisitor : ExpressionVisitorBase { - public override Expression Visit(Expression node) + public override Expression Visit(Expression expression) { - var existsExpression = node as ExistsExpression; - if (existsExpression != null) + if (expression is ExistsExpression existsExpression) { return VisitExistExpression(existsExpression); } - var selectExpression = node as SelectExpression; - - return selectExpression != null ? VisitSelectExpression(selectExpression) : base.Visit(node); + return expression is SelectExpression selectExpression + ? VisitSelectExpression(selectExpression) + : base.Visit(expression); } private static bool RequiresRowNumberPaging(SelectExpression selectExpression) @@ -97,29 +95,7 @@ private Expression VisitSelectExpression(SelectExpression selectExpression) foreach (var projection in subQuery.Projection) { - var alias = projection as AliasExpression; - var column = projection as ColumnExpression; - - if (column != null) - { - column = new ColumnExpression(column.Name, column.Property, subQuery); - selectExpression.AddToProjection(column); - continue; - } - - column = alias?.TryGetColumnExpression(); - - if (column != null) - { - column = new ColumnExpression(alias.Alias ?? column.Name, column.Property, subQuery); - alias = new AliasExpression(alias.Alias, column); - selectExpression.AddToProjection(alias); - } - else - { - column = new ColumnExpression(alias?.Alias, alias.Expression.Type, subQuery); - selectExpression.AddToProjection(column); - } + selectExpression.AddToProjection(projection.LiftExpressionFromSubquery(subQuery)); } if (subQuery.OrderBy.Count == 0) @@ -128,18 +104,23 @@ private Expression VisitSelectExpression(SelectExpression selectExpression) new Ordering(new SqlFunctionExpression("@@RowCount", typeof(int)), OrderingDirection.Asc)); } - var columnExpression = new ColumnExpression(RowNumberColumnName, typeof(int), subQuery); - var rowNumber = new RowNumberExpression(columnExpression, subQuery.OrderBy); + var innerRowNumberExpression = new AliasExpression( + RowNumberColumnName, + new RowNumberExpression(subQuery.OrderBy)); subQuery.ClearOrderBy(); - subQuery.AddToProjection(rowNumber, false); + subQuery.AddToProjection(innerRowNumberExpression, resetProjectStar: false); + + var rowNumberReferenceExpression = new ColumnReferenceExpression(innerRowNumberExpression, subQuery); var offset = subQuery.Offset ?? Expression.Constant(0); if (subQuery.Offset != null) { selectExpression.AddToPredicate - (Expression.GreaterThan(columnExpression, offset)); + (Expression.GreaterThan(rowNumberReferenceExpression, offset)); + + subQuery.Offset = null; } if (subQuery.Limit != null) @@ -154,7 +135,9 @@ var limitExpression : Expression.Add(offset, subQuery.Limit); selectExpression.AddToPredicate( - Expression.LessThanOrEqual(columnExpression, limitExpression)); + Expression.LessThanOrEqual(rowNumberReferenceExpression, limitExpression)); + + subQuery.Limit = null; } if (selectExpression.Alias != null) @@ -167,15 +150,15 @@ var limitExpression private Expression VisitExistExpression(ExistsExpression existsExpression) { - var newExpression = Visit(existsExpression.Expression); - var subSelectExpression = newExpression as SelectExpression; - if (subSelectExpression != null - && subSelectExpression.Limit == null - && subSelectExpression.Offset == null) + var newSubquery = (SelectExpression)Visit(existsExpression.Subquery); + + if (newSubquery.Limit == null + && newSubquery.Offset == null) { - subSelectExpression.ClearOrderBy(); + newSubquery.ClearOrderBy(); } - return new ExistsExpression(newExpression); + + return new ExistsExpression(newSubquery); } } } diff --git a/src/EFCore.SqlServer/Query/Sql/Internal/SqlServerQuerySqlGenerator.cs b/src/EFCore.SqlServer/Query/Sql/Internal/SqlServerQuerySqlGenerator.cs index 8e2541b2be2..27659cf3a6b 100644 --- a/src/EFCore.SqlServer/Query/Sql/Internal/SqlServerQuerySqlGenerator.cs +++ b/src/EFCore.SqlServer/Query/Sql/Internal/SqlServerQuerySqlGenerator.cs @@ -50,11 +50,6 @@ public override Expression VisitCrossJoinLateral(CrossJoinLateralExpression cros /// protected override void GenerateLimitOffset(SelectExpression selectExpression) { - if (selectExpression.Projection.OfType().Any()) - { - return; - } - if (selectExpression.Offset != null && !selectExpression.OrderBy.Any()) { @@ -74,7 +69,7 @@ public virtual Expression VisitRowNumber(RowNumberExpression rowNumberExpression Sql.Append("ROW_NUMBER() OVER("); GenerateOrderBy(rowNumberExpression.Orderings); - Sql.Append(") AS ").Append(SqlGenerator.DelimitIdentifier(rowNumberExpression.ColumnExpression.Name)); + Sql.Append(")"); return rowNumberExpression; } @@ -103,9 +98,9 @@ public override Expression VisitSqlFunction(SqlFunctionExpression sqlFunctionExp return base.VisitSqlFunction(sqlFunctionExpression); } - protected override void GenerateProjection([NotNull] Expression projection) + protected override void GenerateProjection(Expression projection) { - var newProjection = (projection as AliasExpression)?.Expression?.NodeType == ExpressionType.Coalesce + var newProjection = (projection as BinaryExpression)?.NodeType == ExpressionType.Coalesce && projection.Type.UnwrapNullableType() == typeof(bool) ? new ExplicitCastExpression(projection, projection.Type) : projection; diff --git a/src/EFCore/Query/Expressions/Internal/NullConditionalExpression.cs b/src/EFCore/Query/Expressions/Internal/NullConditionalExpression.cs index 04951a9d717..807743656e5 100644 --- a/src/EFCore/Query/Expressions/Internal/NullConditionalExpression.cs +++ b/src/EFCore/Query/Expressions/Internal/NullConditionalExpression.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Utilities; @@ -142,6 +143,39 @@ var newAccessOperation return this; } + /// + /// Tests if this object is considered equal to another. + /// + /// The object to compare with the current object. + /// + /// true if the objects are considered equal, false if they are not. + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((NullConditionalExpression)obj); + } + + private bool Equals(NullConditionalExpression other) + => Equals(AccessOperation, other.AccessOperation); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() => AccessOperation.GetHashCode(); + /// /// Returns a textual representation of the . /// @@ -149,6 +183,29 @@ var newAccessOperation /// A textual representation of the . /// public override string ToString() - => $"(?{AccessOperation}?)"; // TODO: Improve this + { + if (AccessOperation is MemberExpression memberExpression) + { + return NullableCaller + "?." + memberExpression.Member.Name; + } + + if (AccessOperation is MethodCallExpression methodCallExpression) + { + if (methodCallExpression.Object != null) + { + return NullableCaller + + "?." + methodCallExpression.Method.Name + + "(" + string.Join(",", methodCallExpression.Arguments) + ")"; + } + + var method = methodCallExpression.Method; + + return method.DeclaringType?.Name + "." + method.Name + + "(?" + NullableCaller + "?, " + + string.Join(",", methodCallExpression.Arguments.Skip(1)) + ")"; + } + + return $"?{AccessOperation}?"; + } } } diff --git a/src/EFCore/Query/Internal/ExpressionEqualityComparer.cs b/src/EFCore/Query/Internal/ExpressionEqualityComparer.cs index b1f73e3aace..efde7e84fdf 100644 --- a/src/EFCore/Query/Internal/ExpressionEqualityComparer.cs +++ b/src/EFCore/Query/Internal/ExpressionEqualityComparer.cs @@ -253,14 +253,7 @@ public virtual int GetHashCode(Expression obj) } case ExpressionType.Extension: { - var nullConditionalExpression = obj as NullConditionalExpression; - - if (nullConditionalExpression == null) - { - goto default; - } - - hashCode += (hashCode * 397) ^ GetHashCode(nullConditionalExpression.AccessOperation); + hashCode += (hashCode * 397) ^ obj.GetHashCode(); break; } @@ -380,6 +373,8 @@ public bool Compare(Expression a, Expression b) return CompareMemberInit((MemberInitExpression)a, (MemberInitExpression)b); case ExpressionType.ListInit: return CompareListInit((ListInitExpression)a, (ListInitExpression)b); + case ExpressionType.Extension: + return CompareExtension(a, b); default: throw new NotImplementedException(); } @@ -440,8 +435,7 @@ private bool CompareParameter(ParameterExpression a, ParameterExpression b) { if (_parameterScope != null) { - ParameterExpression mapped; - if (_parameterScope.TryGetValue(a, out mapped)) + if (_parameterScope.TryGetValue(a, out ParameterExpression mapped)) { return mapped.Name == b.Name && mapped.Type == b.Type; @@ -564,6 +558,9 @@ private static bool CompareMemberList(IReadOnlyList a, IReadOnlyList private bool CompareNewArray(NewArrayExpression a, NewArrayExpression b) => CompareExpressionList(a.Expressions, b.Expressions); + private bool CompareExtension(Expression a, Expression b) + => a.Equals(b); + private bool CompareInvocation(InvocationExpression a, InvocationExpression b) => Compare(a.Expression, b.Expression) && CompareExpressionList(a.Arguments, b.Arguments); diff --git a/test/EFCore.SqlServer.FunctionalTests/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ComplexNavigationsQuerySqlServerTest.cs index 04c9b78556f..6582b7a4dcf 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ComplexNavigationsQuerySqlServerTest.cs @@ -2494,7 +2494,7 @@ public override void GroupJoin_on_right_side_being_a_subquery() AssertSql( @"@__p_0: 2 -SELECT [l2].[Id], [t].[Name], [t].[Id] +SELECT [l2].[Id], [t].[Name] FROM [Level2] AS [l2] LEFT JOIN ( SELECT TOP(@__p_0) [x].* diff --git a/test/EFCore.SqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs index 97ba826243e..71254ad5442 100644 --- a/test/EFCore.SqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs @@ -956,7 +956,6 @@ THEN 1 ELSE 0 public override void Select_ternary_operation_with_has_value_not_null() { - // TODO: Optimize this query (See #4267) base.Select_ternary_operation_with_has_value_not_null(); AssertSql( @@ -2360,7 +2359,8 @@ LEFT JOIN ( SELECT [g].* FROM [Gear] AS [g] WHERE [g].[Discriminator] IN (N'Officer', N'Gear') -) AS [t0] ON ([t].[GearNickName] = [t0].[Nickname]) AND ([t].[GearSquadId] = [t0].[SquadId])", +) AS [t0] ON ([t].[GearNickName] = [t0].[Nickname]) AND ([t].[GearSquadId] = [t0].[SquadId]) +ORDER BY [t].[GearNickName], [t].[GearSquadId]", Sql); } diff --git a/test/EFCore.SqlServer.FunctionalTests/IncludeSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/IncludeSqlServerTest.cs index c5fd33bda15..6a2a0cffb3d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/IncludeSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/IncludeSqlServerTest.cs @@ -366,15 +366,15 @@ ORDER BY [oo].[OrderDate] DESC SELECT [c.Orders].[OrderID], [c.Orders].[CustomerID], [c.Orders].[EmployeeID], [c.Orders].[OrderDate] FROM [Orders] AS [c.Orders] INNER JOIN ( - SELECT TOP(1) [c0].* - FROM [Customers] AS [c0] - WHERE [c0].[CustomerID] LIKE N'W' + N'%' AND (CHARINDEX(N'W', [c0].[CustomerID]) = 1) - ORDER BY ( + SELECT TOP(1) [c0].*, ( SELECT TOP(1) [oo0].[OrderDate] FROM [Orders] AS [oo0] WHERE [c0].[CustomerID] = [oo0].[CustomerID] ORDER BY [oo0].[OrderDate] DESC - ) DESC, [c0].[CustomerID] + ) AS [c] + FROM [Customers] AS [c0] + WHERE [c0].[CustomerID] LIKE N'W' + N'%' AND (CHARINDEX(N'W', [c0].[CustomerID]) = 1) + ORDER BY [c] DESC, [c0].[CustomerID] ) AS [t] ON [c.Orders].[CustomerID] = [t].[CustomerID] ORDER BY ( SELECT TOP(1) [oo1].[OrderDate] @@ -508,15 +508,15 @@ ORDER BY [o].[EmployeeID] SELECT [c.Orders].[OrderID], [c.Orders].[CustomerID], [c.Orders].[EmployeeID], [c.Orders].[OrderDate] FROM [Orders] AS [c.Orders] INNER JOIN ( - SELECT TOP(1) [c0].* - FROM [Customers] AS [c0] - WHERE [c0].[CustomerID] = N'ALFKI' - ORDER BY ( + SELECT TOP(1) [c0].*, ( SELECT TOP(1) [o0].[OrderDate] FROM [Orders] AS [o0] WHERE [c0].[CustomerID] = [o0].[CustomerID] ORDER BY [o0].[EmployeeID] - ), [c0].[CustomerID] + ) AS [c] + FROM [Customers] AS [c0] + WHERE [c0].[CustomerID] = N'ALFKI' + ORDER BY [c], [c0].[CustomerID] ) AS [t] ON [c.Orders].[CustomerID] = [t].[CustomerID] ORDER BY ( SELECT TOP(1) [o1].[OrderDate] @@ -524,7 +524,7 @@ FROM [Orders] AS [o1] WHERE [t].[CustomerID] = [o1].[CustomerID] ORDER BY [o1].[EmployeeID] ), [t].[CustomerID]", - Sql); + Sql); } public override void Include_collection_as_no_tracking(bool useString) @@ -1376,17 +1376,17 @@ SELECT TOP(1) [oo].[OrderDate] FROM [Orders] AS [oo] WHERE [c].[CustomerID] = [oo].[CustomerID] ORDER BY [oo].[OrderDate] DESC - ) AS [c0_0], [c].[CustomerID] + ) AS [c], [c].[CustomerID] FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'W' + N'%' AND (CHARINDEX(N'W', [c].[CustomerID]) = 1) - ORDER BY [c0_0] DESC, [c].[CustomerID] + ORDER BY [c] DESC, [c].[CustomerID] ) AS [c0] ON [o].[CustomerID] = [c0].[CustomerID] -ORDER BY [c0].[c0_0] DESC, [c0].[CustomerID], [o].[OrderID] +ORDER BY [c0].[c] DESC, [c0].[CustomerID], [o].[OrderID] SELECT [o0].[OrderID], [o0].[ProductID], [o0].[Discount], [o0].[Quantity], [o0].[UnitPrice] FROM [Order Details] AS [o0] INNER JOIN ( - SELECT DISTINCT [c0].[c0_0], [c0].[CustomerID], [o].[OrderID] + SELECT DISTINCT [c0].[c], [c0].[CustomerID], [o].[OrderID] FROM [Orders] AS [o] INNER JOIN ( SELECT DISTINCT TOP(1) ( @@ -1394,13 +1394,13 @@ SELECT TOP(1) [oo].[OrderDate] FROM [Orders] AS [oo] WHERE [c].[CustomerID] = [oo].[CustomerID] ORDER BY [oo].[OrderDate] DESC - ) AS [c0_0], [c].[CustomerID] + ) AS [c], [c].[CustomerID] FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'W' + N'%' AND (CHARINDEX(N'W', [c].[CustomerID]) = 1) - ORDER BY [c0_0] DESC, [c].[CustomerID] + ORDER BY [c] DESC, [c].[CustomerID] ) AS [c0] ON [o].[CustomerID] = [c0].[CustomerID] ) AS [o1] ON [o0].[OrderID] = [o1].[OrderID] -ORDER BY [o1].[c0_0] DESC, [o1].[CustomerID], [o1].[OrderID]", +ORDER BY [o1].[c] DESC, [o1].[CustomerID], [o1].[OrderID]", Sql); } diff --git a/test/EFCore.SqlServer.FunctionalTests/NullSemanticsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/NullSemanticsQuerySqlServerTest.cs index 467e67d76c5..3f2106052b1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/NullSemanticsQuerySqlServerTest.cs @@ -1089,12 +1089,17 @@ public override void Where_comparison_null_constant_and_null_parameter() base.Where_comparison_null_constant_and_null_parameter(); Assert.Equal( - @"SELECT [e].[Id] + @"@__prm_0: (Size = 4000) (DbType = String) + +SELECT [e].[Id] FROM [NullSemanticsEntity1] AS [e] +WHERE @__prm_0 IS NULL + +@__prm_0: (Size = 4000) (DbType = String) SELECT [e].[Id] FROM [NullSemanticsEntity1] AS [e] -WHERE 0 = 1", +WHERE @__prm_0 IS NOT NULL", Sql); } @@ -1103,12 +1108,17 @@ public override void Where_comparison_null_constant_and_nonnull_parameter() base.Where_comparison_null_constant_and_nonnull_parameter(); Assert.Equal( - @"SELECT [e].[Id] + @"@__prm_0: Foo (Size = 4000) + +SELECT [e].[Id] FROM [NullSemanticsEntity1] AS [e] -WHERE 0 = 1 +WHERE @__prm_0 IS NULL + +@__prm_0: Foo (Size = 4000) SELECT [e].[Id] -FROM [NullSemanticsEntity1] AS [e]", +FROM [NullSemanticsEntity1] AS [e] +WHERE @__prm_0 IS NOT NULL", Sql); } diff --git a/test/EFCore.SqlServer.FunctionalTests/QueryNavigationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/QueryNavigationsSqlServerTest.cs index b9b07d44b65..ba1d34b89e8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/QueryNavigationsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/QueryNavigationsSqlServerTest.cs @@ -1120,15 +1120,6 @@ SELECT [o2].[OrderID] FROM [Orders] AS [o2] WHERE @_outer_CustomerID = [o2].[CustomerID] -SELECT [o4].[OrderID] -FROM [Orders] AS [o4] - -@_outer_CustomerID: ANATR (Size = 450) - -SELECT [o2].[OrderID] -FROM [Orders] AS [o2] -WHERE @_outer_CustomerID = [o2].[CustomerID] - SELECT [o4].[OrderID] FROM [Orders] AS [o4]", Sql); diff --git a/test/EFCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs index a2e11265536..158bad11635 100644 --- a/test/EFCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs @@ -6011,9 +6011,9 @@ public override void Select_null_coalesce_operator() base.Select_null_coalesce_operator(); Assert.Equal( - @"SELECT [c].[CustomerID], [c].[CompanyName], COALESCE([c].[Region], N'ZZ') + @"SELECT [c].[CustomerID], [c].[CompanyName], COALESCE([c].[Region], N'ZZ') AS [c] FROM [Customers] AS [c] -ORDER BY COALESCE([c].[Region], N'ZZ')", +ORDER BY [c]", Sql); } @@ -6445,7 +6445,7 @@ public override void Filter_coalesce_operator() Assert.Equal( @"SELECT [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] -WHERE COALESCE([c].[CompanyName], [c].[ContactName]) = N'The Big Cheese'", +WHERE (COALESCE([c].[CompanyName], [c].[ContactName])) = N'The Big Cheese'", Sql); } @@ -6461,11 +6461,11 @@ SELECT DISTINCT [t0].* FROM ( SELECT [t].* FROM ( - 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] + 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], COALESCE([c].[Region], N'ZZ') AS [c] FROM [Customers] AS [c] - ORDER BY COALESCE([c].[Region], N'ZZ') + ORDER BY [c] ) AS [t] - ORDER BY COALESCE([t].[Region], N'ZZ') + ORDER BY [t].[c] OFFSET @__p_1 ROWS ) AS [t0]", Sql); @@ -6477,13 +6477,12 @@ public override void Select_take_null_coalesce_operator() Assert.Equal(@"@__p_0: 5 -SELECT TOP(@__p_0) [c].[CustomerID], [c].[CompanyName], COALESCE([c].[Region], N'ZZ') +SELECT TOP(@__p_0) [c].[CustomerID], [c].[CompanyName], COALESCE([c].[Region], N'ZZ') AS [c] FROM [Customers] AS [c] -ORDER BY COALESCE([c].[Region], N'ZZ')", +ORDER BY [c]", Sql); } - // TODO: See issue#6703 [SqlServerCondition(SqlServerCondition.SupportsOffset)] public override void Select_take_skip_null_coalesce_operator() { @@ -6495,11 +6494,11 @@ public override void Select_take_skip_null_coalesce_operator() SELECT [t].* FROM ( - SELECT TOP(@__p_0) [c].[CustomerID], [c].[CompanyName], COALESCE([c].[Region], N'ZZ') AS [Coalesce] + SELECT TOP(@__p_0) [c].[CustomerID], [c].[CompanyName], COALESCE([c].[Region], N'ZZ') AS [c] FROM [Customers] AS [c] - ORDER BY [Coalesce] + ORDER BY [c] ) AS [t] -ORDER BY [t].[Coalesce] +ORDER BY [t].[c] OFFSET @__p_1 ROWS", Sql); } @@ -6515,11 +6514,11 @@ public override void Select_take_skip_null_coalesce_operator2() SELECT [t].* FROM ( - SELECT TOP(@__p_0) [c].[CustomerID], [c].[CompanyName], [c].[Region] + SELECT TOP(@__p_0) [c].[CustomerID], [c].[CompanyName], [c].[Region], COALESCE([c].[Region], N'ZZ') AS [c] FROM [Customers] AS [c] - ORDER BY COALESCE([c].[Region], N'ZZ') + ORDER BY [c] ) AS [t] -ORDER BY COALESCE([t].[Region], N'ZZ') +ORDER BY [t].[c] OFFSET @__p_1 ROWS", Sql); } @@ -6535,11 +6534,11 @@ public override void Select_take_skip_null_coalesce_operator3() SELECT [t].* FROM ( - 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] + 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], COALESCE([c].[Region], N'ZZ') AS [c] FROM [Customers] AS [c] - ORDER BY COALESCE([c].[Region], N'ZZ') + ORDER BY [c] ) AS [t] -ORDER BY COALESCE([t].[Region], N'ZZ') +ORDER BY [t].[c] OFFSET @__p_1 ROWS", Sql); } @@ -7095,13 +7094,13 @@ public override void Select_expression_references_are_updated_correctly_with_sub Assert.Equal( @"@__nextYear_0: 2017 -SELECT [t].[c0] +SELECT [t].[c] FROM ( - SELECT DISTINCT DATEPART(year, [o].[OrderDate]) AS [c0] + SELECT DISTINCT DATEPART(year, [o].[OrderDate]) AS [c] FROM [Orders] AS [o] WHERE [o].[OrderDate] IS NOT NULL ) AS [t] -WHERE [t].[c0] < @__nextYear_0", +WHERE [t].[c] < @__nextYear_0", Sql); } @@ -7522,6 +7521,127 @@ THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) Sql); } + public override void Anonymous_member_distinct_where() + { + base.Anonymous_member_distinct_where(); + + Assert.Equal( + @"SELECT [t].[CustomerID] +FROM ( + SELECT DISTINCT [c].[CustomerID] + FROM [Customers] AS [c] +) AS [t] +WHERE [t].[CustomerID] = N'ALFKI'", + Sql); + } + + public override void Anonymous_member_distinct_orderby() + { + base.Anonymous_member_distinct_orderby(); + + Assert.Equal( + @"SELECT [t].[CustomerID] +FROM ( + SELECT DISTINCT [c].[CustomerID] + FROM [Customers] AS [c] +) AS [t] +ORDER BY [t].[CustomerID]", + Sql); + } + + public override void Anonymous_member_distinct_result() + { + base.Anonymous_member_distinct_result(); + + Assert.Equal( + @"SELECT COUNT(*) +FROM ( + SELECT DISTINCT [c].[CustomerID] + FROM [Customers] AS [c] +) AS [t] +WHERE [t].[CustomerID] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [t].[CustomerID]) = 1)", + Sql); + } + + public override void Anonymous_complex_distinct_where() + { + base.Anonymous_complex_distinct_where(); + + Assert.Equal( + @"SELECT [t].[c] +FROM ( + SELECT DISTINCT [c].[CustomerID] + [c].[City] AS [c] + FROM [Customers] AS [c] +) AS [t] +WHERE [t].[c] = N'ALFKIBerlin'", + Sql); + } + + public override void Anonymous_complex_distinct_orderby() + { + base.Anonymous_complex_distinct_orderby(); + + Assert.Equal( + @"SELECT [t].[c] +FROM ( + SELECT DISTINCT [c].[CustomerID] + [c].[City] AS [c] + FROM [Customers] AS [c] +) AS [t] +ORDER BY [t].[c]", + Sql); + } + + public override void Anonymous_complex_distinct_result() + { + base.Anonymous_complex_distinct_result(); + + Assert.Equal( + @"SELECT COUNT(*) +FROM ( + SELECT DISTINCT [c].[CustomerID] + [c].[City] AS [c] + FROM [Customers] AS [c] +) AS [t] +WHERE [t].[c] LIKE N'A' + N'%' AND (CHARINDEX(N'A', [t].[c]) = 1)", + Sql); + } + + public override void Anonymous_complex_orderby() + { + base.Anonymous_complex_orderby(); + + Assert.Equal( + @"SELECT [c].[CustomerID] + [c].[City] AS [c] +FROM [Customers] AS [c] +ORDER BY [c]", + Sql); + } + + public override void Anonymous_subquery_orderby() + { + base.Anonymous_subquery_orderby(); + + Assert.Equal( + @"SELECT ( + SELECT TOP(1) [o2].[OrderDate] + FROM [Orders] AS [o2] + WHERE [c].[CustomerID] = [o2].[CustomerID] + ORDER BY [o2].[OrderID] DESC +) +FROM [Customers] AS [c] +WHERE ( + SELECT COUNT(*) + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID] +) > 1 +ORDER BY ( + SELECT TOP(1) [o0].[OrderDate] + FROM [Orders] AS [o0] + WHERE [c].[CustomerID] = [o0].[CustomerID] + ORDER BY [o0].[OrderID] DESC +)", + Sql); + } + private const string FileLineEnding = @" "; diff --git a/test/EFCore.SqlServer.FunctionalTests/RowNumberPagingTest.cs b/test/EFCore.SqlServer.FunctionalTests/RowNumberPagingTest.cs index 7ce758a31c3..115251f04fc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/RowNumberPagingTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/RowNumberPagingTest.cs @@ -103,9 +103,9 @@ public override void Join_Customers_Orders_Projection_With_String_Concat_Skip_Ta @"@__p_0: 10 @__p_1: 5 -SELECT [t].[c0], [t].[OrderID] +SELECT [t].[c], [t].[OrderID] FROM ( - SELECT ([c].[ContactName] + N' ') + [c].[ContactTitle] AS [c0], [o].[OrderID], ROW_NUMBER() OVER(ORDER BY [o].[OrderID]) AS [__RowNumber__] + SELECT ([c].[ContactName] + N' ') + [c].[ContactTitle] AS [c], [o].[OrderID], ROW_NUMBER() OVER(ORDER BY [o].[OrderID]) AS [__RowNumber__] FROM [Customers] AS [c] INNER JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] ) AS [t] @@ -122,7 +122,7 @@ public override void Join_Customers_Orders_Orders_Skip_Take_Same_Properties() @"@__p_0: 10 @__p_1: 5 -SELECT [t].[OrderID], [t].[CustomerID], [t].[c0] AS [c0], [t].[ContactName], [t].[c1] AS [c1] +SELECT [t].[OrderID], [t].[CustomerID], [t].[c0], [t].[ContactName], [t].[c1] FROM ( SELECT [o].[OrderID], [ca].[CustomerID], [cb].[CustomerID] AS [c0], [ca].[ContactName], [cb].[ContactName] AS [c1], ROW_NUMBER() OVER(ORDER BY [o].[OrderID]) AS [__RowNumber__] FROM [Orders] AS [o] @@ -190,11 +190,11 @@ SELECT DISTINCT [t0].* FROM ( SELECT [t1].* FROM ( - SELECT [t].*, ROW_NUMBER() OVER(ORDER BY COALESCE([t].[Region], N'ZZ')) AS [__RowNumber__] + SELECT [t].*, ROW_NUMBER() OVER(ORDER BY [t].[c]) AS [__RowNumber__] FROM ( - 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] + 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], COALESCE([c].[Region], N'ZZ') AS [c] FROM [Customers] AS [c] - ORDER BY COALESCE([c].[Region], N'ZZ') + ORDER BY [c] ) AS [t] ) AS [t1] WHERE [t1].[__RowNumber__] > @__p_1 @@ -211,15 +211,15 @@ public override void Select_take_skip_null_coalesce_operator() SELECT [t0].* FROM ( - SELECT [t].*, ROW_NUMBER() OVER(ORDER BY [t].[Coalesce]) AS [__RowNumber__] + SELECT [t].*, ROW_NUMBER() OVER(ORDER BY [t].[c]) AS [__RowNumber__] FROM ( - SELECT TOP(@__p_0) [c].[CustomerID], [c].[CompanyName], COALESCE([c].[Region], N'ZZ') AS [Coalesce] + SELECT TOP(@__p_0) [c].[CustomerID], [c].[CompanyName], COALESCE([c].[Region], N'ZZ') AS [c] FROM [Customers] AS [c] - ORDER BY [Coalesce] + ORDER BY [c] ) AS [t] ) AS [t0] WHERE [t0].[__RowNumber__] > @__p_1 -ORDER BY [t0].[Coalesce]", +ORDER BY [t0].[c]", Sql); } @@ -355,9 +355,9 @@ public override void Skip_Take_Any() SELECT CASE WHEN EXISTS ( - SELECT [t].[c0] + SELECT [t].[c] FROM ( - SELECT 1 AS [c0], ROW_NUMBER() OVER(ORDER BY [c].[ContactName]) AS [__RowNumber__] + SELECT 1 AS [c], ROW_NUMBER() OVER(ORDER BY [c].[ContactName]) AS [__RowNumber__] FROM [Customers] AS [c] ) AS [t] WHERE ([t].[__RowNumber__] > @__p_0) AND ([t].[__RowNumber__] <= (@__p_0 + @__p_1))) @@ -376,9 +376,9 @@ public override void Skip_Take_All() SELECT CASE WHEN NOT EXISTS ( - SELECT [t].[c0] + SELECT [t].[c] FROM ( - SELECT 1 AS [c0], ROW_NUMBER() OVER(ORDER BY [c].[ContactName]) AS [__RowNumber__] + SELECT 1 AS [c], ROW_NUMBER() OVER(ORDER BY [c].[ContactName]) AS [__RowNumber__] FROM [Customers] AS [c] WHERE LEN([c].[CustomerID]) <> 5 ) AS [t]