From bb66f16982aca14d1e60e58530794af7b9e4cbb3 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 --- .../RelationalExpressionExtensions.cs | 43 +- .../EqualityPredicateInExpressionOptimizer.cs | 54 +- .../Internal/IncludeExpressionVisitor.cs | 133 +-- .../IsNullExpressionBuildingVisitor.cs | 69 +- .../Internal/MaterializerFactory.cs | 4 +- ...ationalEntityQueryableExpressionVisitor.cs | 14 +- .../RelationalProjectionExpressionVisitor.cs | 50 +- .../SqlTranslatingExpressionVisitor.cs | 101 +- ...ranslatingExpressionVisitorDependencies.cs | 27 - .../Query/Expressions/AliasExpression.cs | 75 +- .../Query/Expressions/ColumnExpression.cs | 44 +- .../Expressions/ColumnReferenceExpression.cs | 185 +++ .../Expressions/NotNullableExpression.cs | 34 +- .../Query/Expressions/NullableExpression.cs | 34 +- .../Expressions/ProjectStarExpression.cs | 151 +++ .../Query/Expressions/SelectExpression.cs | 1037 ++++++++--------- .../SelectExpressionDependencies.cs | 22 +- .../RelationalResultOperatorHandler.cs | 117 +- .../Query/RelationalQueryModelVisitor.cs | 166 ++- .../Query/Sql/DefaultQuerySqlGenerator.cs | 187 ++- .../Query/Sql/ISqlExpressionVisitor.cs | 18 + .../FromSqlNonComposedQuerySqlGenerator.cs | 8 +- .../QueryTestBase.cs | 65 +- .../SqlServerMigrationsSqlGenerator.cs | 3 + .../Internal/RowNumberExpression.cs | 46 +- .../Internal/SqlServerQueryModelVisitor.cs | 58 +- .../Internal/SqlServerQuerySqlGenerator.cs | 11 +- .../Internal/NullConditionalExpression.cs | 69 +- .../Internal/ExpressionEqualityComparer.cs | 17 +- .../ComplexNavigationsQuerySqlServerTest.cs | 2 +- .../GearsOfWarQuerySqlServerTest.cs | 10 +- .../IncludeSqlServerTest.cs | 36 +- .../NullSemanticsQuerySqlServerTest.cs | 30 +- .../QueryNavigationsSqlServerTest.cs | 9 - .../QuerySqlServerTest.cs | 162 ++- .../RowNumberPagingTest.cs | 28 +- 36 files changed, 1680 insertions(+), 1439 deletions(-) create mode 100644 src/EFCore.Relational/Query/Expressions/ColumnReferenceExpression.cs create mode 100644 src/EFCore.Relational/Query/Expressions/ProjectStarExpression.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..0b406be5611 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,21 @@ 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; + var projection = innerJoinSelectExpression.Projection[innerJoinSelectExpression.AddToProjection(orderingExpression)]; - 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); + var newExpression = projection.LiftExpressionFromSubquery(innerJoinExpression); targetSelectExpression.AddToOrderBy(new Ordering(newExpression, ordering.OrderingDirection)); } - if ((innerJoinSelectExpression.Limit == null) - && (innerJoinSelectExpression.Offset == null)) + if (innerJoinSelectExpression.Limit == null + && innerJoinSelectExpression.Offset == null) { innerJoinSelectExpression.ClearOrderBy(); } @@ -620,21 +570,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 +602,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..6cc4d102305 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. /// @@ -199,8 +137,7 @@ public override int GetHashCode() { unchecked { - // ReSharper disable once NonReadonlyMemberInGetHashCode - return (_expression.GetHashCode() * 397) ^ (_alias?.GetHashCode() ?? 0); + return (_expression.GetHashCode() * 397) ^ _alias.GetHashCode(); } } } diff --git a/src/EFCore.Relational/Query/Expressions/ColumnExpression.cs b/src/EFCore.Relational/Query/Expressions/ColumnExpression.cs index c295c7d605c..319d3204a41 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. @@ -106,7 +86,7 @@ protected override Expression Accept(ExpressionVisitor visitor) } /// - /// Reduces the node and then calls the method passing the + /// Reduces the node and then calls the method passing the /// reduced expression. /// Throws an exception if the node isn't reducible. /// @@ -121,10 +101,10 @@ 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))) + // Compare on names only because multiple properties can map to same column in inheritance scenario + => Name == other.Name && Type == other.Type - && _tableExpression.Equals(other._tableExpression); + && Table.Equals(other.Table); /// /// Tests if this object is considered equal to another. @@ -133,7 +113,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,7 +125,7 @@ public override bool Equals([CanBeNull] object obj) return true; } - return (obj.GetType() == GetType()) + return obj.GetType() == GetType() && Equals((ColumnExpression)obj); } @@ -159,8 +139,10 @@ public override int GetHashCode() { unchecked { - return (_property.GetHashCode() * 397) - ^ _tableExpression.GetHashCode(); + var hashCode = _property?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (_tableExpression?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (Name?.GetHashCode() ?? 0); + 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..34b2d4d0a91 --- /dev/null +++ b/src/EFCore.Relational/Query/Expressions/ColumnReferenceExpression.cs @@ -0,0 +1,185 @@ +// 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; + private readonly Type _type; + + /// + /// 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; + _type = expression.Type; + _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 => _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; + + private bool Equals([NotNull] ColumnReferenceExpression other) + => Equals(_expression, other._expression) + && Equals(_tableExpression, other._tableExpression) + && _type == other._type + && string.Equals(Name, other.Name); + + /// + /// 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); + } + + /// + /// 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(); + hashCode = (hashCode * 397) ^ _type.GetHashCode(); + hashCode = (hashCode * 397) ^ Name.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/NotNullableExpression.cs b/src/EFCore.Relational/Query/Expressions/NotNullableExpression.cs index 68686438565..0982318d691 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) => _operand.Equals(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/ProjectStarExpression.cs b/src/EFCore.Relational/Query/Expressions/ProjectStarExpression.cs new file mode 100644 index 00000000000..3ae7f809661 --- /dev/null +++ b/src/EFCore.Relational/Query/Expressions/ProjectStarExpression.cs @@ -0,0 +1,151 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Query.Sql; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.Query.Expressions +{ + /// + /// A project star expression. + /// + public class ProjectStarExpression : Expression + { + private readonly List _projections; + private readonly TableExpressionBase _tableExpression; + + /// + /// Creates a new instance of a ProjectStarExpression. + /// + /// The list of existing expressions being used from this star projection. + /// The target table expression being represented by this star projection. + public ProjectStarExpression( + [NotNull] List projections, + [NotNull] TableExpressionBase tableExpression) + { + Check.NotNull(tableExpression, nameof(tableExpression)); + + _projections = projections; + _tableExpression = tableExpression; + } + /// + /// Gets or adds given expression in the list of projections being used from this star projection. + /// + /// The expression to be added to projections. + /// + /// The expression present in projections list. + /// + public virtual Expression GetOrAdd([NotNull] Expression expression) + { + var projection = _projections.Find(e => SelectExpression.ExpressionEqualityComparer.Equals(e, expression)); + + if (projection != null) + { + return projection; + } + + _projections.Add(expression); + + return expression; + } + + /// + /// The target table being project starred. + /// + public virtual TableExpressionBase Table => _tableExpression; + + /// + /// 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 => typeof(object); + + /// + /// 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.VisitProjectStar(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((ProjectStarExpression)obj); + } + + private bool Equals([NotNull] ProjectStarExpression other) + => _projections.SequenceEqual(other._projections) + && _tableExpression.Equals(other._tableExpression); + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = _projections.Aggregate(0, (current, projection) => current + ((current * 397) ^ projection.GetHashCode())); + + return (hashCode * 397) ^ _tableExpression.GetHashCode(); + } + } + + /// + /// Creates a representation of the Expression. + /// + /// A representation of the Expression. + public override string ToString() => _tableExpression.Alias + ".*"; + } +} diff --git a/src/EFCore.Relational/Query/Expressions/SelectExpression.cs b/src/EFCore.Relational/Query/Expressions/SelectExpression.cs index f5a150566ee..0a922a7ae87 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,26 @@ 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 + public 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 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 +56,7 @@ public SelectExpression( Check.NotNull(queryCompilationContext, nameof(queryCompilationContext)); Dependencies = dependencies; + _relationalAnnotationProvider = dependencies.RelationalAnnotationProvider; _queryCompilationContext = queryCompilationContext; } @@ -97,6 +103,14 @@ public virtual TableExpressionBase ProjectStarTable [param: CanBeNull] set { _projectStarTable = value; } } + /// + /// Gets or sets the ProjectStarExpression to be used to store actual projection being used out of star projection. + /// + /// + /// The project star expression. + /// + public virtual ProjectStarExpression ProjectStarExpression { get; private set; } + /// /// Type of this expression. /// @@ -104,6 +118,105 @@ public virtual TableExpressionBase ProjectStarTable ? _projection[0].Type : base.Type; + /// + /// The tables making up the FROM part of the SELECT expression. + /// + public virtual IReadOnlyList Tables => _tables; + + /// + /// Gets or sets a value indicating whether this expression projects a single wildcard ('*'). + /// + /// + /// true if this SelectExpression is project star, false if not. + /// + public virtual bool IsProjectStar + { + get + { + return _isProjectStar; + } + set + { + _isProjectStar = value; + + ProjectStarExpression = value ? new ProjectStarExpression(_projection.ToList(), ProjectStarTable) : null; + } + } + + /// + /// Gets or sets a value indicating whether this SelectExpression is DISTINCT. + /// + /// + /// true if this SelectExpression is distinct, false if not. + /// + public virtual bool IsDistinct + { + get { return _isDistinct; } + set + { + if (_offset != null) + { + PushDownSubquery(); + ClearOrderBy(); + } + + _isDistinct = value; + } + } + + /// + /// Gets or sets the LIMIT of this SelectExpression. + /// + /// + /// The limit. + /// + public virtual Expression Limit + { + get { return _limit; } + [param: CanBeNull] + set + { + if (value != null && _limit != null) + { + PushDownSubquery(); + } + + _limit = value; + } + } + + /// + /// Gets or sets the OFFSET of this SelectExpression. + /// + /// + /// The offset. + /// + public virtual Expression Offset + { + get { return _offset; } + [param: CanBeNull] + set + { + if (_limit != null + && value != null) + { + PushDownSubquery(); + } + + _offset = value; + } + } + + /// + /// 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. /// @@ -119,9 +232,9 @@ var selectExpression _limit = _limit, _offset = _offset, _isDistinct = _isDistinct, - _subqueryDepth = _subqueryDepth, - IsProjectStar = IsProjectStar, - Predicate = Predicate + Predicate = Predicate, + ProjectStarTable = ProjectStarTable, + IsProjectStar = IsProjectStar }; if (alias != null) @@ -129,26 +242,28 @@ var selectExpression selectExpression.Alias = _queryCompilationContext.CreateUniqueTableAlias(alias); } + selectExpression._tables.AddRange(_tables); selectExpression._projection.AddRange(_projection); - - selectExpression.AddTables(_tables); - selectExpression.AddToOrderBy(_orderBy); + selectExpression._orderBy.AddRange(_orderBy); return selectExpression; } /// - /// The tables making up the FROM part of the SELECT expression. + /// Clears all elements of this SelectExpression. /// - public virtual IReadOnlyList Tables => _tables; - - /// - /// Gets or sets a value indicating whether this expression projects a single wildcard ('*'). - /// - /// - /// true if this SelectExpression is project star, false if not. - /// - public virtual bool IsProjectStar { get; set; } + public virtual void Clear() + { + _tables.Clear(); + _projection.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 @@ -167,36 +282,6 @@ public virtual bool IsIdentityQuery() && 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. /// @@ -268,90 +353,10 @@ public virtual TableExpressionBase GetTableForQuerySource([NotNull] IQuerySource { Check.NotNull(querySource, nameof(querySource)); - return _tables.FirstOrDefault(te => te.QuerySource == querySource || te.HandlesQuerySource(querySource)) + return _tables.FirstOrDefault(te => te.QuerySource == querySource || te.HandlesQuerySource(querySource)) ?? ProjectStarTable; } - /// - /// Gets or sets a value indicating whether this SelectExpression is DISTINCT. - /// - /// - /// true if this SelectExpression is distinct, false if not. - /// - public virtual bool IsDistinct - { - get { return _isDistinct; } - set - { - if (_offset != null) - { - PushDownSubquery(); - ClearOrderBy(); - } - - _isDistinct = value; - } - } - - /// - /// Gets or sets the LIMIT of this SelectExpression. - /// - /// - /// The limit. - /// - public virtual Expression Limit - { - get { return _limit; } - [param: CanBeNull] - set - { - if (value != null) - { - PushDownIfLimit(); - } - - _limit = value; - } - } - - /// - /// Gets or sets the OFFSET of this SelectExpression. - /// - /// - /// The offset. - /// - public virtual Expression Offset - { - get { return _offset; } - [param: CanBeNull] - set - { - if (_limit != null - && value != null) - { - var subquery = PushDownSubquery(); - } - - _offset = value; - } - } - - private void PushDownIfLimit() - { - if (_limit != null) - { - PushDownSubquery(); - } - } - - private void PushDownIfDistinct() - { - if (_isDistinct) - { - PushDownSubquery(); - } - } - /// /// Creates a subquery based on this SelectExpression and makes that table the single entry in /// . Clears all other top-level aspects of this SelectExpression. @@ -361,63 +366,67 @@ 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; + var memberInfo = _memberInfoProjectionMapping.FirstOrDefault(kvp => ExpressionEqualityComparer.Equals(kvp.Value, expression)).Key; + if (expressionToAdd is AliasExpression aliasExpression) + { + expressionToAdd = new AliasExpression(subquery.CreateUniqueProjectionAlias(aliasExpression.Alias, useColumnAliasPrefix: true), aliasExpression.Expression); + } + else if (expressionToAdd is ColumnExpression columnExpression) + { + var uniqueAlias = subquery.CreateUniqueProjectionAlias(columnExpression.Name, useColumnAliasPrefix: true); - if (aliasExpression != null) + if (!string.Equals(columnExpression.Name, uniqueAlias, StringComparison.OrdinalIgnoreCase)) + { + expressionToAdd = new AliasExpression(uniqueAlias, columnExpression); + } + } + else if (expressionToAdd is ColumnReferenceExpression columnReferenceExpression) { - var columnExpression = aliasExpression.TryGetColumnExpression(); + var uniqueAlias = subquery.CreateUniqueProjectionAlias(columnReferenceExpression.Name, useColumnAliasPrefix: true); - if ((columnExpression != null - && subquery._projection.OfType() - .Any(ae => (ae.Alias ?? ae.TryGetColumnExpression()?.Name) == (aliasExpression.Alias ?? columnExpression.Name))) - || columnExpression == null) + if (!string.Equals(columnReferenceExpression.Name, uniqueAlias, StringComparison.OrdinalIgnoreCase)) { - aliasExpression.Alias = "c" + columnAliasCounter++; + expressionToAdd = new AliasExpression(uniqueAlias, columnReferenceExpression); } } else { - aliasExpression = new AliasExpression("c" + columnAliasCounter++, expression); + expressionToAdd = new AliasExpression(subquery.CreateUniqueProjectionAlias(ColumnAliasPrefix), expression); + } + + if (memberInfo != null) + { + _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 bit of 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 +435,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); + var expressionToAdd + = expression.LiftExpressionFromSubquery(subquery) + ?? subquery.Projection[subquery.AddToProjection(expression, resetProjectStar: false)].LiftExpressionFromSubquery(subquery); - _orderBy.Add( - new Ordering( - new AliasExpression(newExpression), ordering.OrderingDirection)); - } - } - else - { - if (!subquery.IsProjectStar) - { - subquery.AddToProjection(expression); - } - - var newExpression = UpdateColumnExpression(expression, subquery); - - _orderBy.Add( - new Ordering( - new AliasExpression(newExpression), ordering.OrderingDirection)); - } + _orderBy.Add(new Ordering(expressionToAdd, ordering.OrderingDirection)); } if (subquery.Limit == null @@ -470,216 +452,185 @@ 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 column name. - /// The corresponding EF property. - /// The originating query source. - /// - /// The corresponding index of the added expression in . - /// - public virtual int AddToProjection( - [NotNull] string column, - [NotNull] IProperty property, - [NotNull] IQuerySource querySource) + /// The table expression. + public virtual void RemoveTable([NotNull] TableExpressionBase tableExpression) { - Check.NotEmpty(column, nameof(column)); - Check.NotNull(property, nameof(property)); - Check.NotNull(querySource, nameof(querySource)); - - var projectionIndex = GetProjectionIndex(property, querySource); - - if (projectionIndex == -1) - { - projectionIndex = AddToProjection( - new ColumnExpression( - column, - property, - GetTableForQuerySource(querySource))); - } + Check.NotNull(tableExpression, nameof(tableExpression)); - return projectionIndex; + _tables.Remove(tableExpression); } /// - /// Adds an expression to the projection. + /// Removes any tables added to this SelectExpression. /// - /// The expression. - /// - /// The corresponding index of the added expression in . - /// - public virtual int AddToProjection([NotNull] Expression expression) - => AddToProjection(expression, true); + public virtual void ClearTables() => _tables.Clear(); /// - /// Adds an expression to the projection. + /// Generates an expression bound to this select expression for the supplied property. /// - /// The expression. - /// true to reset the value of . + /// 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] Expression expression, bool resetProjectStar) + public virtual Expression BindPropertyToSelectExpression( + [NotNull] IProperty property, + [NotNull] IQuerySource querySource) { - Check.NotNull(expression, nameof(expression)); - - if (expression.NodeType == ExpressionType.Convert) - { - var unaryExpression = (UnaryExpression)expression; - - if (unaryExpression.Type.UnwrapNullableType() - == unaryExpression.Operand.Type) - { - expression = unaryExpression.Operand; - } - } + Check.NotNull(property, nameof(property)); + Check.NotNull(querySource, nameof(querySource)); - var columnExpression = expression as ColumnExpression; + var table = GetTableForQuerySource(querySource); - if (columnExpression != null) + Expression projectionToSearch; + if (table is JoinExpressionBase joinTable) { - return AddToProjection(columnExpression); + table = joinTable.TableExpression; } - var aliasExpression = expression as AliasExpression; - - if (aliasExpression != null) + if (table is SelectExpression subquerySelectExpression) { - return AddToProjection(aliasExpression); + if (subquerySelectExpression.IsProjectStar) + { + var boundExpression = subquerySelectExpression.BindPropertyToSelectExpression(property, querySource); + projectionToSearch = boundExpression.LiftExpressionFromSubquery(table); + } + else + { + var subQueryProjection = subquerySelectExpression.Projection[subquerySelectExpression.GetProjectionIndex(property, querySource)]; + projectionToSearch = subQueryProjection.LiftExpressionFromSubquery(table); + } } - - _projection.Add(expression); - - if (resetProjectStar) + else { - IsProjectStar = false; + projectionToSearch = new ColumnExpression(_relationalAnnotationProvider.For(property).ColumnName, property, table); } - return _projection.Count - 1; + return IsProjectStar + ? ProjectStarExpression.GetOrAdd(projectionToSearch) + : (_projection.Find(e => ExpressionEqualityComparer.Equals(e, projectionToSearch)) ?? projectionToSearch); } - + /// - /// Adds an to the projection. + /// Adds a column to the projection. /// - /// The alias expression. + /// The corresponding EF property. + /// The originating query source. /// /// The corresponding index of the added expression in . /// - public virtual int AddToProjection([NotNull] AliasExpression aliasExpression) + public virtual int AddToProjection( + [NotNull] IProperty property, + [NotNull] IQuerySource querySource) { - 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(); - - 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) - { - var currentAlias = alias ?? columnExpression?.Name ?? expression.NodeType.ToString(); - var uniqueAlias = CreateUniqueProjectionAlias(currentAlias); - - if (columnExpression == null - || !string.Equals(currentAlias, uniqueAlias, StringComparison.OrdinalIgnoreCase)) - { - alias = uniqueAlias; - } - } - - projectionIndex = _projection.Count; - - if (alias != null) - { - foreach (var orderByAliasExpression - in _orderBy.Select(o => o.Expression).OfType()) - { - 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)); - - IsProjectStar = false; - } + Check.NotNull(property, nameof(property)); + Check.NotNull(querySource, nameof(querySource)); - return projectionIndex; + return AddToProjection( + BindPropertyToSelectExpression(property, querySource)); } /// - /// Adds a ColumnExpression to the projection. + /// Adds an expression to the projection. /// - /// The column expression. + /// The expression. + /// true to reset the value of . /// /// The corresponding index of the added expression in . /// - public virtual int AddToProjection([NotNull] ColumnExpression columnExpression) + public virtual int AddToProjection([NotNull] Expression expression, bool resetProjectStar = true) { - Check.NotNull(columnExpression, nameof(columnExpression)); + Check.NotNull(expression, nameof(expression)); + + if (expression.NodeType == ExpressionType.Convert) + { + var unaryExpression = (UnaryExpression)expression; + + if (unaryExpression.Type.UnwrapNullableType() + == unaryExpression.Operand.Type) + { + expression = unaryExpression.Operand; + } + } var projectionIndex - = _projection - .FindIndex(e => - { - var ce = e.TryGetColumnExpression(); + = _projection.FindIndex(e => ExpressionEqualityComparer.Equals(e, expression)); - return ce != null - && ce.Name == columnExpression.Name - && ce.TableAlias == columnExpression.TableAlias; - }); + if (projectionIndex != -1) + { + return projectionIndex; + } - if (projectionIndex == -1) + if (!(expression is ColumnExpression || expression is ColumnReferenceExpression)) { - var aliasExpression = new AliasExpression(columnExpression); + var indexInOrderBy = _orderBy.FindIndex(o => ExpressionEqualityComparer.Equals(o.Expression, expression)); + + if (indexInOrderBy != -1) + { + expression = new AliasExpression(CreateUniqueProjectionAlias(ColumnAliasPrefix), expression); + var updatedOrdering = new Ordering(expression, _orderBy[indexInOrderBy].OrderingDirection); + + _orderBy.RemoveAt(indexInOrderBy); + _orderBy.Insert(indexInOrderBy, updatedOrdering); + } + } - // Alias != null means SelectExpression in subquery which needs projections to have unique aliases - if (Alias != null) + // Alias != null means SelectExpression in subquery which needs projections to have unique aliases + if (Alias != null) + { + if (expression is ColumnExpression columnExpression) { var currentAlias = columnExpression.Name; var uniqueAlias = CreateUniqueProjectionAlias(currentAlias); + expression + = !string.Equals(currentAlias, uniqueAlias, StringComparison.OrdinalIgnoreCase) + ? (Expression)new AliasExpression(uniqueAlias, columnExpression) + : columnExpression; + } + else if (expression is AliasExpression aliasExpression) + { + var currentAlias = aliasExpression.Alias; + var uniqueAlias = CreateUniqueProjectionAlias(currentAlias); + if (!string.Equals(currentAlias, uniqueAlias, StringComparison.OrdinalIgnoreCase)) { - aliasExpression.Alias = uniqueAlias; + expression = new AliasExpression(uniqueAlias, aliasExpression.Expression); } } + else if (expression is ColumnReferenceExpression columnReferenceExpression) + { + var currentAlias = columnReferenceExpression.Name; + var uniqueAlias = CreateUniqueProjectionAlias(currentAlias); - projectionIndex = _projection.Count; + expression + = !string.Equals(currentAlias, uniqueAlias, StringComparison.OrdinalIgnoreCase) + ? (Expression)new AliasExpression(uniqueAlias, columnReferenceExpression) + : columnReferenceExpression; + } + } - _projection.Add(aliasExpression); + _projection.Add(expression); + if (resetProjectStar) + { IsProjectStar = false; } - return projectionIndex; + return _projection.Count - 1; } /// @@ -703,19 +654,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 +662,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 +674,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 +688,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 +697,127 @@ 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 projectionToSearch = BindPropertyToSelectExpression(property, querySource); return _projection - .FindIndex(e => + .FindIndex(e => ExpressionEqualityComparer.Equals(e, projectionToSearch)); + } + + /// + /// 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 projection in subquery._projection) + { + var columnReference = projection.LiftExpressionFromSubquery(subquery); + if (columnReference != null) + { + _projection.Add(columnReference); + } + else { - var ce = e.TryGetColumnExpression(); + throw new Exception("Subquery should not have this kind of expression."); + } + } + + IsProjectStar = false; + } + } + + private string CreateUniqueProjectionAlias(string currentAlias, bool useColumnAliasPrefix = false) + { + var uniqueAlias = currentAlias ?? ColumnAliasPrefix; + + if (useColumnAliasPrefix) + { + currentAlias = ColumnAliasPrefix; + } + + var counter = 0; + + while (_projection.Select(e => + { return 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))) + { + uniqueAlias = currentAlias + counter++; + } - return ce?.Property == property - && ce.TableAlias == table.Alias; - }); + return uniqueAlias; } /// - /// Adds a column to the ORDER BY of this SelectExpression. + /// Gets the projection corresponding to supplied member info. /// - /// The column name. - /// The corresponding EF property. - /// The target table. - /// The ordering direction. + /// The corresponding member info. /// - /// An AliasExpression corresponding to the expression added to the ORDER BY. + /// The projection. /// - public virtual AliasExpression AddToOrderBy( - [NotNull] string column, - [NotNull] IProperty property, - [NotNull] TableExpressionBase table, - OrderingDirection orderingDirection) + public virtual Expression GetProjectionForMemberInfo([NotNull] MemberInfo memberInfo) { - Check.NotEmpty(column, nameof(column)); - Check.NotNull(property, nameof(property)); - Check.NotNull(table, nameof(table)); + Check.NotNull(memberInfo, nameof(memberInfo)); - var columnExpression = new ColumnExpression(column, property, table); - var aliasExpression = new AliasExpression(columnExpression); + return _memberInfoProjectionMapping.ContainsKey(memberInfo) + ? _memberInfoProjectionMapping[memberInfo] + : null; + } - if (_orderBy.FindIndex(o => o.Expression.TryGetColumnExpression()?.Equals(columnExpression) ?? false) == -1) - { - _orderBy.Add(new Ordering(aliasExpression, orderingDirection)); - } + /// + /// 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)); - return aliasExpression; + _memberInfoProjectionMapping[memberInfo] = projection; } /// - /// Adds multiple expressions to the ORDER BY of this SelectExpression. + /// Adds a predicate expression to this SelectExpression, combining it with + /// any existing predicate if necessary. /// - /// The orderings expressions. - public virtual void AddToOrderBy([NotNull] IEnumerable orderings) + /// The predicate expression to add. + public virtual void AddToPredicate([NotNull] Expression predicate) { - Check.NotNull(orderings, nameof(orderings)); - - foreach (var ordering in orderings) - { - var aliasExpression = ordering.Expression as AliasExpression; - var columnExpression = ordering.Expression as ColumnExpression; + Check.NotNull(predicate, nameof(predicate)); - 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); - } - } + 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 +839,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,44 +953,6 @@ public virtual PredicateJoinExpressionBase AddLeftOuterJoin( return outerJoinExpression; } - private string CreateUniqueProjectionAlias(string currentAlias) - { - var uniqueAlias = currentAlias ?? "A"; - var counter = 0; - - while (_projection - .OfType() - .Any(p => string.Equals(p.Alias ?? p.TryGetColumnExpression()?.Name, uniqueAlias, StringComparison.OrdinalIgnoreCase))) - { - uniqueAlias = currentAlias + counter++; - } - - return uniqueAlias; - } - - /// - /// Removes a table from this SelectExpression. - /// - /// The table expression. - public virtual void RemoveTable([NotNull] TableExpressionBase tableExpression) - { - Check.NotNull(tableExpression, nameof(tableExpression)); - - _tables.Remove(tableExpression); - } - - /// - /// 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; - } - /// /// Dispatches to the specific visit method for this node type. /// @@ -1194,70 +1039,98 @@ 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)); - - var columnExpression = expression as ColumnExpression; + Check.NotEmpty(column, nameof(column)); + Check.NotNull(property, nameof(property)); + Check.NotNull(table, nameof(table)); + Check.NotNull(querySource, nameof(querySource)); - if (columnExpression != null) - { - return columnExpression.Property == null - ? new ColumnExpression(columnExpression.Name, columnExpression.Type, tableExpression) - : new ColumnExpression(columnExpression.Name, columnExpression.Property, tableExpression); - } + return AddToProjection( + BindPropertyToSelectExpression(property, table, querySource)); + } - var aliasExpression = expression as AliasExpression; + /// + /// 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 (aliasExpression != null) + Expression projectionToSearch; + if (table is JoinExpressionBase joinTable) { - var selectExpression = aliasExpression.Expression as SelectExpression; - if (selectExpression != null) - { - return new ColumnExpression(aliasExpression.Alias, selectExpression.Type, tableExpression); - } - - return new AliasExpression( - aliasExpression.Alias, - UpdateColumnExpression(aliasExpression.Expression, tableExpression)) - { - SourceMember = aliasExpression.SourceMember - }; + table = joinTable.TableExpression; } - switch (expression.NodeType) + if (table is SelectExpression subquerySelectExpression) { - case ExpressionType.Coalesce: + if (subquerySelectExpression.IsProjectStar) { - var binaryExpression = (BinaryExpression)expression; - var left = UpdateColumnExpression(binaryExpression.Left, tableExpression); - var right = UpdateColumnExpression(binaryExpression.Right, tableExpression); - return binaryExpression.Update(left, binaryExpression.Conversion, right); + var boundExpression = subquerySelectExpression.BindPropertyToSelectExpression(property, querySource); + projectionToSearch = boundExpression.LiftExpressionFromSubquery(table); } - case ExpressionType.Conditional: + else { - 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 subQueryProjection = subquerySelectExpression.Projection[subquerySelectExpression.GetProjectionIndex(property, querySource)]; + projectionToSearch = subQueryProjection.LiftExpressionFromSubquery(table); } } + else + { + projectionToSearch = new ColumnExpression(_relationalAnnotationProvider.For(property).ColumnName, property, table); + } + + return IsProjectStar + ? ProjectStarExpression.GetOrAdd(projectionToSearch) + : (_projection.Find(e => ExpressionEqualityComparer.Equals(e, projectionToSearch)) ?? projectionToSearch); + } + + /// + /// 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)); - return expression; + var orderingExpression = BindPropertyToSelectExpression(property, table, querySource); + + 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/Internal/RelationalResultOperatorHandler.cs b/src/EFCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs index fadb8f3fcab..c7bc7cbc7b5 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); } @@ -449,22 +423,7 @@ private static Expression HandleDistinct(HandlerContext handlerContext) 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); - }); - })) + !selectExpression.Projection.Contains(o.Expression, SelectExpression.ExpressionEqualityComparer))) { handlerContext.SelectExpression.ClearOrderBy(); } @@ -498,6 +457,7 @@ var sqlExpression { var columns = (sqlExpression as ConstantExpression)?.Value as Expression[] ?? new[] { sqlExpression }; + handlerContext.SelectExpression .PrependToOrderBy(columns.Select(c => new Ordering(c, OrderingDirection.Asc))); } @@ -634,8 +594,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 +667,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 +737,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..0ea3bd84ee4 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(binaryExpression.Left.RemoveConvert()); - 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); } @@ -794,7 +769,6 @@ public QuerySourceUpdater( protected override Expression VisitConstant(ConstantExpression constantExpression) { var shaper = constantExpression.Value as Shaper; - if (shaper != null) { foreach (var queryAnnotation @@ -889,7 +863,7 @@ var sqlTranslatingExpressionVisitor _conditionalRemovingExpressionVisitorFactory .Create() .Visit(sqlPredicateExpression); - + selectExpression.AddToPredicate(sqlPredicateExpression); } else @@ -1033,9 +1007,9 @@ where oldShaper.IsShaperForQuerySource(i.QuerySource) { newShaper = oldShaper.Unwrap(querySourceReferenceExpression.ReferencedQuerySource); } - + newShaper = newShaper ?? ProjectionShaper.Create(oldShaper, materializer); - + Expression = Expression.Call( shapedQuery.Method @@ -1177,7 +1151,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 +1164,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 +1192,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 +1235,7 @@ var innerShapedQuery if (selectManyMethodCallExpression == null || !selectManyMethodCallExpression.Method.MethodIsClosedFormOf(LinqOperatorProvider.SelectMany) - || !IsShapedQueryExpression(outerShapedQuery) + || !IsShapedQueryExpression(outerShapedQuery) || !IsShapedQueryExpression(innerShapedQuery)) { return false; @@ -1300,6 +1274,7 @@ var compositeShaper Expression = Expression.Call( + // ReSharper disable once PossibleNullReferenceException outerShapedQuery.Method .GetGenericMethodDefinition() .MakeGenericMethod(materializerLambda.ReturnType), @@ -1311,9 +1286,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 +1306,7 @@ var innerShapedQuery if (joinMethodCallExpression == null || !joinMethodCallExpression.Method.MethodIsClosedFormOf(LinqOperatorProvider.Join) - || !IsShapedQueryExpression(outerShapedQuery) + || !IsShapedQueryExpression(outerShapedQuery) || !IsShapedQueryExpression(innerShapedQuery)) { return false; @@ -1391,6 +1366,7 @@ var compositeShaper Expression = Expression.Call( + // ReSharper disable once PossibleNullReferenceException outerShapedQuery.Method .GetGenericMethodDefinition() .MakeGenericMethod(materializerLambda.ReturnType), @@ -1457,11 +1433,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 +1446,7 @@ var predicate outerSelectExpression.RemoveRangeFromProjection(previousProjectionCount); - var projection + var projections = QueryCompilationContext.QuerySourceRequiresMaterialization(joinClause) ? innerSelectExpression.Projection : Enumerable.Empty(); @@ -1478,17 +1454,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 +1481,7 @@ var joinExpression new Ordering(expression, OrderingDirection.Asc)); } } - + var outerShaper = ExtractShaper(outerShapedQuery, 0); var innerShaper = ExtractShaper(innerShapedQuery, previousProjectionCount); @@ -1523,6 +1499,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 +1526,7 @@ var defaultGroupJoinInclude } private bool TryFlattenGroupJoinDefaultIfEmpty( - GroupJoinClause groupJoinClause, + [NotNull] GroupJoinClause groupJoinClause, QueryModel queryModel, int index, int previousProjectionCount, @@ -1602,13 +1579,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 +1609,9 @@ var innerItemParameter AddOrUpdateMapping(joinClause, innerItemParameter); - var transparentIdentifierType + var transparentIdentifierType = CreateTransparentIdentifierType( - previousParameter.Type, + previousParameter.Type, innerShaper.Type); var materializer @@ -1811,16 +1788,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 +1828,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 +1836,6 @@ private TResult BindMemberOrMethod( selectExpression? .AddToProjection( - _relationalAnnotationProvider.For(property).ColumnName, property, querySource); } @@ -1880,7 +1851,6 @@ private TResult BindMemberOrMethod( = ParentQueryModelVisitor?.TryGetQuery(querySource); selectExpression?.AddToProjection( - _relationalAnnotationProvider.For(property).ColumnName, property, querySource); } @@ -1921,7 +1891,7 @@ var parameterWithSamePrefixCount } QueryCompilationContext.ParentQueryReferenceParameters.Add(parameterName); - + var querySourceReference = new QuerySourceReferenceExpression(querySource); var propertyExpression = isMemberExpression ? Expression.Property(querySourceReference, property.PropertyInfo) @@ -1934,9 +1904,9 @@ var parameterWithSamePrefixCount _injectedParameters[parameterName] = propertyExpression; - Expression + Expression = CreateInjectParametersExpression( - Expression, + Expression, new Dictionary { [parameterName] = propertyExpression }); return Expression.Parameter( @@ -1965,7 +1935,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..d6b00f99856 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); } @@ -1097,8 +1057,8 @@ 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; @@ -1151,13 +1111,45 @@ 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 ProjectStarExpression. + /// + /// The project star expression. + /// + /// An Expression. + /// + public virtual Expression VisitProjectStar(ProjectStarExpression projectStarExpression) + { + Check.NotNull(projectStarExpression, nameof(projectStarExpression)); + + return projectStarExpression; + } + + /// + /// 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 +1161,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 +1326,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 +1455,13 @@ var parameterName /// protected virtual RelationalTypeMapping InferTypeMappingFromColumn([NotNull] Expression expression) { - var column = expression.TryGetColumnExpression(); - return column?.Property != null - ? Dependencies.RelationalTypeMapper.FindMapping(column.Property) - : null; + return expression is ColumnExpression columnExpression + ? Dependencies.RelationalTypeMapper.FindMapping(columnExpression.Property) + : (expression is ColumnReferenceExpression columnReferenceExpression + ? InferTypeMappingFromColumn(columnReferenceExpression.Expression) + : (expression is AliasExpression aliasExpression + ? InferTypeMappingFromColumn(aliasExpression.Expression) + : null)); } /// @@ -1509,8 +1497,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 +1552,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(); + var nonParameterExpression = leftExpression is ParameterExpression ? rightExpression : leftExpression; - if (columnExpression != null) - { - return - expression.NodeType == ExpressionType.Equal - ? (Expression)new IsNullExpression(columnExpression) - : Expression.Not(new IsNullExpression(columnExpression)); - } - } - - var constantExpression - = leftExpression as ConstantExpression - ?? rightExpression as ConstantExpression; - - if (constantExpression != null) + if (nonParameterExpression is ConstantExpression constantExpression) { if (parameterValue == null && constantExpression.Value == null) @@ -1612,6 +1580,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 +1620,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..6e4d64a5472 100644 --- a/src/EFCore.Relational/Query/Sql/ISqlExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Sql/ISqlExpressionVisitor.cs @@ -173,5 +173,23 @@ public interface ISqlExpressionVisitor /// An Expression. /// Expression VisitSqlFragment([NotNull] SqlFragmentExpression sqlFragmentExpression); + + /// + /// Visit a ColumnReferenceExpression. + /// + /// The ColumnReferenceExpression expression. + /// + /// An Expression. + /// + Expression VisitColumnReference([NotNull] ColumnReferenceExpression columnReferenceExpression); + + /// + /// Visit a ProjectStarExpression. + /// + /// The ProjectStarExpression expression. + /// + /// An Expression. + /// + Expression VisitProjectStar([NotNull] ProjectStarExpression projectStarExpression); } } \ 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 e66f49eb9ee..0f3bc1a6e84 100644 --- a/src/EFCore.Specification.Tests/QueryTestBase.cs +++ b/src/EFCore.Specification.Tests/QueryTestBase.cs @@ -3439,7 +3439,7 @@ public virtual void OrderBy_correlated_subquery2() { AssertQuery( (os, cs) => os.Where( - o => o.OrderID <= 10250 + o => o.OrderID <= 10250 && cs.OrderBy( c => cs.Any( c2 => c2.CustomerID == "ALFKI")) @@ -4944,7 +4944,7 @@ public virtual void Where_math_ceiling2() ods => ods.Where(od => Math.Ceiling(od.UnitPrice) > 10), entryCount: 1677); } - + [ConditionalFact] public virtual void Where_math_floor() { @@ -6031,7 +6031,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( @@ -7101,6 +7101,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..07c44b646a2 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,34 @@ 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..2b6676fcd1c 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); } } } @@ -69,15 +68,12 @@ private class RowNumberPagingExpressionVisitor : ExpressionVisitorBase { public override Expression Visit(Expression node) { - var existsExpression = node as ExistsExpression; - if (existsExpression != null) + if (node is ExistsExpression existsExpression) { return VisitExistExpression(existsExpression); } - var selectExpression = node as SelectExpression; - - return selectExpression != null ? VisitSelectExpression(selectExpression) : base.Visit(node); + return node is SelectExpression selectExpression ? VisitSelectExpression(selectExpression) : base.Visit(node); } private static bool RequiresRowNumberPaging(SelectExpression selectExpression) @@ -97,28 +93,15 @@ 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; - } + var columnReference = projection.LiftExpressionFromSubquery(subQuery); - column = alias?.TryGetColumnExpression(); - - if (column != null) + if (columnReference != null) { - column = new ColumnExpression(alias.Alias ?? column.Name, column.Property, subQuery); - alias = new AliasExpression(alias.Alias, column); - selectExpression.AddToProjection(alias); + selectExpression.AddToProjection(columnReference); } else { - column = new ColumnExpression(alias?.Alias, alias.Expression.Type, subQuery); - selectExpression.AddToProjection(column); + throw new Exception("Subquery should not have any other type of expression."); } } @@ -128,18 +111,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 +142,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) @@ -168,9 +158,7 @@ var limitExpression private Expression VisitExistExpression(ExistsExpression existsExpression) { var newExpression = Visit(existsExpression.Expression); - var subSelectExpression = newExpression as SelectExpression; - if (subSelectExpression != null - && subSelectExpression.Limit == null + if (newExpression is SelectExpression subSelectExpression && subSelectExpression.Limit == null && subSelectExpression.Offset == null) { subSelectExpression.ClearOrderBy(); 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..51d1bb35cd5 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; @@ -149,6 +150,72 @@ var newAccessOperation /// A textual representation of the . /// public override string ToString() - => $"(?{AccessOperation}?)"; // TODO: Improve this + { + if (AccessOperation is MemberExpression memberExpression) + { + return Caller + "?." + memberExpression.Member.Name; + } + + if (AccessOperation is MethodCallExpression methodCallExpression) + { + if (methodCallExpression.Object != null) + { + return methodCallExpression.Object + + "?." + methodCallExpression.Method.Name + + "(" + string.Join(",", methodCallExpression.Arguments) + ")"; + } + var method = methodCallExpression.Method; + return method.DeclaringType?.Name + "." + method.Name + + "(?" + methodCallExpression.Arguments[0] + "?, " + + string.Join(",", methodCallExpression.Arguments.Skip(1)) + ")"; + } + + return "?" + AccessOperation + "?"; + } + + /// + /// 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) + { + return _type == other._type && NullableCaller.Equals(other.NullableCaller) && Caller.Equals(other.Caller) && AccessOperation.Equals(other.AccessOperation); + } + + /// + /// Returns a hash code for this object. + /// + /// + /// A hash code for this object. + /// + public override int GetHashCode() + { + unchecked + { + var hashCode = _type.GetHashCode(); + hashCode = (hashCode * 397) ^ NullableCaller.GetHashCode(); + hashCode = (hashCode * 397) ^ Caller.GetHashCode(); + hashCode = (hashCode * 397) ^ AccessOperation.GetHashCode(); + return hashCode; + } + } } } diff --git a/src/EFCore/Query/Internal/ExpressionEqualityComparer.cs b/src/EFCore/Query/Internal/ExpressionEqualityComparer.cs index b1f73e3aace..1172379f129 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 += 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..7ec18fb7061 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( @@ -1693,7 +1692,7 @@ public override void Coalesce_operator_in_predicate() AssertSql( @"SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapon] AS [w] -WHERE COALESCE([w].[IsAutomatic], 0) = 1", +WHERE (COALESCE([w].[IsAutomatic], 0)) = 1", Sql); } @@ -1704,7 +1703,7 @@ public override void Coalesce_operator_in_predicate_with_other_conditions() AssertSql( @"SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapon] AS [w] -WHERE ([w].[AmmunitionType] = 1) AND (COALESCE([w].[IsAutomatic], 0) = 1)", +WHERE ([w].[AmmunitionType] = 1) AND ((COALESCE([w].[IsAutomatic], 0)) = 1)", Sql); } @@ -1714,7 +1713,7 @@ public override void Coalesce_operator_in_projection_with_other_conditions() AssertSql( @"SELECT CASE - WHEN ([w].[AmmunitionType] = 1) AND (COALESCE([w].[IsAutomatic], 0) = 1) + WHEN ([w].[AmmunitionType] = 1) AND ((COALESCE([w].[IsAutomatic], 0)) = 1) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END FROM [Weapon] AS [w]", @@ -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..712fc0bad08 100644 --- a/test/EFCore.SqlServer.FunctionalTests/NullSemanticsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/NullSemanticsQuerySqlServerTest.cs @@ -787,7 +787,7 @@ public override void Where_coalesce() Assert.Equal( @"SELECT [e].[Id] FROM [NullSemanticsEntity1] AS [e] -WHERE COALESCE([e].[NullableBoolA], 1) = 1", +WHERE (COALESCE([e].[NullableBoolA], 1)) = 1", Sql); } @@ -820,7 +820,7 @@ public override void Where_equal_with_coalesce() Assert.Equal( @"SELECT [e].[Id] FROM [NullSemanticsEntity1] AS [e] -WHERE (COALESCE([e].[NullableStringA], [e].[NullableStringB]) = [e].[NullableStringC]) OR (([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) AND [e].[NullableStringC] IS NULL)", +WHERE ((COALESCE([e].[NullableStringA], [e].[NullableStringB])) = [e].[NullableStringC]) OR (([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) AND [e].[NullableStringC] IS NULL)", Sql); } @@ -831,7 +831,7 @@ public override void Where_not_equal_with_coalesce() Assert.Equal( @"SELECT [e].[Id] FROM [NullSemanticsEntity1] AS [e] -WHERE ((COALESCE([e].[NullableStringA], [e].[NullableStringB]) <> [e].[NullableStringC]) OR (([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) OR [e].[NullableStringC] IS NULL)) AND (([e].[NullableStringA] IS NOT NULL OR [e].[NullableStringB] IS NOT NULL) OR [e].[NullableStringC] IS NOT NULL)", +WHERE (((COALESCE([e].[NullableStringA], [e].[NullableStringB])) <> [e].[NullableStringC]) OR (([e].[NullableStringA] IS NULL AND [e].[NullableStringB] IS NULL) OR [e].[NullableStringC] IS NULL)) AND (([e].[NullableStringA] IS NOT NULL OR [e].[NullableStringB] IS NOT NULL) OR [e].[NullableStringC] IS NOT NULL)", Sql); } @@ -842,7 +842,7 @@ public override void Where_equal_with_coalesce_both_sides() Assert.Equal( @"SELECT [e].[Id] FROM [NullSemanticsEntity1] AS [e] -WHERE COALESCE([e].[NullableStringA], [e].[NullableStringB]) = COALESCE([e].[StringA], [e].[StringB])", +WHERE (COALESCE([e].[NullableStringA], [e].[NullableStringB])) = (COALESCE([e].[StringA], [e].[StringB]))", Sql); } @@ -853,7 +853,7 @@ public override void Where_not_equal_with_coalesce_both_sides() Assert.Equal( @"SELECT [e].[Id] FROM [NullSemanticsEntity1] AS [e] -WHERE ((COALESCE([e].[NullableIntA], [e].[NullableIntB]) <> COALESCE([e].[NullableIntC], [e].[NullableIntB])) OR (([e].[NullableIntA] IS NULL AND [e].[NullableIntB] IS NULL) OR ([e].[NullableIntC] IS NULL AND [e].[NullableIntB] IS NULL))) AND (([e].[NullableIntA] IS NOT NULL OR [e].[NullableIntB] IS NOT NULL) OR ([e].[NullableIntC] IS NOT NULL OR [e].[NullableIntB] IS NOT NULL))", +WHERE (((COALESCE([e].[NullableIntA], [e].[NullableIntB])) <> (COALESCE([e].[NullableIntC], [e].[NullableIntB]))) OR (([e].[NullableIntA] IS NULL AND [e].[NullableIntB] IS NULL) OR ([e].[NullableIntC] IS NULL AND [e].[NullableIntB] IS NULL))) AND (([e].[NullableIntA] IS NOT NULL OR [e].[NullableIntB] IS NOT NULL) OR ([e].[NullableIntC] IS NOT NULL OR [e].[NullableIntB] IS NOT NULL))", Sql); } @@ -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 12b15353c6e..05ba247bab6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/QuerySqlServerTest.cs @@ -5954,9 +5954,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); } @@ -6388,7 +6388,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); } @@ -6404,11 +6404,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); @@ -6420,13 +6420,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() { @@ -6438,11 +6437,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); } @@ -6458,11 +6457,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); } @@ -6478,11 +6477,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); } @@ -7002,13 +7001,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); } @@ -7429,6 +7428,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]