diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/ResultTransformingExpressionVisitor.cs b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/ResultTransformingExpressionVisitor.cs index 2780e99e42f..bed252a6c7d 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/Internal/ResultTransformingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/Internal/ResultTransformingExpressionVisitor.cs @@ -25,11 +25,9 @@ public class ResultTransformingExpressionVisitor : ExpressionVisitorBas /// directly from your code. This API may change or be removed in future releases. /// public ResultTransformingExpressionVisitor( - [NotNull] IQuerySource outerQuerySource, [NotNull] RelationalQueryCompilationContext relationalQueryCompilationContext, bool throwOnNullResult) { - Check.NotNull(outerQuerySource, nameof(outerQuerySource)); Check.NotNull(relationalQueryCompilationContext, nameof(relationalQueryCompilationContext)); _relationalQueryCompilationContext = relationalQueryCompilationContext; diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/RelationalProjectionExpressionVisitor.cs b/src/EFCore.Relational/Query/ExpressionVisitors/RelationalProjectionExpressionVisitor.cs index 6f32fc63a4d..77536bded83 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/RelationalProjectionExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/RelationalProjectionExpressionVisitor.cs @@ -6,11 +6,9 @@ using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.Extensions.Internal; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.Expressions; -using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; @@ -30,8 +28,9 @@ public class RelationalProjectionExpressionVisitor : ProjectionExpressionVisitor private readonly ISqlTranslatingExpressionVisitorFactory _sqlTranslatingExpressionVisitorFactory; private readonly IEntityMaterializerSource _entityMaterializerSource; private readonly IQuerySource _querySource; - private readonly SelectExpression _selectExpression; - private bool _topLevelProjection; + private readonly SelectExpression _groupAggregateTargetSelectExpression; + private readonly SelectExpression _targetSelectExpression; + private bool _isGroupAggregate; private readonly Dictionary _sourceExpressionProjectionMapping = new Dictionary(); @@ -54,8 +53,18 @@ public RelationalProjectionExpressionVisitor( _sqlTranslatingExpressionVisitorFactory = dependencies.SqlTranslatingExpressionVisitorFactory; _entityMaterializerSource = dependencies.EntityMaterializerSource; _querySource = querySource; - _topLevelProjection = true; - _selectExpression = QueryModelVisitor.TryGetQuery(querySource); + _targetSelectExpression = QueryModelVisitor.TryGetQuery(querySource); + + if (_targetSelectExpression != null) + { + _groupAggregateTargetSelectExpression = _targetSelectExpression.Clone(); + + _isGroupAggregate = _querySource.ItemType.IsGrouping() && _targetSelectExpression.GroupBy.Count > 0; + if (_isGroupAggregate) + { + _targetSelectExpression.ClearProjection(); + } + } } private new RelationalQueryModelVisitor QueryModelVisitor @@ -72,7 +81,7 @@ protected override Expression VisitMemberInit(MemberInitExpression memberInitExp { var newMemberInitExpression = base.VisitMemberInit(memberInitExpression); - if (_selectExpression != null) + if (_targetSelectExpression != null) { foreach (var sourceBinding in memberInitExpression.Bindings) { @@ -84,7 +93,7 @@ protected override Expression VisitMemberInit(MemberInitExpression memberInitExp { var memberInfo = memberAssignment.Member; - _selectExpression.SetProjectionForMemberInfo( + _targetSelectExpression.SetProjectionForMemberInfo( memberInfo, sqlExpression); } @@ -122,7 +131,7 @@ var propertyCallExpressions var newNewExpression = base.VisitNew(newExpression); - if (_selectExpression != null) + if (_targetSelectExpression != null) { for (var i = 0; i < newExpression.Arguments.Count; i++) { @@ -134,7 +143,7 @@ var propertyCallExpressions if (memberInfo != null) { - _selectExpression.SetProjectionForMemberInfo( + _targetSelectExpression.SetProjectionForMemberInfo( memberInfo, sqlExpression); } @@ -154,25 +163,7 @@ var propertyCallExpressions /// public override Expression Visit(Expression expression) { - if (_topLevelProjection - && (QueryModelVisitor.Expression as MethodCallExpression)?.Method.MethodIsClosedFormOf( - QueryModelVisitor.QueryCompilationContext.QueryMethodProvider.GroupByMethod) == true) - { - var translation = new GroupByAggregateTranslatingExpressionVisitor(this) - - .Translate(expression); - - if (translation != null) - { - QueryModelVisitor.RequiresStreamingGroupResultOperator = false; - - return translation; - } - } - - _topLevelProjection = false; - - if (expression == null || _selectExpression == null) + if (expression == null || _targetSelectExpression == null) { return expression; } @@ -190,10 +181,10 @@ public override Expression Visit(Expression expression) return VisitMemberInit(memberInitExpression); case QuerySourceReferenceExpression qsre: - if (_selectExpression.HandlesQuerySource(qsre.ReferencedQuerySource)) + if (_targetSelectExpression.HandlesQuerySource(qsre.ReferencedQuerySource)) { - _selectExpression.ProjectStarTable - = _selectExpression.GetTableForQuerySource(qsre.ReferencedQuerySource); + _targetSelectExpression.ProjectStarTable + = _targetSelectExpression.GetTableForQuerySource(qsre.ReferencedQuerySource); } return qsre; @@ -202,10 +193,47 @@ public override Expression Visit(Expression expression) case MethodCallExpression methodCallExpression when IncludeCompiler.IsIncludeMethod(methodCallExpression): return methodCallExpression; + // Group By key translation to cover composite key cases + case MemberExpression memberExpression + when memberExpression.Expression.TryGetReferencedQuerySource() == _querySource + && _querySource.ItemType.IsGrouping() + && memberExpression.Member.Name == nameof(IGrouping.Key): + + var groupResultOperator + = (GroupResultOperator)((SubQueryExpression)((FromClauseBase)_querySource).FromExpression) + .QueryModel.ResultOperators.Last(); + + if (groupResultOperator.KeySelector is NewExpression) + { + // If the key is composite then we need to visit actual keySelector to construct the type. + // Since we are mapping translating actual KeySelector now, we need to re-map QuerySources + + var querySourceFinder = new QuerySourceFindingExpressionVisitor(); + querySourceFinder.Visit(groupResultOperator.KeySelector); + + foreach (var querySource in querySourceFinder.QuerySources) + { + QueryModelVisitor.QueryCompilationContext.AddOrUpdateMapping( + querySource, + QueryModelVisitor.CurrentParameter); + } + + _isGroupAggregate = false; + var translatedKey = Visit(groupResultOperator.KeySelector); + _isGroupAggregate = true; + + return translatedKey; + } + + goto default; + default: var sqlExpression = _sqlTranslatingExpressionVisitorFactory - .Create(QueryModelVisitor, _selectExpression, inProjection: true) + .Create( + QueryModelVisitor, + _isGroupAggregate ? _groupAggregateTargetSelectExpression : _targetSelectExpression, + inProjection: true) .Visit(expression); if (sqlExpression == null) @@ -225,11 +253,14 @@ var sqlExpression sqlExpression = nullableExpression.Operand; } - if (sqlExpression is ColumnExpression) + // We bind with ValueBuffer in GroupByAggregate case straight away + // Since the expression can be some translation from [g].[Key] which won't bind with MemberAccessBindingEV + if (!_isGroupAggregate + && sqlExpression is ColumnExpression) { - var index = _selectExpression.AddToProjection(sqlExpression); + var index = _targetSelectExpression.AddToProjection(sqlExpression); - _sourceExpressionProjectionMapping[expression] = _selectExpression.Projection[index]; + _sourceExpressionProjectionMapping[expression] = _targetSelectExpression.Projection[index]; return expression; } @@ -240,9 +271,9 @@ var targetExpression if (targetExpression.Type == typeof(ValueBuffer)) { - var index = _selectExpression.AddToProjection(sqlExpression); + var index = _targetSelectExpression.AddToProjection(sqlExpression); - _sourceExpressionProjectionMapping[expression] = _selectExpression.Projection[index]; + _sourceExpressionProjectionMapping[expression] = _targetSelectExpression.Projection[index]; var readValueExpression = _entityMaterializerSource @@ -273,269 +304,16 @@ var outputDataInfo } } - #region GroupByHelper - - private class GroupByAggregateTranslatingExpressionVisitor : RelinqExpressionVisitor + private class QuerySourceFindingExpressionVisitor : RelinqExpressionVisitor { - private readonly RelationalProjectionExpressionVisitor _projectionExpressionVisitor; - private readonly RelationalQueryModelVisitor _queryModelVisitor; - private readonly IQuerySource _groupQuerySource; - private readonly ISqlTranslatingExpressionVisitorFactory _sqlTranslatingExpressionVisitorFactory; - private readonly IEntityMaterializerSource _entityMaterializerSource; - private readonly SelectExpression _selectExpression; - private readonly Dictionary _sqlMapping = new Dictionary(); - private bool _translateToSql; - private Expression _keySelector; - private const string KeyName = nameof(IGrouping.Key); - - private static readonly List _aggregateResultOperators = new List - { - typeof(AverageResultOperator), - typeof(CountResultOperator), - typeof(LongCountResultOperator), - typeof(MaxResultOperator), - typeof(MinResultOperator), - typeof(SumResultOperator) - }; - - public GroupByAggregateTranslatingExpressionVisitor(RelationalProjectionExpressionVisitor projectionExpressionVisitor) - { - _projectionExpressionVisitor = projectionExpressionVisitor; - _queryModelVisitor = projectionExpressionVisitor.QueryModelVisitor; - _groupQuerySource = projectionExpressionVisitor._querySource; - _sqlTranslatingExpressionVisitorFactory = projectionExpressionVisitor._sqlTranslatingExpressionVisitorFactory; - _entityMaterializerSource = projectionExpressionVisitor._entityMaterializerSource; - _selectExpression = _queryModelVisitor.TryGetQuery(_groupQuerySource); - } - - public Expression Translate(Expression expression) - { - if (!CanTranslate(expression)) - { - return null; - } - - _translateToSql = true; - - Visit(expression); - - _selectExpression.ClearProjection(); - UpdateGroupQuerySourceParameter(typeof(ValueBuffer)); - - _translateToSql = false; - - return Visit(expression); - } - - protected override Expression VisitMember(MemberExpression memberExpression) - { - if (!_translateToSql) - { - // For composite Key case, projection member info will be already populated - var sqlExpression = _selectExpression.GetProjectionForMemberInfo(memberExpression.Member); - if (sqlExpression != null) - { - return BindSqlToValueBuffer(sqlExpression, memberExpression.Type); - } - - if (memberExpression.Member.Name == KeyName - && memberExpression.Expression.TryGetReferencedQuerySource() == _groupQuerySource) - { - var querySourceFinder = new QuerySourceFindingExpressionVisitor(); - querySourceFinder.Visit(_keySelector); - - var currentParameter = _queryModelVisitor.CurrentParameter; - - foreach (var querySource in querySourceFinder.QuerySources) - { - _queryModelVisitor.QueryCompilationContext.AddOrUpdateMapping( - querySource, - currentParameter); - } - - sqlExpression = _projectionExpressionVisitor.Visit(_keySelector); - _sqlMapping[memberExpression] = sqlExpression; - return sqlExpression; - } - } - - return base.VisitMember(memberExpression); - } - - protected override Expression VisitSubQuery(SubQueryExpression subQueryExpression) - { - if (_translateToSql) - { - var sqlExpression = _sqlTranslatingExpressionVisitorFactory.Create( - _queryModelVisitor, - _selectExpression, - inProjection: true) - .Visit(subQueryExpression); - _sqlMapping[subQueryExpression] = sqlExpression; - - return subQueryExpression; - } + public ICollection QuerySources { get; } = new HashSet(); - return BindSqlToValueBuffer(_sqlMapping[subQueryExpression], subQueryExpression.Type); - } - - private Expression BindSqlToValueBuffer(Expression sqlExpression, Type expressionType) + protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpression querySourceReferenceExpression) { - var targetExpression - = _queryModelVisitor.QueryCompilationContext.QuerySourceMapping - .GetExpression(_groupQuerySource); - - if (targetExpression.Type == typeof(ValueBuffer)) - { - var index = _selectExpression.AddToProjection(sqlExpression); - - var readValueExpression - = _entityMaterializerSource - .CreateReadValueExpression( - targetExpression, - expressionType, - index, - sqlExpression.FindProperty(expressionType)); - - return Expression.Convert(readValueExpression, expressionType); - } - - return null; - } - - private bool CanTranslate(Expression expression) - { - // Check for Query shape - if (IsAggregateGroupBySelector(expression)) - { - var groupByResultOperator = - (GroupResultOperator)((SubQueryExpression)((MainFromClause)_groupQuerySource).FromExpression) - .QueryModel.ResultOperators - .Last(); - - _keySelector = groupByResultOperator.KeySelector; - var elementSelector = groupByResultOperator.ElementSelector; - - if (!(elementSelector is QuerySourceReferenceExpression) - && _sqlTranslatingExpressionVisitorFactory.Create( - _queryModelVisitor, - _selectExpression) - .Visit(groupByResultOperator.ElementSelector) == null) - { - return false; - } - - _selectExpression.ClearOrderBy(); - _selectExpression.ClearProjection(); - - UpdateGroupQuerySourceParameter(typeof(ValueBuffer)); - - _projectionExpressionVisitor.Visit(_keySelector); - var columns = _selectExpression.Projection.ToArray(); - _selectExpression.ClearProjection(); - - if (!(elementSelector is QuerySourceReferenceExpression)) - { - _projectionExpressionVisitor.Visit(groupByResultOperator.ElementSelector); - } - - if (_selectExpression.Projection.Count > 1) - { - _selectExpression.ClearProjection(); - } - - _selectExpression.AddToGroupBy(columns); - - var shapedQuery = - (MethodCallExpression)((MethodCallExpression)_queryModelVisitor.Expression).Arguments[0]; - - var valueBufferShaper = new ValueBufferShaper(_groupQuerySource); - - var groupShapedQuery = Expression.Call( - _queryModelVisitor.QueryCompilationContext - .QueryMethodProvider - .ShapedQueryMethod - .MakeGenericMethod(valueBufferShaper.Type), - shapedQuery.Arguments[0], - shapedQuery.Arguments[1], - Expression.Constant(valueBufferShaper)); - - _queryModelVisitor.Expression = groupShapedQuery; - - UpdateGroupQuerySourceParameter(groupShapedQuery.Type); - - return true; - } + QuerySources.Add(querySourceReferenceExpression.TryGetReferencedQuerySource()); - return false; - } - - private class QuerySourceFindingExpressionVisitor : RelinqExpressionVisitor - { - public ICollection QuerySources { get; } = new HashSet(); - - protected override Expression VisitQuerySourceReference(QuerySourceReferenceExpression querySourceReferenceExpression) - { - QuerySources.Add(querySourceReferenceExpression.TryGetReferencedQuerySource()); - - return base.VisitQuerySourceReference(querySourceReferenceExpression); - } - } - - private void UpdateGroupQuerySourceParameter(Type parameterType) - { - var currentParameter = Expression.Parameter( - parameterType, - _groupQuerySource.ItemName); - - _queryModelVisitor.CurrentParameter = currentParameter; - _queryModelVisitor.QueryCompilationContext.QuerySourceMapping.ReplaceMapping( - _groupQuerySource, - currentParameter); - } - - private bool IsAggregateGroupBySelector(Expression expression) - { - if (expression is NewExpression newExpression) - { - return newExpression.Arguments.All(e => IsAggregateSubQueryExpression(e) || IsKeySelector(e)); - } - - return IsAggregateSubQueryExpression(expression); - } - - private bool IsAggregateSubQueryExpression(Expression expression) - { - if (expression is SubQueryExpression subQuery - && subQuery.QueryModel.BodyClauses.Count == 0 - && subQuery.QueryModel.MainFromClause.FromExpression.TryGetReferencedQuerySource() == _groupQuerySource - && subQuery.QueryModel.ResultOperators.Count == 1 - && !(subQuery.QueryModel.SelectClause.Selector is ConstantExpression) - && _aggregateResultOperators.Contains(subQuery.QueryModel.ResultOperators.Single().GetType())) - { - return true; - } - - return false; - } - - private bool IsKeySelector(Expression expression) - { - while (expression is MemberExpression memberExpression) - { - if (memberExpression.Member.Name == KeyName - && memberExpression.Expression.TryGetReferencedQuerySource() == _groupQuerySource) - { - return true; - } - - expression = memberExpression.Expression; - } - - return false; + return base.VisitQuerySourceReference(querySourceReferenceExpression); } } - - #endregion } } diff --git a/src/EFCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitor.cs index 1f08165aa3d..fc4f209efec 100644 --- a/src/EFCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/ExpressionVisitors/SqlTranslatingExpressionVisitor.cs @@ -147,7 +147,6 @@ protected override Expression VisitBinary(BinaryExpression expression) switch (expression.NodeType) { case ExpressionType.Coalesce: - { var left = Visit(expression.Left); var right = Visit(expression.Right); @@ -157,31 +156,25 @@ protected override Expression VisitBinary(BinaryExpression expression) && right.Type != typeof(Expression[]) ? expression.Update(left, expression.Conversion, right) : null; - } case ExpressionType.Equal: case ExpressionType.NotEqual: - { var structuralComparisonExpression = UnfoldStructuralComparison( expression.NodeType, ProcessComparisonExpression(expression)); return structuralComparisonExpression; - } case ExpressionType.GreaterThan: case ExpressionType.GreaterThanOrEqual: case ExpressionType.LessThan: case ExpressionType.LessThanOrEqual: - { return ProcessComparisonExpression(expression); - } case ExpressionType.AndAlso: - { - var left = Visit(expression.Left); - var right = Visit(expression.Right); + left = Visit(expression.Left); + right = Visit(expression.Right); if (expression == _topLevelPredicate) { @@ -209,7 +202,6 @@ var structuralComparisonExpression return left != null && right != null ? Expression.AndAlso(left, right) : null; - } case ExpressionType.OrElse: case ExpressionType.Add: @@ -219,7 +211,6 @@ var structuralComparisonExpression case ExpressionType.Modulo: case ExpressionType.And: case ExpressionType.Or: - { var leftExpression = Visit(expression.Left); var rightExpression = Visit(expression.Right); @@ -232,7 +223,6 @@ var structuralComparisonExpression expression.IsLiftedToNull, expression.Method) : null; - } } return null; @@ -721,11 +711,35 @@ var newMemberExpression private Expression TryBindQuerySourcePropertyExpression(MemberExpression memberExpression) { - if (memberExpression.Expression is QuerySourceReferenceExpression qsre) + if (!(memberExpression.Expression is QuerySourceReferenceExpression qsre)) + { + // For memberExpression of form g.Key.Id + // We need SelectExpression for g & Member for Id (since we saved mappings) + if (memberExpression.Expression is MemberExpression innerMemberExpression + && innerMemberExpression.Expression is QuerySourceReferenceExpression + && innerMemberExpression.Expression.Type.IsGrouping() + && innerMemberExpression.Member.Name == nameof(IGrouping.Key)) + { + qsre = (QuerySourceReferenceExpression)innerMemberExpression.Expression; + } + else + { + qsre = null; + } + } + + if (qsre != null) { - var selectExpression = _queryModelVisitor.TryGetQuery(qsre.ReferencedQuerySource); + var sql = _queryModelVisitor.TryGetQuery(qsre.ReferencedQuerySource) + ?.GetProjectionForMemberInfo(memberExpression.Member); - return selectExpression?.GetProjectionForMemberInfo(memberExpression.Member); + if (_topLevelPredicate != null + && sql is AliasExpression aliasExpression) + { + return aliasExpression.Expression; + } + + return sql; } return null; @@ -794,7 +808,6 @@ protected override Expression VisitUnary(UnaryExpression expression) switch (expression.NodeType) { case ExpressionType.Negate: - { var operand = Visit(expression.Operand); if (operand != null) { @@ -802,22 +815,20 @@ protected override Expression VisitUnary(UnaryExpression expression) } break; - } + case ExpressionType.Not: - { - var operand = Visit(expression.Operand); + operand = Visit(expression.Operand); if (operand != null) { return Expression.Not(operand); } break; - } + case ExpressionType.Convert: - { var isTopLevelProjection = _isTopLevelProjection; _isTopLevelProjection = false; - var operand = Visit(expression.Operand); + operand = Visit(expression.Operand); _isTopLevelProjection = isTopLevelProjection; if (operand != null) @@ -832,7 +843,7 @@ protected override Expression VisitUnary(UnaryExpression expression) } break; - } + } return null; @@ -939,6 +950,39 @@ var subQueryModelVisitor = (RelationalQueryModelVisitor)_queryModelVisitor.QueryCompilationContext .CreateQueryModelVisitor(_queryModelVisitor); + if (expression.QueryModel.MainFromClause.FromExpression is QuerySourceReferenceExpression groupQsre + && groupQsre.Type.IsGrouping()) + { + var targetExpression = _queryModelVisitor.QueryCompilationContext.QuerySourceMapping + .GetExpression(groupQsre.ReferencedQuerySource); + + if (targetExpression.Type == typeof(ValueBuffer)) + { + var outerSelectExpression = _targetSelectExpression.Clone(); + subQueryModelVisitor.AddQuery(subQueryModel.MainFromClause, outerSelectExpression); + + _queryModelVisitor.QueryCompilationContext.AddOrUpdateMapping( + groupQsre.ReferencedQuerySource, + Expression.Parameter( + typeof(IEnumerable<>).MakeGenericType(typeof(ValueBuffer)))); + + subQueryModelVisitor.VisitSubQueryModel(subQueryModel); + + _queryModelVisitor.QueryCompilationContext.AddOrUpdateMapping( + groupQsre.ReferencedQuerySource, + targetExpression); + + if (outerSelectExpression.Projection.Count == 1) + { + var projection = outerSelectExpression.Projection.Single(); + + return projection is AliasExpression aliasExpression + ? aliasExpression.Expression + : projection; + } + } + } + var queriesProjectionCountMapping = _queryModelVisitor.Queries .ToDictionary(k => k, s => s.Projection.Count); @@ -946,13 +990,6 @@ var queriesProjectionCountMapping var queryModelMapping = new Dictionary(); subQueryModel.PopulateQueryModelMapping(queryModelMapping); - var groupByTranslation = expression.QueryModel.MainFromClause.FromExpression.Type.IsGrouping(); - if (groupByTranslation) - { - var outerSelectExpression = _targetSelectExpression.Clone(); - subQueryModelVisitor.AddQuery(subQueryModel.MainFromClause, outerSelectExpression); - } - subQueryModelVisitor.VisitSubQueryModel(subQueryModel); if (subQueryModelVisitor.IsLiftable) @@ -968,12 +1005,6 @@ var queriesProjectionCountMapping _queryModelVisitor.LiftInjectedParameters(subQueryModelVisitor); - if (groupByTranslation - && selectExpression.Projection.Count == 1) - { - return selectExpression.Projection.Single(); - } - return selectExpression; } @@ -1041,7 +1072,6 @@ protected override Expression VisitExtension(Expression expression) switch (expression) { case StringCompareExpression stringCompare: - { var newLeft = Visit(stringCompare.Left); var newRight = Visit(stringCompare.Right); if (newLeft == null @@ -1054,9 +1084,8 @@ protected override Expression VisitExtension(Expression expression) || newRight != stringCompare.Right ? new StringCompareExpression(stringCompare.Operator, newLeft, newRight) : expression; - } + case ExplicitCastExpression explicitCast: - { var newOperand = Visit(explicitCast.Operand); if (newOperand == null) { @@ -1066,9 +1095,8 @@ protected override Expression VisitExtension(Expression expression) return newOperand != explicitCast.Operand ? new ExplicitCastExpression(newOperand, explicitCast.Type) : expression; - } + case NullConditionalExpression nullConditionalExpression: - { var newAccessOperation = Visit(nullConditionalExpression.AccessOperation); if (newAccessOperation == null) { @@ -1081,17 +1109,15 @@ protected override Expression VisitExtension(Expression expression) } return new NullableExpression(newAccessOperation); - } + case NullSafeEqualExpression nullSafeEqualExpression: - { var equalityExpression = new NullCompensatedExpression(nullSafeEqualExpression.EqualExpression); return Visit(equalityExpression); - } + case NullCompensatedExpression nullCompensatedExpression: - { - var newOperand = Visit(nullCompensatedExpression.Operand); + newOperand = Visit(nullCompensatedExpression.Operand); if (newOperand == null) { return null; @@ -1100,7 +1126,7 @@ var equalityExpression return newOperand != nullCompensatedExpression.Operand ? new NullCompensatedExpression(newOperand) : nullCompensatedExpression; - } + case DiscriminatorPredicateExpression discriminatorPredicateExpression: return new DiscriminatorPredicateExpression( base.VisitExtension(expression), discriminatorPredicateExpression.QuerySource); diff --git a/src/EFCore.Relational/Query/Expressions/SelectExpression.cs b/src/EFCore.Relational/Query/Expressions/SelectExpression.cs index 6b33e4a642e..7e9e3252260 100644 --- a/src/EFCore.Relational/Query/Expressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/Expressions/SelectExpression.cs @@ -93,6 +93,14 @@ public SelectExpression( /// public virtual Expression Predicate { get; [param: CanBeNull] set; } + /// + /// Gets or sets the predicate corresponding to the HAVING part of the SELECT expression. + /// + /// + /// The predicate. + /// + public virtual Expression Having { get; [param: CanBeNull] set; } + /// /// Gets or sets the table to be used for star projection. /// @@ -302,7 +310,8 @@ public virtual bool IsIdentityQuery() && Offset == null && Projection.Count == 0 && OrderBy.Count == 0 - && GroupBy.Count == 0 + // GroupBy is intentionally ommitted because GroupBy does not require a pushdown. + //&& GroupBy.Count == 0 && Tables.Count == 1; /// @@ -689,7 +698,10 @@ public virtual void SetProjectionExpression([NotNull] Expression expression) private Expression CreateUniqueProjection(Expression expression, string newAlias = null) { - var currentProjectionIndex = _projection.FindIndex(e => e.Equals(expression)); + var currentProjectionIndex + = _projection.FindIndex( + e => ExpressionEqualityComparer.Instance.Equals(e, expression) + || ExpressionEqualityComparer.Instance.Equals((e as AliasExpression)?.Expression, expression)); if (currentProjectionIndex != -1) { @@ -871,7 +883,14 @@ public virtual void AddToPredicate([NotNull] Expression predicate) { Check.NotNull(predicate, nameof(predicate)); - Predicate = Predicate != null ? AndAlso(Predicate, predicate) : predicate; + if (_groupBy.Count == 0) + { + Predicate = Predicate != null ? AndAlso(Predicate, predicate) : predicate; + } + else + { + Having = Having != null ? AndAlso(Having, predicate) : predicate; + } } /// @@ -882,6 +901,9 @@ public virtual void AddToGroupBy([NotNull] Expression[] groupingExpressions) { Check.NotNull(groupingExpressions, nameof(groupingExpressions)); + // Skip/Take will cause PushDown prior to calling this method so it is safe to clear ordering if any. + // Ordering before GroupBy Aggregate has no effect. + _orderBy.Clear(); _groupBy.AddRange(groupingExpressions); } diff --git a/src/EFCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs b/src/EFCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs index a9ea489ea78..5d6a8b8785a 100644 --- a/src/EFCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs +++ b/src/EFCore.Relational/Query/Internal/RelationalResultOperatorHandler.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -15,8 +16,10 @@ using Microsoft.EntityFrameworkCore.Query.Expressions; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors; using Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal; +using Microsoft.EntityFrameworkCore.Storage; using Remotion.Linq; using Remotion.Linq.Clauses; +using Remotion.Linq.Clauses.Expressions; using Remotion.Linq.Clauses.ResultOperators; namespace Microsoft.EntityFrameworkCore.Query.Internal @@ -469,6 +472,93 @@ var sqlExpression PrepareSelectExpressionForAggregate(selectExpression); + if (!handlerContext.QueryModelVisitor.QueryCompilationContext + .QuerySourceRequiresMaterialization(groupResultOperator) + && (groupResultOperator.ElementSelector is QuerySourceReferenceExpression elementQsre + || sqlTranslatingExpressionVisitor.Visit(groupResultOperator.ElementSelector) != null) + && handlerContext.QueryModelVisitor.Expression is MethodCallExpression shapedQueryMethod + && shapedQueryMethod.Method.MethodIsClosedFormOf( + handlerContext.QueryModelVisitor.QueryCompilationContext.QueryMethodProvider.ShapedQueryMethod)) + { + // GroupBy Aggregate + // TODO: InjectParameters type Expression. + Expression[] key = null; + var groupByKeyMemberInfo = groupResultOperator.ItemType.GetMember("Key")[0]; + + switch (groupResultOperator.KeySelector) + { + case MemberExpression memberExpression: + var sql = sqlTranslatingExpressionVisitor.Visit(memberExpression); + selectExpression.SetProjectionForMemberInfo(groupByKeyMemberInfo, sql); + key = new[] { sql }; + break; + + + case NewExpression newExpression: + key = VisitAndSaveMapping( + sqlTranslatingExpressionVisitor, + selectExpression, + newExpression); + break; + + case MemberInitExpression memberInitExpression: + key = VisitAndSaveMapping( + sqlTranslatingExpressionVisitor, + selectExpression, + memberInitExpression); + break; + } + + if (key != null + || groupResultOperator.KeySelector is ConstantExpression + || groupResultOperator.KeySelector is ParameterExpression) + { + if (!(groupResultOperator.ElementSelector is QuerySourceReferenceExpression)) + { + handlerContext.QueryModelVisitor.VisitSelectClause( + new SelectClause(groupResultOperator.ElementSelector), + handlerContext.QueryModel); + + if (selectExpression.Projection.Count > 1) + { + selectExpression.ClearProjection(); + } + } + + if (key == null) + { + var constantOrParameter = groupResultOperator.KeySelector; + var subquery = selectExpression.PushDownSubquery(); + + if (subquery.Projection.Count == 0) + { + selectExpression.IsProjectStar = false; + } + else + { + selectExpression.ExplodeStarProjection(); + } + + var projectionIndex = subquery.AddToProjection(constantOrParameter, resetProjectStar: false); + subquery.SetProjectionForMemberInfo(groupByKeyMemberInfo, constantOrParameter); + + var projection = subquery.Projection[projectionIndex].LiftExpressionFromSubquery(subquery); + selectExpression.SetProjectionForMemberInfo(groupByKeyMemberInfo, projection); + + key = new[] { projection }; + } + + selectExpression.AddToGroupBy(key); + + return Expression.Call( + handlerContext.QueryModelVisitor.QueryCompilationContext.QueryMethodProvider + .ShapedQueryMethod.MakeGenericMethod(typeof(ValueBuffer)), + shapedQueryMethod.Arguments[0], + shapedQueryMethod.Arguments[1], + Expression.Constant(new ValueBufferShaper(groupResultOperator))); + } + } + sqlExpression = sqlTranslatingExpressionVisitor.Visit(groupResultOperator.KeySelector); @@ -497,6 +587,88 @@ var newGroupByCall : oldGroupByCall; } + + private static Expression[] VisitAndSaveMapping( + SqlTranslatingExpressionVisitor sqlTranslator, + SelectExpression selectExpression, + NewExpression newExpression) + { + var translation = new Expression[newExpression.Arguments.Count]; + + for (var i = 0; i < newExpression.Arguments.Count; i++) + { + var sourceExpression = newExpression.Arguments[i]; + + var sqlExpression = sqlTranslator.Visit(sourceExpression); + + Debug.Assert(sqlExpression != null); + + if (sqlExpression is ConstantExpression constantExpression + && constantExpression.Value is Expression[]) + { + // Encountered anonymous object inside anonymous object so abort + return null; + } + + + var memberInfo = newExpression.Members?[i]; + + if (memberInfo != null) + { + selectExpression.SetProjectionForMemberInfo( + memberInfo, + sqlExpression); + } + + translation[i] = sqlExpression; + } + + return translation; + } + + private static Expression[] VisitAndSaveMapping( + SqlTranslatingExpressionVisitor sqlTranslator, + SelectExpression selectExpression, + MemberInitExpression memberInitExpression) + { + var translation = new Expression[memberInitExpression.Bindings.Count]; + + for (var i = 0; i < memberInitExpression.Bindings.Count; i++) + { + if (memberInitExpression.Bindings[i] is MemberAssignment sourceExpression) + { + var sqlExpression = sqlTranslator.Visit(sourceExpression.Expression); + + Debug.Assert(sqlExpression != null); + + if (sqlExpression is ConstantExpression constantExpression + && constantExpression.Value is Expression[]) + { + // Encountered anonymous object inside DTO so abort + return null; + } + + var memberInfo = sourceExpression.Member; + + if (memberInfo != null) + { + selectExpression.SetProjectionForMemberInfo( + memberInfo, + sqlExpression); + } + + translation[i] = sqlExpression; + } + else + { + // Unknown binding + return null; + } + } + + return translation; + } + private static readonly MethodInfo _groupByAsync = typeof(RelationalResultOperatorHandler) .GetTypeInfo() @@ -835,20 +1007,9 @@ private static readonly MethodInfo _transformClientExpressionMethodInfo private static Expression TransformClientExpression( HandlerContext handlerContext, bool throwOnNullResult = false) - { - var querySource - = handlerContext.QueryModel.BodyClauses - .OfType() - .LastOrDefault() - ?? handlerContext.QueryModel.MainFromClause; - - var visitor - = new ResultTransformingExpressionVisitor( - querySource, + => new ResultTransformingExpressionVisitor( handlerContext.QueryModelVisitor.QueryCompilationContext, - throwOnNullResult); - - return visitor.Visit(handlerContext.QueryModelVisitor.Expression); - } + throwOnNullResult) + .Visit(handlerContext.QueryModelVisitor.Expression); } } diff --git a/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs index d98d0a4e587..9c50f01c72e 100644 --- a/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryModelVisitor.cs @@ -55,7 +55,6 @@ private readonly Dictionary _subQuery private bool _requiresClientProjection; private bool _requiresClientOrderBy; private bool _requiresClientResultOperator; - private ResultOperatorBase _groupResultOperatorInQueryModel; private readonly List _unflattenedGroupJoinClauses = new List(); private readonly List _flattenedAdditionalFromClauses = new List(); @@ -291,11 +290,6 @@ public override void VisitQueryModel(QueryModel queryModel) base.VisitQueryModel(queryModel); - if (RequiresStreamingGroupResultOperator) - { - WarnClientEval(queryModel, _groupResultOperatorInQueryModel); - } - var joinEliminator = new JoinEliminator(); var compositePredicateVisitor = _compositePredicateExpressionVisitorFactory.Create(); @@ -379,7 +373,7 @@ var newProjection break; - // TODO: Visit sub-query (SelectExpression) here? + // TODO: Visit sub-query (SelectExpression) here? } } @@ -834,7 +828,9 @@ var subQueryModelVisitor || !subSelectExpression.IsCorrelated() || !(querySource is AdditionalFromClause))) { - if (!subSelectExpression.IsIdentityQuery()) + if (!subSelectExpression.IsIdentityQuery() + // If the query has GroupBy then we don't need to pushdown since we can compose further. + && subSelectExpression.GroupBy.Count == 0) { subSelectExpression.PushDownSubquery().QuerySource = querySource; } @@ -1213,7 +1209,7 @@ public override void VisitResultOperator(ResultOperatorBase resultOperator, Quer if (RequiresStreamingGroupResultOperator) { - _groupResultOperatorInQueryModel = resultOperator; + WarnClientEval(queryModel, resultOperator); } if (RequiresClientResultOperator) @@ -1591,8 +1587,7 @@ var joinExpression foreach (var mapping in previousMapping) { - QueryCompilationContext.QuerySourceMapping - .ReplaceMapping(mapping.Key, mapping.Value); + QueryCompilationContext.AddOrUpdateMapping(mapping.Key, mapping.Value); } } else @@ -1792,7 +1787,7 @@ var newExpression return true; } - private bool IsFlattenableGroupJoinDefaultIfEmpty( + private static bool IsFlattenableGroupJoinDefaultIfEmpty( [NotNull] GroupJoinClause groupJoinClause, QueryModel queryModel, int index) diff --git a/src/EFCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs b/src/EFCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs index 6013233637f..b8f07f22d1f 100644 --- a/src/EFCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs +++ b/src/EFCore.Relational/Query/Sql/DefaultQuerySqlGenerator.cs @@ -253,6 +253,11 @@ public virtual Expression VisitSelect(SelectExpression selectExpression) _relationalCommandBuilder.Append("GROUP BY "); GenerateList(selectExpression.GroupBy); + + if (selectExpression.Having != null) + { + GenerateHaving(selectExpression.Having); + } } if (selectExpression.OrderBy.Count > 0) @@ -523,6 +528,20 @@ protected virtual void GeneratePredicate([NotNull] Expression predicate) Visit(optimizedPredicate); } + /// + /// Visit the predicate in SQL HAVING clause + /// + /// The having predicate expression. + protected virtual void GenerateHaving([NotNull] Expression predicate) + { + var optimizedPredicate = ApplyOptimizations(predicate, searchCondition: true); + + _relationalCommandBuilder.AppendLine() + .Append("HAVING "); + + Visit(optimizedPredicate); + } + private static bool? GetBooleanConstantValue(Expression expression) => expression is ConstantExpression constantExpression && constantExpression.Type.UnwrapNullableType() == typeof(bool) diff --git a/src/EFCore.Specification.Tests/Query/AsyncGroupByQueryTestBase.cs b/src/EFCore.Specification.Tests/Query/AsyncGroupByQueryTestBase.cs index b863fbee7b6..76d89a0c8ef 100644 --- a/src/EFCore.Specification.Tests/Query/AsyncGroupByQueryTestBase.cs +++ b/src/EFCore.Specification.Tests/Query/AsyncGroupByQueryTestBase.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.Threading.Tasks; using Microsoft.EntityFrameworkCore.TestModels.Northwind; @@ -113,7 +114,7 @@ await AssertQuery( new { g.Key, - Average = g.LongCount() + LongCount = g.LongCount() }), e => e.Key); } @@ -127,7 +128,7 @@ await AssertQuery( new { g.Key, - Average = g.Max(o => o.OrderID) + Max = g.Max(o => o.OrderID) }), e => e.Key); } @@ -141,7 +142,7 @@ await AssertQuery( new { g.Key, - Average = g.Min(o => o.OrderID) + Min = g.Min(o => o.OrderID) }), e => e.Key); } @@ -155,7 +156,7 @@ await AssertQuery( new { g.Key, - Average = g.Sum(o => o.OrderID) + Sum = g.Sum(o => o.OrderID) }), e => e.Key); } @@ -250,6 +251,19 @@ await AssertQuery( e => e.Min + " " + e.Max); } + [ConditionalFact] + public virtual async Task GroupBy_anonymous_with_alias_Select_Key_Sum() + { + await AssertQuery( + os => os.GroupBy(o => new { Id = o.CustomerID }).Select( + g => + new + { + Key = g.Key.Id, + Sum = g.Sum(o => o.OrderID) + })); + } + [ConditionalFact] public virtual async Task GroupBy_Composite_Select_Average() { @@ -339,7 +353,7 @@ await AssertQuery( new { g.Key, - Average = g.LongCount() + LongCount = g.LongCount() }), e => e.Key.CustomerID + " " + e.Key.EmployeeID); } @@ -353,7 +367,7 @@ await AssertQuery( new { g.Key, - Average = g.Max(o => o.OrderID) + Max = g.Max(o => o.OrderID) }), e => e.Key.CustomerID + " " + e.Key.EmployeeID); } @@ -367,7 +381,7 @@ await AssertQuery( new { g.Key, - Average = g.Min(o => o.OrderID) + Min = g.Min(o => o.OrderID) }), e => e.Key.CustomerID + " " + e.Key.EmployeeID); } @@ -381,7 +395,7 @@ await AssertQuery( new { g.Key, - Average = g.Sum(o => o.OrderID) + Sum = g.Sum(o => o.OrderID) }), e => e.Key.CustomerID + " " + e.Key.EmployeeID); } @@ -438,6 +452,115 @@ await AssertQuery( e => e.Min + " " + e.Max); } + [ConditionalFact] + public virtual async Task GroupBy_Dto_as_key_Select_Sum() + { + await AssertQuery( + os => os.GroupBy(o => new NominalType { CustomerID = o.CustomerID, EmployeeID = o.EmployeeID }).Select( + g => + new + { + Sum = g.Sum(o => o.OrderID), + g.Key, + })); + } + + [ConditionalFact] + public virtual async Task GroupBy_Dto_as_element_selector_Select_Sum() + { + await AssertQuery( + os => os.GroupBy( + o => o.CustomerID, + o => new NominalType { CustomerID = o.CustomerID, EmployeeID = o.EmployeeID }) + .Select( + g => + new + { + Sum = g.Sum(o => o.EmployeeID), + g.Key, + })); + } + + protected class NominalType + { + public string CustomerID { get; set; } + public uint? EmployeeID { get; set; } + + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((NominalType)obj); + } + + public override int GetHashCode() => 0; + + private bool Equals(NominalType other) + => string.Equals(CustomerID, other.CustomerID) + && EmployeeID == other.EmployeeID; + } + + [ConditionalFact] + public virtual async Task GroupBy_Composite_Select_Dto_Sum_Min_Key_flattened_Max_Avg() + { + await AssertQuery( + os => os.GroupBy(o => new { o.CustomerID, o.EmployeeID }).Select( + g => + new CompositeDto + { + Sum = g.Sum(o => o.OrderID), + Min = g.Min(o => o.OrderID), + CustomerId = g.Key.CustomerID, + EmployeeId = g.Key.EmployeeID, + Max = g.Max(o => o.OrderID), + Avg = g.Average(o => o.OrderID) + }), + e => e.CustomerId + " " + e.EmployeeId); + } + + protected class CompositeDto + { + public int Sum { get; set; } + public int Min { get; set; } + public int Max { get; set; } + public double Avg { get; set; } + public string CustomerId { get; set; } + public uint? EmployeeId { get; set; } + + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((CompositeDto)obj); + } + + public override int GetHashCode() => 0; + + private bool Equals(CompositeDto other) + => Sum == other.Sum + && Min == other.Min + && Max == other.Max + && Avg == other.Avg + && EmployeeId == other.EmployeeId + && string.Equals(CustomerId, other.CustomerId); + } + [ConditionalFact] public virtual async Task GroupBy_Composite_Select_Sum_Min_part_Key_flattened_Max_Avg() { @@ -455,6 +578,87 @@ await AssertQuery( e => e.Min + " " + e.Max); } + [ConditionalFact] + public virtual async Task GroupBy_Constant_Select_Sum_Min_Key_Max_Avg() + { + await AssertQuery( + os => os.GroupBy(o => 2).Select( + g => + new + { + Sum = g.Sum(o => o.OrderID), + Min = g.Min(o => o.OrderID), + g.Key, + Max = g.Max(o => o.OrderID), + Avg = g.Average(o => o.OrderID) + }), + e => e.Min + " " + e.Max); + } + + [ConditionalFact] + public virtual async Task GroupBy_after_predicate_Constant_Select_Sum_Min_Key_Max_Avg() + { + await AssertQuery( + os => os.Where(o => o.OrderID > 10500).GroupBy(o => 2).Select( + g => + new + { + Sum = g.Sum(o => o.OrderID), + Min = g.Min(o => o.OrderID), + Random = g.Key, + Max = g.Max(o => o.OrderID), + Avg = g.Average(o => o.OrderID) + }), + e => e.Min + " " + e.Max); + } + + [ConditionalFact] + public virtual async Task GroupBy_Constant_with_element_selector_Select_Sum_Min_Key_Max_Avg() + { + await AssertQuery( + os => os.GroupBy(o => 2, o => o.OrderID).Select( + g => + new + { + Sum = g.Sum(), + g.Key, + }), + e => e.Sum); + } + + [ConditionalFact] + public virtual async Task GroupBy_param_Select_Sum_Min_Key_Max_Avg() + { + var a = 2; + await AssertQuery( + os => os.GroupBy(o => a).Select( + g => + new + { + Sum = g.Sum(o => o.OrderID), + Min = g.Min(o => o.OrderID), + g.Key, + Max = g.Max(o => o.OrderID), + Avg = g.Average(o => o.OrderID) + }), + e => e.Min + " " + e.Max); + } + + [ConditionalFact] + public virtual async Task GroupBy_param_with_element_selector_Select_Sum_Min_Key_Max_Avg() + { + var a = 2; + await AssertQuery( + os => os.GroupBy(o => a, o => o.OrderID).Select( + g => + new + { + Sum = g.Sum(), + g.Key, + }), + e => e.Sum); + } + #endregion #region GroupByWithElementSelectorAggregate @@ -658,6 +862,21 @@ on o.CustomerID equals c.CustomerID e => e.Key); } + [ConditionalFact] + public virtual async Task GroupBy_required_navigation_member_Aggregate() + { + await AssertQuery( + ods => + ods.GroupBy(od => od.Order.CustomerID) + .Select(g => + new + { + CustomerId = g.Key, + Count = g.Count() + }), + e => e.CustomerId); + } + [ConditionalFact] public virtual async Task Join_complex_GroupBy_Aggregate() { @@ -716,6 +935,21 @@ from c in grouping e => e.Key); } + [ConditionalFact] + public virtual async Task GroupBy_optional_navigation_member_Aggregate() + { + await AssertQuery( + os => + os.GroupBy(o => o.Customer.Country) + .Select(g => + new + { + Country = g.Key, + Count = g.Count() + }), + e => e.Country); + } + [ConditionalFact] public virtual async Task GroupJoin_complex_GroupBy_Aggregate() { @@ -745,6 +979,21 @@ on o1.OrderID equals o2.OrderID e => e.Key); } + [ConditionalFact] + public virtual async Task GroupBy_multi_navigation_members_Aggregate() + { + await AssertQuery( + ods => + ods.GroupBy(od => new { od.Order.CustomerID, od.Product.ProductName }) + .Select(g => + new + { + CompositeKey = g.Key, + Count = g.Count() + }), + e => e.CompositeKey.CustomerID + " " + e.CompositeKey.ProductName); + } + [ConditionalFact(Skip = "Unable to bind group by. See Issue#6658")] public virtual async Task Union_simple_groupby() { @@ -763,7 +1012,99 @@ await AssertQuery( #endregion - #region GroupByAggregateVariations + #region GroupByAggregateComposition + + [ConditionalFact] + public virtual async Task GroupBy_OrderBy_key() + { + await AssertQuery( + os => + os.GroupBy(o => o.CustomerID) + .OrderBy(o => o.Key) + .Select(g => new { g.Key, c = g.Count() }), + assertOrder: true); + } + + [ConditionalFact] + public virtual async Task GroupBy_OrderBy_count() + { + await AssertQuery( + os => + os.GroupBy(o => o.CustomerID) + .OrderBy(o => o.Count()) + .ThenBy(o => o.Key) + .Select(g => new { g.Key, Count = g.Count() }), + assertOrder: true); + } + + [ConditionalFact] + public virtual async Task GroupBy_OrderBy_count_Select_sum() + { + await AssertQuery( + os => + os.GroupBy(o => o.CustomerID) + .OrderBy(o => o.Count()) + .ThenBy(o => o.Key) + .Select(g => new { g.Key, Sum = g.Sum(o => o.OrderID) }), + assertOrder: true); + } + + [ConditionalFact(Skip = "Issue#11251")] + public virtual async Task GroupBy_OrderBy_count_Select_sum_over_unmapped_property() + { + await AssertQuery( + os => + os.GroupBy(o => o.CustomerID) + .OrderBy(o => o.Count()) + .ThenBy(o => o.Key) + .Select(g => new { g.Key, Sum = g.Sum(o => o.Freight) }), + assertOrder: true); + } + + [ConditionalFact] + public virtual async Task GroupBy_filter_key() + { + await AssertQuery( + os => + os.GroupBy(o => o.CustomerID) + .Where(o => o.Key == "ALFKI") + .Select(g => new { g.Key, c = g.Count() })); + } + + [ConditionalFact] + public virtual async Task GroupBy_filter_count() + { + await AssertQuery( + os => + os.GroupBy(o => o.CustomerID) + .Where(o => o.Count() > 4) + .Select(g => new { g.Key, Count = g.Count() })); + } + + [ConditionalFact] + public virtual async Task GroupBy_filter_count_OrderBy_count_Select_sum() + { + await AssertQuery( + os => + os.GroupBy(o => o.CustomerID) + .Where(o => o.Count() > 4) + .OrderBy(o => o.Count()) + .ThenBy(o => o.Key) + .Select(g => new { g.Key, Count = g.Count(), Sum = g.Sum(o => o.OrderID) })); + } + + [ConditionalFact(Skip = "Issue#10012")] + public virtual async Task GroupBy_Aggregate_Join() + { + await AssertQuery( + (os, cs) => + from a in os.GroupBy(o => o.CustomerID) + .Where(g => g.Count() > 5) + .Select(g => new { CustomerID = g.Key, LastOrderID = g.Max(o => o.OrderID) }) + join c in cs on a.CustomerID equals c.CustomerID + join o in os on a.LastOrderID equals o.OrderID + select new { c, o }); + } [ConditionalFact] public virtual async Task GroupBy_with_result_selector() @@ -792,10 +1133,6 @@ await AssertQueryScalar( os => os.GroupBy(o => o.CustomerID).Select(g => g.Sum(e => 1))); } - #endregion - - #region GroupByKeyBinding - [ConditionalFact] public virtual async Task Distinct_GroupBy_OrderBy_key() { @@ -821,7 +1158,7 @@ public virtual async Task Select_nested_collection_with_groupby() .Select( c => c.Orders.Any() ? c.Orders.GroupBy(o => o.OrderID).Select(g => g.Key).ToArray() - : new int[0]).ToList(); + : Array.Empty()).ToList(); var query = context.Customers // ReSharper disable once StringStartsWithIsCultureSpecific @@ -829,7 +1166,7 @@ public virtual async Task Select_nested_collection_with_groupby() .Select( c => c.Orders.Any() ? c.Orders.GroupBy(o => o.OrderID).Select(g => g.Key).ToArray() - : new int[0]); + : Array.Empty()); var result = await query.ToListAsync(); @@ -1069,12 +1406,12 @@ await AssertQuery( os => os.GroupBy(o => o.CustomerID).OrderBy(g => g.Key).Select(g => new { Foo = "Foo", Group = g }), e => GroupingSorter()(e.Group), elementAsserter: (e, a) => - { - Assert.Equal(e.Foo, a.Foo); - IGrouping eGrouping = e.Group; - IGrouping aGrouping = a.Group; - Assert.Equal(eGrouping.OrderBy(p => p.OrderID), aGrouping.OrderBy(p => p.OrderID)); - }, + { + Assert.Equal(e.Foo, a.Foo); + IGrouping eGrouping = e.Group; + IGrouping aGrouping = a.Group; + Assert.Equal(eGrouping.OrderBy(p => p.OrderID), aGrouping.OrderBy(p => p.OrderID)); + }, entryCount: 830); } @@ -1114,6 +1451,7 @@ public virtual async Task GroupBy_Distinct() { await AssertQuery( os => + // TODO: See issue#11215 os.GroupBy(o => o.CustomerID).Distinct().Select(g => g.Key)); } @@ -1156,6 +1494,8 @@ await AssertQuery( elementAsserter: GroupingAsserter(d => d.EmployeeID)); } + + [ConditionalFact] public virtual async Task GroupBy_with_aggregate_through_navigation_property() { @@ -1302,6 +1642,6 @@ group o by c into grp } } - #endregion - } + #endregion + } } diff --git a/src/EFCore.Specification.Tests/Query/GroupByQueryTestBase.cs b/src/EFCore.Specification.Tests/Query/GroupByQueryTestBase.cs index fc2ec6258eb..3b63e2a1a55 100644 --- a/src/EFCore.Specification.Tests/Query/GroupByQueryTestBase.cs +++ b/src/EFCore.Specification.Tests/Query/GroupByQueryTestBase.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 Microsoft.EntityFrameworkCore.TestModels.Northwind; using Microsoft.EntityFrameworkCore.TestUtilities; @@ -116,7 +117,7 @@ public virtual void GroupBy_Property_Select_Key_LongCount() new { g.Key, - Average = g.LongCount() + LongCount = g.LongCount() }), e => e.Key); } @@ -130,7 +131,7 @@ public virtual void GroupBy_Property_Select_Key_Max() new { g.Key, - Average = g.Max(o => o.OrderID) + Max = g.Max(o => o.OrderID) }), e => e.Key); } @@ -144,7 +145,7 @@ public virtual void GroupBy_Property_Select_Key_Min() new { g.Key, - Average = g.Min(o => o.OrderID) + Min = g.Min(o => o.OrderID) }), e => e.Key); } @@ -158,7 +159,7 @@ public virtual void GroupBy_Property_Select_Key_Sum() new { g.Key, - Average = g.Sum(o => o.OrderID) + Sum = g.Sum(o => o.OrderID) }), e => e.Key); } @@ -253,6 +254,19 @@ public virtual void GroupBy_anonymous_Select_Sum_Min_Max_Avg() e => e.Min + " " + e.Max); } + [ConditionalFact] + public virtual void GroupBy_anonymous_with_alias_Select_Key_Sum() + { + AssertQuery( + os => os.GroupBy(o => new { Id = o.CustomerID }).Select( + g => + new + { + Key = g.Key.Id, + Sum = g.Sum(o => o.OrderID) + })); + } + [ConditionalFact] public virtual void GroupBy_Composite_Select_Average() { @@ -342,7 +356,7 @@ public virtual void GroupBy_Composite_Select_Key_LongCount() new { g.Key, - Average = g.LongCount() + LongCount = g.LongCount() }), e => e.Key.CustomerID + " " + e.Key.EmployeeID); } @@ -356,7 +370,7 @@ public virtual void GroupBy_Composite_Select_Key_Max() new { g.Key, - Average = g.Max(o => o.OrderID) + Max = g.Max(o => o.OrderID) }), e => e.Key.CustomerID + " " + e.Key.EmployeeID); } @@ -370,7 +384,7 @@ public virtual void GroupBy_Composite_Select_Key_Min() new { g.Key, - Average = g.Min(o => o.OrderID) + Min = g.Min(o => o.OrderID) }), e => e.Key.CustomerID + " " + e.Key.EmployeeID); } @@ -384,7 +398,7 @@ public virtual void GroupBy_Composite_Select_Key_Sum() new { g.Key, - Average = g.Sum(o => o.OrderID) + Sum = g.Sum(o => o.OrderID) }), e => e.Key.CustomerID + " " + e.Key.EmployeeID); } @@ -441,6 +455,115 @@ public virtual void GroupBy_Composite_Select_Sum_Min_Key_flattened_Max_Avg() e => e.Min + " " + e.Max); } + [ConditionalFact] + public virtual void GroupBy_Dto_as_key_Select_Sum() + { + AssertQuery( + os => os.GroupBy(o => new NominalType { CustomerID = o.CustomerID, EmployeeID = o.EmployeeID }).Select( + g => + new + { + Sum = g.Sum(o => o.OrderID), + g.Key, + })); + } + + [ConditionalFact] + public virtual void GroupBy_Dto_as_element_selector_Select_Sum() + { + AssertQuery( + os => os.GroupBy( + o => o.CustomerID, + o => new NominalType { CustomerID = o.CustomerID, EmployeeID = o.EmployeeID }) + .Select( + g => + new + { + Sum = g.Sum(o => o.EmployeeID), + g.Key, + })); + } + + protected class NominalType + { + public string CustomerID { get; set; } + public uint? EmployeeID { get; set; } + + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((NominalType)obj); + } + + public override int GetHashCode() => 0; + + private bool Equals(NominalType other) + => string.Equals(CustomerID, other.CustomerID) + && EmployeeID == other.EmployeeID; + } + + [ConditionalFact] + public virtual void GroupBy_Composite_Select_Dto_Sum_Min_Key_flattened_Max_Avg() + { + AssertQuery( + os => os.GroupBy(o => new { o.CustomerID, o.EmployeeID }).Select( + g => + new CompositeDto + { + Sum = g.Sum(o => o.OrderID), + Min = g.Min(o => o.OrderID), + CustomerId = g.Key.CustomerID, + EmployeeId = g.Key.EmployeeID, + Max = g.Max(o => o.OrderID), + Avg = g.Average(o => o.OrderID) + }), + e => e.CustomerId + " " + e.EmployeeId); + } + + protected class CompositeDto + { + public int Sum { get; set; } + public int Min { get; set; } + public int Max { get; set; } + public double Avg { get; set; } + public string CustomerId { get; set; } + public uint? EmployeeId { get; set; } + + public override bool Equals(object obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + return obj.GetType() == GetType() && Equals((CompositeDto)obj); + } + + public override int GetHashCode() => 0; + + private bool Equals(CompositeDto other) + => Sum == other.Sum + && Min == other.Min + && Max == other.Max + && Avg == other.Avg + && EmployeeId == other.EmployeeId + && string.Equals(CustomerId, other.CustomerId); + } + [ConditionalFact] public virtual void GroupBy_Composite_Select_Sum_Min_part_Key_flattened_Max_Avg() { @@ -458,6 +581,87 @@ public virtual void GroupBy_Composite_Select_Sum_Min_part_Key_flattened_Max_Avg( e => e.Min + " " + e.Max); } + [ConditionalFact] + public virtual void GroupBy_Constant_Select_Sum_Min_Key_Max_Avg() + { + AssertQuery( + os => os.GroupBy(o => 2).Select( + g => + new + { + Sum = g.Sum(o => o.OrderID), + Min = g.Min(o => o.OrderID), + g.Key, + Max = g.Max(o => o.OrderID), + Avg = g.Average(o => o.OrderID) + }), + e => e.Min + " " + e.Max); + } + + [ConditionalFact] + public virtual void GroupBy_after_predicate_Constant_Select_Sum_Min_Key_Max_Avg() + { + AssertQuery( + os => os.Where(o => o.OrderID > 10500).GroupBy(o => 2).Select( + g => + new + { + Sum = g.Sum(o => o.OrderID), + Min = g.Min(o => o.OrderID), + Random = g.Key, + Max = g.Max(o => o.OrderID), + Avg = g.Average(o => o.OrderID) + }), + e => e.Min + " " + e.Max); + } + + [ConditionalFact] + public virtual void GroupBy_Constant_with_element_selector_Select_Sum_Min_Key_Max_Avg() + { + AssertQuery( + os => os.GroupBy(o => 2, o=> o.OrderID).Select( + g => + new + { + Sum = g.Sum(), + g.Key, + }), + e => e.Sum); + } + + [ConditionalFact] + public virtual void GroupBy_param_Select_Sum_Min_Key_Max_Avg() + { + var a = 2; + AssertQuery( + os => os.GroupBy(o => a).Select( + g => + new + { + Sum = g.Sum(o => o.OrderID), + Min = g.Min(o => o.OrderID), + g.Key, + Max = g.Max(o => o.OrderID), + Avg = g.Average(o => o.OrderID) + }), + e => e.Min + " " + e.Max); + } + + [ConditionalFact] + public virtual void GroupBy_param_with_element_selector_Select_Sum_Min_Key_Max_Avg() + { + var a = 2; + AssertQuery( + os => os.GroupBy(o => a, o => o.OrderID).Select( + g => + new + { + Sum = g.Sum(), + g.Key, + }), + e => e.Sum); + } + #endregion #region GroupByWithElementSelectorAggregate @@ -661,6 +865,21 @@ on o.CustomerID equals c.CustomerID e => e.Key); } + [ConditionalFact] + public virtual void GroupBy_required_navigation_member_Aggregate() + { + AssertQuery( + ods => + ods.GroupBy(od => od.Order.CustomerID) + .Select(g => + new + { + CustomerId = g.Key, + Count = g.Count() + }), + e => e.CustomerId); + } + [ConditionalFact] public virtual void Join_complex_GroupBy_Aggregate() { @@ -719,6 +938,21 @@ from c in grouping e => e.Key); } + [ConditionalFact] + public virtual void GroupBy_optional_navigation_member_Aggregate() + { + AssertQuery( + os => + os.GroupBy(o => o.Customer.Country) + .Select(g => + new + { + Country = g.Key, + Count = g.Count() + }), + e => e.Country); + } + [ConditionalFact] public virtual void GroupJoin_complex_GroupBy_Aggregate() { @@ -748,6 +982,21 @@ on o1.OrderID equals o2.OrderID e => e.Key); } + [ConditionalFact] + public virtual void GroupBy_multi_navigation_members_Aggregate() + { + AssertQuery( + ods => + ods.GroupBy(od => new { od.Order.CustomerID, od.Product.ProductName }) + .Select(g => + new + { + CompositeKey = g.Key, + Count = g.Count() + }), + e => e.CompositeKey.CustomerID + " " + e.CompositeKey.ProductName); + } + [ConditionalFact(Skip = "Unable to bind group by. See Issue#6658")] public virtual void Union_simple_groupby() { @@ -766,7 +1015,99 @@ public virtual void Union_simple_groupby() #endregion - #region GroupByAggregateVariations + #region GroupByAggregateComposition + + [ConditionalFact] + public virtual void GroupBy_OrderBy_key() + { + AssertQuery( + os => + os.GroupBy(o => o.CustomerID) + .OrderBy(o => o.Key) + .Select(g => new { g.Key, c = g.Count() }), + assertOrder: true); + } + + [ConditionalFact] + public virtual void GroupBy_OrderBy_count() + { + AssertQuery( + os => + os.GroupBy(o => o.CustomerID) + .OrderBy(o => o.Count()) + .ThenBy(o => o.Key) + .Select(g => new { g.Key, Count = g.Count() }), + assertOrder: true); + } + + [ConditionalFact] + public virtual void GroupBy_OrderBy_count_Select_sum() + { + AssertQuery( + os => + os.GroupBy(o => o.CustomerID) + .OrderBy(o => o.Count()) + .ThenBy(o => o.Key) + .Select(g => new { g.Key, Sum = g.Sum(o => o.OrderID) }), + assertOrder: true); + } + + [ConditionalFact(Skip = "Issue#11251")] + public virtual void GroupBy_OrderBy_count_Select_sum_over_unmapped_property() + { + AssertQuery( + os => + os.GroupBy(o => o.CustomerID) + .OrderBy(o => o.Count()) + .ThenBy(o => o.Key) + .Select(g => new { g.Key, Sum = g.Sum(o => o.Freight) }), + assertOrder: true); + } + + [ConditionalFact] + public virtual void GroupBy_filter_key() + { + AssertQuery( + os => + os.GroupBy(o => o.CustomerID) + .Where(o => o.Key == "ALFKI") + .Select(g => new { g.Key, c = g.Count() })); + } + + [ConditionalFact] + public virtual void GroupBy_filter_count() + { + AssertQuery( + os => + os.GroupBy(o => o.CustomerID) + .Where(o => o.Count() > 4) + .Select(g => new { g.Key, Count = g.Count() })); + } + + [ConditionalFact] + public virtual void GroupBy_filter_count_OrderBy_count_Select_sum() + { + AssertQuery( + os => + os.GroupBy(o => o.CustomerID) + .Where(o => o.Count() > 4) + .OrderBy(o => o.Count()) + .ThenBy(o => o.Key) + .Select(g => new { g.Key, Count = g.Count(), Sum = g.Sum(o => o.OrderID) })); + } + + [ConditionalFact(Skip = "Issue#10012")] + public virtual void GroupBy_Aggregate_Join() + { + AssertQuery( + (os, cs) => + from a in os.GroupBy(o => o.CustomerID) + .Where(g => g.Count() > 5) + .Select(g => new { CustomerID = g.Key, LastOrderID = g.Max(o => o.OrderID) }) + join c in cs on a.CustomerID equals c.CustomerID + join o in os on a.LastOrderID equals o.OrderID + select new { c, o }); + } [ConditionalFact] public virtual void GroupBy_with_result_selector() @@ -795,10 +1136,6 @@ public virtual void GroupBy_Sum_constant() os => os.GroupBy(o => o.CustomerID).Select(g => g.Sum(e => 1))); } - #endregion - - #region GroupByKeyBinding - [ConditionalFact] public virtual void Distinct_GroupBy_OrderBy_key() { @@ -824,7 +1161,7 @@ public virtual void Select_nested_collection_with_groupby() .Select( c => c.Orders.Any() ? c.Orders.GroupBy(o => o.OrderID).Select(g => g.Key).ToArray() - : new int[0]).ToList(); + : Array.Empty()).ToList(); ClearLog(); @@ -834,7 +1171,7 @@ public virtual void Select_nested_collection_with_groupby() .Select( c => c.Orders.Any() ? c.Orders.GroupBy(o => o.OrderID).Select(g => g.Key).ToArray() - : new int[0]); + : Array.Empty()); var result = query.ToList(); @@ -1119,6 +1456,7 @@ public virtual void GroupBy_Distinct() { AssertQuery( os => + // TODO: See issue#11215 os.GroupBy(o => o.CustomerID).Distinct().Select(g => g.Key)); } @@ -1280,12 +1618,12 @@ public virtual void Join_GroupBy_entity_ToList() var actual = (from c in context.Customers.OrderBy(c => c.CustomerID).Take(5) join o in context.Orders.OrderBy(o => o.OrderID).Take(50) on c.CustomerID equals o.CustomerID - group o by c into grp - select new - { - C = grp.Key, - Os = grp.ToList() - }).ToList(); + group o by c into grp + select new + { + C = grp.Key, + Os = grp.ToList() + }).ToList(); var expected = (from c in Fixture.QueryAsserter.ExpectedData.Set() .OrderBy(c => c.CustomerID).Take(5) diff --git a/src/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs b/src/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs index b08e5ad464f..498d106f0ce 100644 --- a/src/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs +++ b/src/EFCore.Specification.Tests/Query/SimpleQueryTestBase.ResultOperators.cs @@ -1240,5 +1240,11 @@ public virtual void Paging_operation_on_string_doesnt_issue_warning() Assert.Equal(91, query.Count); } } + + [ConditionalFact] + public virtual void Project_constant_Sum() + { + AssertSingleResult(es => es.Sum(e => 1)); + } } } diff --git a/src/EFCore.Specification.Tests/TestModels/Northwind/NorthwindData.Objects.cs b/src/EFCore.Specification.Tests/TestModels/Northwind/NorthwindData.Objects.cs index c558c7b5152..54b326e8894 100644 --- a/src/EFCore.Specification.Tests/TestModels/Northwind/NorthwindData.Objects.cs +++ b/src/EFCore.Specification.Tests/TestModels/Northwind/NorthwindData.Objects.cs @@ -1798,7 +1798,7 @@ public static Product[] CreateProducts() new Product { ProductID = 22, - ProductName = "Gustaf's Kn„ckebr”d", + ProductName = "Gustaf's Knäckebröd", SupplierID = 9, CategoryID = 5, QuantityPerUnit = "24 - 500 g pkgs.", @@ -1811,7 +1811,7 @@ public static Product[] CreateProducts() new Product { ProductID = 23, - ProductName = "Tunnbr”d", + ProductName = "Tunnbröd", SupplierID = 9, CategoryID = 5, QuantityPerUnit = "12 - 250 g pkgs.", @@ -1824,7 +1824,7 @@ public static Product[] CreateProducts() new Product { ProductID = 24, - ProductName = "Guaran  Fant stica", + ProductName = "Guaraná Fantástica", SupplierID = 10, CategoryID = 1, QuantityPerUnit = "12 - 355 ml cans", @@ -1837,7 +1837,7 @@ public static Product[] CreateProducts() new Product { ProductID = 25, - ProductName = "NuNuCa Nuá-Nougat-Creme", + ProductName = "NuNuCa Nuß-Nougat-Creme", SupplierID = 11, CategoryID = 3, QuantityPerUnit = "20 - 450 g glasses", @@ -1850,7 +1850,7 @@ public static Product[] CreateProducts() new Product { ProductID = 26, - ProductName = "Gumb„r Gummib„rchen", + ProductName = "Gumbär Gummibärchen", SupplierID = 11, CategoryID = 3, QuantityPerUnit = "100 - 250 g bags", @@ -1876,7 +1876,7 @@ public static Product[] CreateProducts() new Product { ProductID = 28, - ProductName = "R”ssle Sauerkraut", + ProductName = "Rössle Sauerkraut", SupplierID = 12, CategoryID = 7, QuantityPerUnit = "25 - 825 g cans", @@ -1889,7 +1889,7 @@ public static Product[] CreateProducts() new Product { ProductID = 29, - ProductName = "Thringer Rostbratwurst", + ProductName = "Thüringer Rostbratwurst", SupplierID = 12, CategoryID = 6, QuantityPerUnit = "50 bags x 30 sausgs.", @@ -2006,7 +2006,7 @@ public static Product[] CreateProducts() new Product { ProductID = 38, - ProductName = "C“te de Blaye", + ProductName = "Côte de Blaye", SupplierID = 18, CategoryID = 1, QuantityPerUnit = "12 - 75 cl bottles", @@ -2214,7 +2214,7 @@ public static Product[] CreateProducts() new Product { ProductID = 54, - ProductName = "TourtiŠre", + ProductName = "Tourtière", SupplierID = 25, CategoryID = 6, QuantityPerUnit = "16 pies", @@ -2227,7 +2227,7 @@ public static Product[] CreateProducts() new Product { ProductID = 55, - ProductName = "Pƒt‚ chinois", + ProductName = "Pâté chinois", SupplierID = 25, CategoryID = 6, QuantityPerUnit = "24 boxes x 2 pies", @@ -2305,7 +2305,7 @@ public static Product[] CreateProducts() new Product { ProductID = 61, - ProductName = "Sirop d'‚rable", + ProductName = "Sirop d'érable", SupplierID = 29, CategoryID = 2, QuantityPerUnit = "24 - 500 ml bottles", @@ -2344,7 +2344,7 @@ public static Product[] CreateProducts() new Product { ProductID = 64, - ProductName = "Wimmers gute Semmelkn”del", + ProductName = "Wimmers gute Semmelknödel", SupplierID = 12, CategoryID = 5, QuantityPerUnit = "20 bags x 4 pieces", @@ -2461,7 +2461,7 @@ public static Product[] CreateProducts() new Product { ProductID = 73, - ProductName = "R”d Kaviar", + ProductName = "Röd Kaviar", SupplierID = 17, CategoryID = 8, QuantityPerUnit = "24 - 150 g jars", @@ -2487,7 +2487,7 @@ public static Product[] CreateProducts() new Product { ProductID = 75, - ProductName = "Rh”nbr„u Klosterbier", + ProductName = "Rhönbräu Klosterbier", SupplierID = 12, CategoryID = 1, QuantityPerUnit = "24 - 0.5 l bottles", @@ -2500,7 +2500,7 @@ public static Product[] CreateProducts() new Product { ProductID = 76, - ProductName = "Lakkalik””ri", + ProductName = "Lakkalikööri", SupplierID = 23, CategoryID = 1, QuantityPerUnit = "500 ml", @@ -2513,7 +2513,7 @@ public static Product[] CreateProducts() new Product { ProductID = 77, - ProductName = "Original Frankfurter grne Soáe", + ProductName = "Original Frankfurter grüne Soße", SupplierID = 12, CategoryID = 2, QuantityPerUnit = "12 boxes", diff --git a/src/EFCore.SqlServer/Query/ExpressionVisitors/Internal/SqlServerSqlTranslatingExpressionVisitorFactory.cs b/src/EFCore.SqlServer/Query/ExpressionVisitors/Internal/SqlServerSqlTranslatingExpressionVisitorFactory.cs index e867e946312..b84e8e8ba08 100644 --- a/src/EFCore.SqlServer/Query/ExpressionVisitors/Internal/SqlServerSqlTranslatingExpressionVisitorFactory.cs +++ b/src/EFCore.SqlServer/Query/ExpressionVisitors/Internal/SqlServerSqlTranslatingExpressionVisitorFactory.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.Diagnostics; using System.Linq.Expressions; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Query.Expressions; @@ -27,14 +28,13 @@ public SqlServerSqlTranslatingExpressionVisitorFactory( /// 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. /// + [DebuggerStepThrough] public override SqlTranslatingExpressionVisitor Create( RelationalQueryModelVisitor queryModelVisitor, SelectExpression targetSelectExpression = null, Expression topLevelPredicate = null, bool inProjection = false) - { - return new SqlServerSqlTranslatingExpressionVisitor( + => new SqlServerSqlTranslatingExpressionVisitor( Dependencies, queryModelVisitor, targetSelectExpression, topLevelPredicate, inProjection); - } } } diff --git a/src/EFCore.Sqlite.Core/Query/ExpressionVisitors/Internal/SqliteSqlTranslatingExpressionVisitorFactory.cs b/src/EFCore.Sqlite.Core/Query/ExpressionVisitors/Internal/SqliteSqlTranslatingExpressionVisitorFactory.cs index f006d7ac8a8..8831140b7f4 100644 --- a/src/EFCore.Sqlite.Core/Query/ExpressionVisitors/Internal/SqliteSqlTranslatingExpressionVisitorFactory.cs +++ b/src/EFCore.Sqlite.Core/Query/ExpressionVisitors/Internal/SqliteSqlTranslatingExpressionVisitorFactory.cs @@ -32,9 +32,7 @@ public override SqlTranslatingExpressionVisitor Create( SelectExpression targetSelectExpression = null, Expression topLevelPredicate = null, bool inProjection = false) - { - return new SqliteSqlTranslatingExpressionVisitor( + => new SqliteSqlTranslatingExpressionVisitor( Dependencies, queryModelVisitor, targetSelectExpression, topLevelPredicate, inProjection); - } } } diff --git a/src/EFCore/Query/EntityQueryModelVisitor.cs b/src/EFCore/Query/EntityQueryModelVisitor.cs index 57876bb4350..35a16de4616 100644 --- a/src/EFCore/Query/EntityQueryModelVisitor.cs +++ b/src/EFCore/Query/EntityQueryModelVisitor.cs @@ -1396,7 +1396,7 @@ var memberAccessExpression targetExpression, _queryCompilationContext.QuerySourceMapping.GetExpression(querySource)); - _queryCompilationContext.QuerySourceMapping.ReplaceMapping(querySource, memberAccessExpression); + _queryCompilationContext.AddOrUpdateMapping(querySource, memberAccessExpression); } private static Expression ShiftMemberAccess(Expression targetExpression, Expression currentExpression) diff --git a/src/EFCore/Query/ExpressionVisitors/Internal/CorrelatedCollectionOptimizingVisitor.cs b/src/EFCore/Query/ExpressionVisitors/Internal/CorrelatedCollectionOptimizingVisitor.cs index 15d51f80227..1f6f657b103 100644 --- a/src/EFCore/Query/ExpressionVisitors/Internal/CorrelatedCollectionOptimizingVisitor.cs +++ b/src/EFCore/Query/ExpressionVisitors/Internal/CorrelatedCollectionOptimizingVisitor.cs @@ -146,7 +146,7 @@ private Expression Rewrite( bool forceListResult) { var querySourceReferenceFindingExpressionTreeVisitor - = new QuerySourceReferenceFindingExpressionTreeVisitor(); + = new QuerySourceReferenceFindingExpressionVisitor(); var originalCorrelationPredicate = collectionQueryModel.BodyClauses.OfType() .Single(c => c.Predicate is NullSafeEqualExpression); @@ -156,7 +156,7 @@ var querySourceReferenceFindingExpressionTreeVisitor querySourceReferenceFindingExpressionTreeVisitor.Visit(keyEquality.Left); var parentQuerySourceReferenceExpression = querySourceReferenceFindingExpressionTreeVisitor.QuerySourceReferenceExpression; - querySourceReferenceFindingExpressionTreeVisitor = new QuerySourceReferenceFindingExpressionTreeVisitor(); + querySourceReferenceFindingExpressionTreeVisitor = new QuerySourceReferenceFindingExpressionVisitor(); querySourceReferenceFindingExpressionTreeVisitor.Visit(keyEquality.Right); var currentKey = BuildKeyAccess(navigation.ForeignKey.Properties, querySourceReferenceFindingExpressionTreeVisitor.QuerySourceReferenceExpression); diff --git a/src/EFCore/Query/ExpressionVisitors/Internal/QuerySourceReferenceFindingExpressionTreeVisitor.cs b/src/EFCore/Query/ExpressionVisitors/Internal/QuerySourceReferenceFindingExpressionVisitor.cs similarity index 94% rename from src/EFCore/Query/ExpressionVisitors/Internal/QuerySourceReferenceFindingExpressionTreeVisitor.cs rename to src/EFCore/Query/ExpressionVisitors/Internal/QuerySourceReferenceFindingExpressionVisitor.cs index 9614faf3ea2..f354bb8847a 100644 --- a/src/EFCore/Query/ExpressionVisitors/Internal/QuerySourceReferenceFindingExpressionTreeVisitor.cs +++ b/src/EFCore/Query/ExpressionVisitors/Internal/QuerySourceReferenceFindingExpressionVisitor.cs @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal /// 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 class QuerySourceReferenceFindingExpressionTreeVisitor : RelinqExpressionVisitor + public class QuerySourceReferenceFindingExpressionVisitor : RelinqExpressionVisitor { /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/src/EFCore/Query/ExpressionVisitors/Internal/RequiresMaterializationExpressionVisitor.cs b/src/EFCore/Query/ExpressionVisitors/Internal/RequiresMaterializationExpressionVisitor.cs index 7991a9c572d..88aaab26ff5 100644 --- a/src/EFCore/Query/ExpressionVisitors/Internal/RequiresMaterializationExpressionVisitor.cs +++ b/src/EFCore/Query/ExpressionVisitors/Internal/RequiresMaterializationExpressionVisitor.cs @@ -25,6 +25,16 @@ namespace Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal /// public class RequiresMaterializationExpressionVisitor : ExpressionVisitorBase { + private static readonly ISet _aggregateResultOperators = new HashSet + { + typeof(AverageResultOperator), + typeof(CountResultOperator), + typeof(LongCountResultOperator), + typeof(MaxResultOperator), + typeof(MinResultOperator), + typeof(SumResultOperator) + }; + private readonly IModel _model; private readonly EntityQueryModelVisitor _queryModelVisitor; private readonly Stack _queryModelStack = new Stack(); @@ -237,26 +247,29 @@ protected override Expression VisitSubQuery(SubQueryExpression expression) { _queryModelStack.Push(expression.QueryModel); - expression.QueryModel.TransformExpressions(Visit); + if (!IsGroupByAggregateSubQuery(expression.QueryModel)) + { + expression.QueryModel.TransformExpressions(Visit); - _queryModelStack.Pop(); + _queryModelStack.Pop(); - AdjustForResultOperators(expression.QueryModel); + AdjustForResultOperators(expression.QueryModel); - var parentQueryModel = _queryModelStack.Peek(); + var parentQueryModel = _queryModelStack.Peek(); - var referencedQuerySource = expression.QueryModel.SelectClause.Selector.TryGetReferencedQuerySource(); + var referencedQuerySource = expression.QueryModel.SelectClause.Selector.TryGetReferencedQuerySource(); - if (referencedQuerySource != null) - { - var parentQuerySource = parentQueryModel.SelectClause.Selector.TryGetReferencedQuerySource(); - if (parentQuerySource != null) + if (referencedQuerySource != null) { - var resultSetOperators = GetSetResultOperatorSourceExpressions(parentQueryModel.ResultOperators); - if (resultSetOperators.Any(r => r.Equals(expression)) - && _querySourceReferences[parentQuerySource] > 0) + var parentQuerySource = parentQueryModel.SelectClause.Selector.TryGetReferencedQuerySource(); + if (parentQuerySource != null) { - PromoteQuerySource(referencedQuerySource); + var resultSetOperators = GetSetResultOperatorSourceExpressions(parentQueryModel.ResultOperators); + if (resultSetOperators.Any(r => r.Equals(expression)) + && _querySourceReferences[parentQuerySource] > 0) + { + PromoteQuerySource(referencedQuerySource); + } } } } @@ -264,6 +277,20 @@ protected override Expression VisitSubQuery(SubQueryExpression expression) return expression; } + private static bool IsGroupByAggregateSubQuery(QueryModel queryModel) + { + if (queryModel.MainFromClause.FromExpression.Type.IsGrouping() + && queryModel.BodyClauses.Count == 0 + && queryModel.ResultOperators.Count == 1 + && !(queryModel.SelectClause.Selector is ConstantExpression) + && _aggregateResultOperators.Contains(queryModel.ResultOperators[0].GetType())) + { + return true; + } + + return false; + } + private void DemoteQuerySource(IQuerySource querySource) { HandleUnderlyingQuerySources(querySource, DemoteQuerySource); @@ -349,8 +376,64 @@ var underlyingExpression } } + private static IEnumerable TraverseQuerySources(Expression expression) + { + switch (expression) + { + case QuerySourceReferenceExpression qsre: + yield return qsre.ReferencedQuerySource; + break; + + case NewExpression newExpression: + foreach (var arg in newExpression.Arguments) + { + foreach (var querySource in TraverseQuerySources(arg)) + { + yield return querySource; + } + } + break; + + case MemberInitExpression memberInitExpression: + foreach (var memberBinding in memberInitExpression.Bindings) + { + if (memberBinding is MemberAssignment memberAssignment) + { + foreach (var querySource in TraverseQuerySources(memberAssignment.Expression)) + { + yield return querySource; + } + } + } + break; + } + } + private void AdjustForResultOperators(QueryModel queryModel) { + var isSubQuery = _queryModelStack.Count > 0; + var finalResultOperator = queryModel.ResultOperators.LastOrDefault(); + + if (isSubQuery && finalResultOperator is GroupResultOperator groupResultOperator) + { + if (!(groupResultOperator.KeySelector is MemberInitExpression)) + { + // This is to compensate for the fact that we have to demote querysources found in selector for GroupBy + // TODO: See #11215 + foreach (var querySource in TraverseQuerySources(queryModel.SelectClause.Selector)) + { + DemoteQuerySource(querySource); + } + + if (groupResultOperator.ElementSelector is QuerySourceReferenceExpression qsre) + { + DemoteQuerySource(qsre.ReferencedQuerySource); + } + + return; + } + } + var referencedQuerySource = queryModel.SelectClause.Selector.TryGetReferencedQuerySource() ?? queryModel.MainFromClause.FromExpression.TryGetReferencedQuerySource(); @@ -365,15 +448,13 @@ var referencedQuerySource return; } - var isSubQuery = _queryModelStack.Count > 0; - var finalResultOperator = queryModel.ResultOperators.LastOrDefault(); + // If the GroupResultOperator is not last (as captured by above) + // then we promote first GroupResultOperator to fall through streaming group by + var firstGroupResultOperator = queryModel.ResultOperators.OfType().FirstOrDefault(); - if (isSubQuery && finalResultOperator is GroupResultOperator) + if (firstGroupResultOperator != null) { - // These two lines should be uncommented to implement GROUP BY translation. - //DemoteQuerySource(referencedQuerySource); - //DemoteQuerySource(groupResultOperator); - return; + PromoteQuerySource(firstGroupResultOperator); } var unreachableFromParentSelector = @@ -396,7 +477,11 @@ var referencedQuerySource return; } - if (ConvergesToSingleValue(queryModel)) + if (ConvergesToSingleValue(queryModel) + // This is to preserve QuerySource from MainFromClause when selector is ConstantExpression + // Since we would cause client evaluation from ConstantExpression. + // TODO: See #11215 + && !(queryModel.SelectClause.Selector is ConstantExpression)) { // This is a top-level query that was not Single/First/Last // but returns a single/scalar value (Avg/Min/Max/etc.) @@ -410,6 +495,14 @@ var referencedQuerySource return; } + // If we are selecting from Grouping source but it is not GroupByAggregate Query + // then do not demote Grouping source since we still need to create groups. + if (referencedQuerySource.ItemType.IsGrouping() + && !IsGroupByAggregateSubQuery(queryModel)) + { + return; + } + DemoteQuerySourceAndUnderlyingFromClause(referencedQuerySource); return; } diff --git a/src/EFCore/Query/Internal/IncludeCompiler.CollectionQueryModelRewritingExpressionVisitor.cs b/src/EFCore/Query/Internal/IncludeCompiler.CollectionQueryModelRewritingExpressionVisitor.cs index 0d4d76459fa..ebed6d761fa 100644 --- a/src/EFCore/Query/Internal/IncludeCompiler.CollectionQueryModelRewritingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/IncludeCompiler.CollectionQueryModelRewritingExpressionVisitor.cs @@ -86,7 +86,7 @@ var navigation private void Rewrite(QueryModel collectionQueryModel, INavigation navigation) { var querySourceReferenceFindingExpressionTreeVisitor - = new QuerySourceReferenceFindingExpressionTreeVisitor(); + = new QuerySourceReferenceFindingExpressionVisitor(); var whereClause = collectionQueryModel.BodyClauses .OfType() diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GroupByQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GroupByQuerySqlServerTest.cs index 2ab0c15612c..eb983e23532 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GroupByQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GroupByQuerySqlServerTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; using Xunit.Abstractions; namespace Microsoft.EntityFrameworkCore.Query @@ -24,6 +25,9 @@ public override void GroupBy_Property_Select_Average() @"SELECT AVG(CAST([o].[OrderID] AS float)) FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); + + // Validating that we don't generate warning when translating GroupBy. See Issue#11157 + Assert.DoesNotContain("The LINQ expression 'GroupBy([o].CustomerID, [o])' could not be translated and will be evaluated locally.", Fixture.TestSqlLoggerFactory.Log); } public override void GroupBy_Property_Select_Count() @@ -81,7 +85,7 @@ public override void GroupBy_Property_Select_Sum_Min_Max_Avg() base.GroupBy_Property_Select_Sum_Min_Max_Avg(); AssertSql( - @"SELECT SUM([o].[OrderID]), MIN([o].[OrderID]), MAX([o].[OrderID]), AVG(CAST([o].[OrderID] AS float)) + @"SELECT SUM([o].[OrderID]) AS [Sum], MIN([o].[OrderID]) AS [Min], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg] FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -91,7 +95,7 @@ public override void GroupBy_Property_Select_Key_Average() base.GroupBy_Property_Select_Key_Average(); AssertSql( - @"SELECT [o].[CustomerID], AVG(CAST([o].[OrderID] AS float)) + @"SELECT [o].[CustomerID] AS [Key], AVG(CAST([o].[OrderID] AS float)) AS [Average] FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -101,7 +105,7 @@ public override void GroupBy_Property_Select_Key_Count() base.GroupBy_Property_Select_Key_Count(); AssertSql( - @"SELECT [o].[CustomerID], COUNT(*) + @"SELECT [o].[CustomerID] AS [Key], COUNT(*) AS [Count] FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -111,7 +115,7 @@ public override void GroupBy_Property_Select_Key_LongCount() base.GroupBy_Property_Select_Key_LongCount(); AssertSql( - @"SELECT [o].[CustomerID], COUNT_BIG(*) + @"SELECT [o].[CustomerID] AS [Key], COUNT_BIG(*) AS [LongCount] FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -121,7 +125,7 @@ public override void GroupBy_Property_Select_Key_Max() base.GroupBy_Property_Select_Key_Max(); AssertSql( - @"SELECT [o].[CustomerID], MAX([o].[OrderID]) + @"SELECT [o].[CustomerID] AS [Key], MAX([o].[OrderID]) AS [Max] FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -131,7 +135,7 @@ public override void GroupBy_Property_Select_Key_Min() base.GroupBy_Property_Select_Key_Min(); AssertSql( - @"SELECT [o].[CustomerID], MIN([o].[OrderID]) + @"SELECT [o].[CustomerID] AS [Key], MIN([o].[OrderID]) AS [Min] FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -141,7 +145,7 @@ public override void GroupBy_Property_Select_Key_Sum() base.GroupBy_Property_Select_Key_Sum(); AssertSql( - @"SELECT [o].[CustomerID], SUM([o].[OrderID]) + @"SELECT [o].[CustomerID] AS [Key], SUM([o].[OrderID]) AS [Sum] FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -151,7 +155,7 @@ public override void GroupBy_Property_Select_Key_Sum_Min_Max_Avg() base.GroupBy_Property_Select_Key_Sum_Min_Max_Avg(); AssertSql( - @"SELECT [o].[CustomerID], SUM([o].[OrderID]), MIN([o].[OrderID]), MAX([o].[OrderID]), AVG(CAST([o].[OrderID] AS float)) + @"SELECT [o].[CustomerID] AS [Key], SUM([o].[OrderID]) AS [Sum], MIN([o].[OrderID]) AS [Min], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg] FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -161,7 +165,7 @@ public override void GroupBy_Property_Select_Sum_Min_Key_Max_Avg() base.GroupBy_Property_Select_Sum_Min_Key_Max_Avg(); AssertSql( - @"SELECT SUM([o].[OrderID]), MIN([o].[OrderID]), [o].[CustomerID], MAX([o].[OrderID]), AVG(CAST([o].[OrderID] AS float)) + @"SELECT SUM([o].[OrderID]) AS [Sum], MIN([o].[OrderID]) AS [Min], [o].[CustomerID] AS [Key], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg] FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -231,7 +235,17 @@ public override void GroupBy_anonymous_Select_Sum_Min_Max_Avg() base.GroupBy_anonymous_Select_Sum_Min_Max_Avg(); AssertSql( - @"SELECT SUM([o].[OrderID]), MIN([o].[OrderID]), MAX([o].[OrderID]), AVG(CAST([o].[OrderID] AS float)) + @"SELECT SUM([o].[OrderID]) AS [Sum], MIN([o].[OrderID]) AS [Min], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg] +FROM [Orders] AS [o] +GROUP BY [o].[CustomerID]"); + } + + public override void GroupBy_anonymous_with_alias_Select_Key_Sum() + { + base.GroupBy_anonymous_with_alias_Select_Key_Sum(); + + AssertSql( + @"SELECT [o].[CustomerID] AS [Key], SUM([o].[OrderID]) AS [Sum] FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -301,7 +315,7 @@ public override void GroupBy_Composite_Select_Sum_Min_Max_Avg() base.GroupBy_Composite_Select_Sum_Min_Max_Avg(); AssertSql( - @"SELECT SUM([o].[OrderID]), MIN([o].[OrderID]), MAX([o].[OrderID]), AVG(CAST([o].[OrderID] AS float)) + @"SELECT SUM([o].[OrderID]) AS [Sum], MIN([o].[OrderID]) AS [Min], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg] FROM [Orders] AS [o] GROUP BY [o].[CustomerID], [o].[EmployeeID]"); } @@ -311,7 +325,7 @@ public override void GroupBy_Composite_Select_Key_Average() base.GroupBy_Composite_Select_Key_Average(); AssertSql( - @"SELECT [o].[CustomerID], [o].[EmployeeID], AVG(CAST([o].[OrderID] AS float)) + @"SELECT [o].[CustomerID], [o].[EmployeeID], AVG(CAST([o].[OrderID] AS float)) AS [Average] FROM [Orders] AS [o] GROUP BY [o].[CustomerID], [o].[EmployeeID]"); } @@ -321,7 +335,7 @@ public override void GroupBy_Composite_Select_Key_Count() base.GroupBy_Composite_Select_Key_Count(); AssertSql( - @"SELECT [o].[CustomerID], [o].[EmployeeID], COUNT(*) + @"SELECT [o].[CustomerID], [o].[EmployeeID], COUNT(*) AS [Count] FROM [Orders] AS [o] GROUP BY [o].[CustomerID], [o].[EmployeeID]"); } @@ -331,7 +345,7 @@ public override void GroupBy_Composite_Select_Key_LongCount() base.GroupBy_Composite_Select_Key_LongCount(); AssertSql( - @"SELECT [o].[CustomerID], [o].[EmployeeID], COUNT_BIG(*) + @"SELECT [o].[CustomerID], [o].[EmployeeID], COUNT_BIG(*) AS [LongCount] FROM [Orders] AS [o] GROUP BY [o].[CustomerID], [o].[EmployeeID]"); } @@ -341,7 +355,7 @@ public override void GroupBy_Composite_Select_Key_Max() base.GroupBy_Composite_Select_Key_Max(); AssertSql( - @"SELECT [o].[CustomerID], [o].[EmployeeID], MAX([o].[OrderID]) + @"SELECT [o].[CustomerID], [o].[EmployeeID], MAX([o].[OrderID]) AS [Max] FROM [Orders] AS [o] GROUP BY [o].[CustomerID], [o].[EmployeeID]"); } @@ -351,7 +365,7 @@ public override void GroupBy_Composite_Select_Key_Min() base.GroupBy_Composite_Select_Key_Min(); AssertSql( - @"SELECT [o].[CustomerID], [o].[EmployeeID], MIN([o].[OrderID]) + @"SELECT [o].[CustomerID], [o].[EmployeeID], MIN([o].[OrderID]) AS [Min] FROM [Orders] AS [o] GROUP BY [o].[CustomerID], [o].[EmployeeID]"); } @@ -361,7 +375,7 @@ public override void GroupBy_Composite_Select_Key_Sum() base.GroupBy_Composite_Select_Key_Sum(); AssertSql( - @"SELECT [o].[CustomerID], [o].[EmployeeID], SUM([o].[OrderID]) + @"SELECT [o].[CustomerID], [o].[EmployeeID], SUM([o].[OrderID]) AS [Sum] FROM [Orders] AS [o] GROUP BY [o].[CustomerID], [o].[EmployeeID]"); } @@ -371,7 +385,7 @@ public override void GroupBy_Composite_Select_Key_Sum_Min_Max_Avg() base.GroupBy_Composite_Select_Key_Sum_Min_Max_Avg(); AssertSql( - @"SELECT [o].[CustomerID], [o].[EmployeeID], SUM([o].[OrderID]), MIN([o].[OrderID]), MAX([o].[OrderID]), AVG(CAST([o].[OrderID] AS float)) + @"SELECT [o].[CustomerID], [o].[EmployeeID], SUM([o].[OrderID]) AS [Sum], MIN([o].[OrderID]) AS [Min], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg] FROM [Orders] AS [o] GROUP BY [o].[CustomerID], [o].[EmployeeID]"); } @@ -381,7 +395,7 @@ public override void GroupBy_Composite_Select_Sum_Min_Key_Max_Avg() base.GroupBy_Composite_Select_Sum_Min_Key_Max_Avg(); AssertSql( - @"SELECT SUM([o].[OrderID]), MIN([o].[OrderID]), [o].[CustomerID], [o].[EmployeeID], MAX([o].[OrderID]), AVG(CAST([o].[OrderID] AS float)) + @"SELECT SUM([o].[OrderID]) AS [Sum], MIN([o].[OrderID]) AS [Min], [o].[CustomerID], [o].[EmployeeID], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg] FROM [Orders] AS [o] GROUP BY [o].[CustomerID], [o].[EmployeeID]"); } @@ -391,7 +405,36 @@ public override void GroupBy_Composite_Select_Sum_Min_Key_flattened_Max_Avg() base.GroupBy_Composite_Select_Sum_Min_Key_flattened_Max_Avg(); AssertSql( - @"SELECT SUM([o].[OrderID]), MIN([o].[OrderID]), [o].[CustomerID], [o].[EmployeeID], MAX([o].[OrderID]), AVG(CAST([o].[OrderID] AS float)) + @"SELECT SUM([o].[OrderID]) AS [Sum], MIN([o].[OrderID]) AS [Min], [o].[CustomerID], [o].[EmployeeID], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg] +FROM [Orders] AS [o] +GROUP BY [o].[CustomerID], [o].[EmployeeID]"); + } + + public override void GroupBy_Dto_as_key_Select_Sum() + { + base.GroupBy_Dto_as_key_Select_Sum(); + + AssertSql( + @"SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] +FROM [Orders] AS [o0]"); + } + + public override void GroupBy_Dto_as_element_selector_Select_Sum() + { + base.GroupBy_Dto_as_element_selector_Select_Sum(); + + AssertSql( + @"SELECT [o].[CustomerID], [o].[EmployeeID] +FROM [Orders] AS [o] +ORDER BY [o].[CustomerID]"); + } + + public override void GroupBy_Composite_Select_Dto_Sum_Min_Key_flattened_Max_Avg() + { + base.GroupBy_Composite_Select_Dto_Sum_Min_Key_flattened_Max_Avg(); + + AssertSql( + @"SELECT SUM([o].[OrderID]) AS [Sum], MIN([o].[OrderID]) AS [Min], [o].[CustomerID], [o].[EmployeeID], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg] FROM [Orders] AS [o] GROUP BY [o].[CustomerID], [o].[EmployeeID]"); } @@ -401,11 +444,81 @@ public override void GroupBy_Composite_Select_Sum_Min_part_Key_flattened_Max_Avg base.GroupBy_Composite_Select_Sum_Min_part_Key_flattened_Max_Avg(); AssertSql( - @"SELECT SUM([o].[OrderID]), MIN([o].[OrderID]), [o].[CustomerID], MAX([o].[OrderID]), AVG(CAST([o].[OrderID] AS float)) + @"SELECT SUM([o].[OrderID]) AS [Sum], MIN([o].[OrderID]) AS [Min], [o].[CustomerID], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg] FROM [Orders] AS [o] GROUP BY [o].[CustomerID], [o].[EmployeeID]"); } + public override void GroupBy_Constant_Select_Sum_Min_Key_Max_Avg() + { + base.GroupBy_Constant_Select_Sum_Min_Key_Max_Avg(); + + AssertSql( + @"SELECT SUM([t].[OrderID]) AS [Sum], MIN([t].[OrderID]) AS [Min], [t].[Key], MAX([t].[OrderID]) AS [Max], AVG(CAST([t].[OrderID] AS float)) AS [Avg] +FROM ( + SELECT [o].*, 2 AS [Key] + FROM [Orders] AS [o] +) AS [t] +GROUP BY [t].[Key]"); + } + + public override void GroupBy_after_predicate_Constant_Select_Sum_Min_Key_Max_Avg() + { + base.GroupBy_after_predicate_Constant_Select_Sum_Min_Key_Max_Avg(); + + AssertSql( + @"SELECT SUM([t].[OrderID]) AS [Sum], MIN([t].[OrderID]) AS [Min], [t].[Key] AS [Random], MAX([t].[OrderID]) AS [Max], AVG(CAST([t].[OrderID] AS float)) AS [Avg] +FROM ( + SELECT [o].*, 2 AS [Key] + FROM [Orders] AS [o] + WHERE [o].[OrderID] > 10500 +) AS [t] +GROUP BY [t].[Key]"); + } + + public override void GroupBy_Constant_with_element_selector_Select_Sum_Min_Key_Max_Avg() + { + base.GroupBy_Constant_with_element_selector_Select_Sum_Min_Key_Max_Avg(); + + AssertSql( + @"SELECT SUM([t].[OrderID]) AS [Sum], [t].[Key] +FROM ( + SELECT [o].[OrderID], 2 AS [Key] + FROM [Orders] AS [o] +) AS [t] +GROUP BY [t].[Key]"); + } + + public override void GroupBy_param_Select_Sum_Min_Key_Max_Avg() + { + base.GroupBy_param_Select_Sum_Min_Key_Max_Avg(); + + AssertSql( + @"@__a_0='2' + +SELECT SUM([t].[OrderID]) AS [Sum], MIN([t].[OrderID]) AS [Min], [t].[Key], MAX([t].[OrderID]) AS [Max], AVG(CAST([t].[OrderID] AS float)) AS [Avg] +FROM ( + SELECT [o].*, @__a_0 AS [Key] + FROM [Orders] AS [o] +) AS [t] +GROUP BY [t].[Key]"); + } + + public override void GroupBy_param_with_element_selector_Select_Sum_Min_Key_Max_Avg() + { + base.GroupBy_param_with_element_selector_Select_Sum_Min_Key_Max_Avg(); + + AssertSql( + @"@__a_0='2' + +SELECT SUM([t].[OrderID]) AS [Sum], [t].[Key] +FROM ( + SELECT [o].[OrderID], @__a_0 AS [Key] + FROM [Orders] AS [o] +) AS [t] +GROUP BY [t].[Key]"); + } + public override void GroupBy_Property_scalar_element_selector_Average() { base.GroupBy_Property_scalar_element_selector_Average(); @@ -471,7 +584,7 @@ public override void GroupBy_Property_scalar_element_selector_Sum_Min_Max_Avg() base.GroupBy_Property_scalar_element_selector_Sum_Min_Max_Avg(); AssertSql( - @"SELECT SUM([o].[OrderID]), MIN([o].[OrderID]), MAX([o].[OrderID]), AVG(CAST([o].[OrderID] AS float)) + @"SELECT SUM([o].[OrderID]) AS [Sum], MIN([o].[OrderID]) AS [Min], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg] FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -541,17 +654,7 @@ public override void GroupBy_Property_anonymous_element_selector_Sum_Min_Max_Avg base.GroupBy_Property_anonymous_element_selector_Sum_Min_Max_Avg(); AssertSql( - @"SELECT SUM([o].[OrderID]), MIN([o].[EmployeeID]), MAX([o].[EmployeeID]), AVG(CAST([o].[OrderID] AS float)) -FROM [Orders] AS [o] -GROUP BY [o].[CustomerID]"); - } - - public override void GroupBy_with_result_selector() - { - base.GroupBy_with_result_selector(); - - AssertSql( - @"SELECT SUM([o].[OrderID]), MIN([o].[OrderID]), MAX([o].[OrderID]), AVG(CAST([o].[OrderID] AS float)) + @"SELECT SUM([o].[OrderID]) AS [Sum], MIN([o].[EmployeeID]) AS [Min], MAX([o].[EmployeeID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg] FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -575,7 +678,7 @@ public override void OrderBy_Skip_GroupBy_Aggregate() SELECT AVG(CAST([t].[OrderID] AS float)) FROM ( - SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] + SELECT [o].* FROM [Orders] AS [o] ORDER BY [o].[OrderID] OFFSET @__p_0 ROWS @@ -592,7 +695,7 @@ public override void OrderBy_Take_GroupBy_Aggregate() SELECT MIN([t].[OrderID]) FROM ( - SELECT TOP(@__p_0) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] + SELECT TOP(@__p_0) [o].* FROM [Orders] AS [o] ORDER BY [o].[OrderID] ) AS [t] @@ -609,7 +712,7 @@ public override void OrderBy_Skip_Take_GroupBy_Aggregate() SELECT MAX([t].[OrderID]) FROM ( - SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] + SELECT [o].* FROM [Orders] AS [o] ORDER BY [o].[OrderID] OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY @@ -622,9 +725,9 @@ public override void Distinct_GroupBy_Aggregate() base.Distinct_GroupBy_Aggregate(); AssertSql( - @"SELECT [t].[CustomerID], COUNT(*) + @"SELECT [t].[CustomerID] AS [Key], COUNT(*) AS [c] FROM ( - SELECT DISTINCT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] + SELECT DISTINCT [o].* FROM [Orders] AS [o] ) AS [t] GROUP BY [t].[CustomerID]"); @@ -635,7 +738,7 @@ public override void Anonymous_projection_Distinct_GroupBy_Aggregate() base.Anonymous_projection_Distinct_GroupBy_Aggregate(); AssertSql( - @"SELECT [t].[EmployeeID], COUNT(*) + @"SELECT [t].[EmployeeID] AS [Key], COUNT(*) AS [c] FROM ( SELECT DISTINCT [o].[OrderID], [o].[EmployeeID] FROM [Orders] AS [o] @@ -648,7 +751,7 @@ public override void SelectMany_GroupBy_Aggregate() base.SelectMany_GroupBy_Aggregate(); AssertSql( - @"SELECT [c.Orders].[EmployeeID], COUNT(*) + @"SELECT [c.Orders].[EmployeeID] AS [Key], COUNT(*) AS [c] FROM [Customers] AS [c] INNER JOIN [Orders] AS [c.Orders] ON [c].[CustomerID] = [c.Orders].[CustomerID] GROUP BY [c.Orders].[EmployeeID]"); @@ -659,12 +762,23 @@ public override void Join_GroupBy_Aggregate() base.Join_GroupBy_Aggregate(); AssertSql( - @"SELECT [c].[CustomerID], AVG(CAST([o].[OrderID] AS float)) + @"SELECT [c].[CustomerID] AS [Key], AVG(CAST([o].[OrderID] AS float)) AS [Count] FROM [Orders] AS [o] INNER JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] GROUP BY [c].[CustomerID]"); } + public override void GroupBy_required_navigation_member_Aggregate() + { + base.GroupBy_required_navigation_member_Aggregate(); + + AssertSql( + @"SELECT [od.Order].[CustomerID] AS [CustomerId], COUNT(*) AS [Count] +FROM [Order Details] AS [od] +INNER JOIN [Orders] AS [od.Order] ON [od].[OrderID] = [od.Order].[OrderID] +GROUP BY [od.Order].[CustomerID]"); + } + public override void Join_complex_GroupBy_Aggregate() { base.Join_complex_GroupBy_Aggregate(); @@ -674,7 +788,7 @@ public override void Join_complex_GroupBy_Aggregate() @__p_1='10' @__p_2='50' -SELECT [t0].[CustomerID], AVG(CAST([t].[OrderID] AS float)) +SELECT [t0].[CustomerID] AS [Key], AVG(CAST([t].[OrderID] AS float)) AS [Count] FROM ( SELECT TOP(@__p_0) [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] FROM [Orders] AS [o] @@ -696,7 +810,7 @@ public override void GroupJoin_GroupBy_Aggregate() base.GroupJoin_GroupBy_Aggregate(); AssertSql( - @"SELECT [o].[CustomerID], AVG(CAST([o].[OrderID] AS float)) + @"SELECT [o].[CustomerID] AS [Key], AVG(CAST([o].[OrderID] AS float)) AS [Count] FROM [Customers] AS [c] INNER JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] GROUP BY [o].[CustomerID]"); @@ -707,7 +821,7 @@ public override void GroupJoin_GroupBy_Aggregate_2() base.GroupJoin_GroupBy_Aggregate_2(); AssertSql( - @"SELECT [c].[CustomerID], MAX([c].[City]) + @"SELECT [c].[CustomerID] AS [Key], MAX([c].[City]) AS [Count] FROM [Customers] AS [c] INNER JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] GROUP BY [c].[CustomerID]"); @@ -718,12 +832,23 @@ public override void GroupJoin_GroupBy_Aggregate_3() base.GroupJoin_GroupBy_Aggregate_3(); AssertSql( - @"SELECT [o].[CustomerID], AVG(CAST([o].[OrderID] AS float)) + @"SELECT [o].[CustomerID] AS [Key], AVG(CAST([o].[OrderID] AS float)) AS [Count] FROM [Orders] AS [o] INNER JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] GROUP BY [o].[CustomerID]"); } + public override void GroupBy_optional_navigation_member_Aggregate() + { + base.GroupBy_optional_navigation_member_Aggregate(); + + AssertSql( + @"SELECT [o.Customer].[Country] +FROM [Orders] AS [o] +LEFT JOIN [Customers] AS [o.Customer] ON [o].[CustomerID] = [o.Customer].[CustomerID] +ORDER BY [o.Customer].[Country]"); + } + public override void GroupJoin_complex_GroupBy_Aggregate() { base.GroupJoin_complex_GroupBy_Aggregate(); @@ -733,7 +858,7 @@ public override void GroupJoin_complex_GroupBy_Aggregate() @__p_1='50' @__p_2='100' -SELECT [t0].[CustomerID], AVG(CAST([t0].[OrderID] AS float)) +SELECT [t0].[CustomerID] AS [Key], AVG(CAST([t0].[OrderID] AS float)) AS [Count] FROM ( SELECT [c].* FROM [Customers] AS [c] @@ -756,13 +881,25 @@ public override void Self_join_GroupBy_Aggregate() base.Self_join_GroupBy_Aggregate(); AssertSql( - @"SELECT [o].[CustomerID], AVG(CAST([o2].[OrderID] AS float)) + @"SELECT [o].[CustomerID] AS [Key], AVG(CAST([o2].[OrderID] AS float)) AS [Count] FROM [Orders] AS [o] INNER JOIN [Orders] AS [o2] ON [o].[OrderID] = [o2].[OrderID] WHERE [o].[OrderID] < 10400 GROUP BY [o].[CustomerID]"); } + public override void GroupBy_multi_navigation_members_Aggregate() + { + base.GroupBy_multi_navigation_members_Aggregate(); + + AssertSql( + @"SELECT [od.Order].[CustomerID], [od.Product].[ProductName], COUNT(*) AS [Count] +FROM [Order Details] AS [od] +INNER JOIN [Products] AS [od.Product] ON [od].[ProductID] = [od.Product].[ProductID] +INNER JOIN [Orders] AS [od.Order] ON [od].[OrderID] = [od.Order].[OrderID] +GROUP BY [od.Order].[CustomerID], [od.Product].[ProductName]"); + } + public override void Union_simple_groupby() { base.Union_simple_groupby(); @@ -770,6 +907,97 @@ public override void Union_simple_groupby() AssertSql(" "); } + public override void GroupBy_OrderBy_key() + { + base.GroupBy_OrderBy_key(); + + AssertSql( + @"SELECT [o].[CustomerID] AS [Key], COUNT(*) AS [c] +FROM [Orders] AS [o] +GROUP BY [o].[CustomerID] +ORDER BY [Key]"); + } + + public override void GroupBy_OrderBy_count() + { + base.GroupBy_OrderBy_count(); + + AssertSql( + @"SELECT [o].[CustomerID] AS [Key], COUNT(*) AS [Count] +FROM [Orders] AS [o] +GROUP BY [o].[CustomerID] +ORDER BY [Count], [Key]"); + } + + public override void GroupBy_OrderBy_count_Select_sum() + { + base.GroupBy_OrderBy_count_Select_sum(); + + AssertSql( + @"SELECT [o].[CustomerID] AS [Key], SUM([o].[OrderID]) AS [Sum] +FROM [Orders] AS [o] +GROUP BY [o].[CustomerID] +ORDER BY COUNT(*), [Key]"); + } + + public override void GroupBy_OrderBy_count_Select_sum_over_unmapped_property() + { + base.GroupBy_OrderBy_count_Select_sum_over_unmapped_property(); + + AssertSql(" "); + } + + public override void GroupBy_filter_key() + { + base.GroupBy_filter_key(); + + AssertSql( + @"SELECT [o].[CustomerID] AS [Key], COUNT(*) AS [c] +FROM [Orders] AS [o] +GROUP BY [o].[CustomerID] +HAVING [o].[CustomerID] = N'ALFKI'"); + } + + public override void GroupBy_filter_count() + { + base.GroupBy_filter_count(); + + AssertSql( + @"SELECT [o].[CustomerID] AS [Key], COUNT(*) AS [Count] +FROM [Orders] AS [o] +GROUP BY [o].[CustomerID] +HAVING COUNT(*) > 4"); + } + + public override void GroupBy_filter_count_OrderBy_count_Select_sum() + { + base.GroupBy_filter_count_OrderBy_count_Select_sum(); + + AssertSql( + @"SELECT [o].[CustomerID] AS [Key], COUNT(*) AS [Count], SUM([o].[OrderID]) AS [Sum] +FROM [Orders] AS [o] +GROUP BY [o].[CustomerID] +HAVING COUNT(*) > 4 +ORDER BY [Count], [Key]"); + } + + public override void GroupBy_Aggregate_Join() + { + base.GroupBy_Aggregate_Join(); + + AssertSql(" "); + } + + public override void GroupBy_with_result_selector() + { + base.GroupBy_with_result_selector(); + + AssertSql( + @"SELECT SUM([o].[OrderID]) AS [Sum], MIN([o].[OrderID]) AS [Min], MAX([o].[OrderID]) AS [Max], AVG(CAST([o].[OrderID] AS float)) AS [Avg] +FROM [Orders] AS [o] +GROUP BY [o].[CustomerID]"); + } + public override void GroupBy_Sum_constant() { base.GroupBy_Sum_constant(); @@ -785,12 +1013,13 @@ public override void Distinct_GroupBy_OrderBy_key() base.Distinct_GroupBy_OrderBy_key(); AssertSql( - @"SELECT [t].[OrderID], [t].[CustomerID], [t].[EmployeeID], [t].[OrderDate] + @"SELECT [t].[CustomerID] AS [Key], COUNT(*) AS [c] FROM ( - SELECT DISTINCT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] + SELECT DISTINCT [o].* FROM [Orders] AS [o] ) AS [t] -ORDER BY [t].[CustomerID]"); +GROUP BY [t].[CustomerID] +ORDER BY [Key]"); } public override void Select_nested_collection_with_groupby() @@ -812,28 +1041,28 @@ FROM [Customers] AS [c] // @"@_outer_CustomerID='ALFKI' (Size = 5) -SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate] +SELECT [o1].[OrderID] FROM [Orders] AS [o1] WHERE @_outer_CustomerID = [o1].[CustomerID] ORDER BY [o1].[OrderID]", // @"@_outer_CustomerID='ANATR' (Size = 5) -SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate] +SELECT [o1].[OrderID] FROM [Orders] AS [o1] WHERE @_outer_CustomerID = [o1].[CustomerID] ORDER BY [o1].[OrderID]", // @"@_outer_CustomerID='ANTON' (Size = 5) -SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate] +SELECT [o1].[OrderID] FROM [Orders] AS [o1] WHERE @_outer_CustomerID = [o1].[CustomerID] ORDER BY [o1].[OrderID]", // @"@_outer_CustomerID='AROUT' (Size = 5) -SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate] +SELECT [o1].[OrderID] FROM [Orders] AS [o1] WHERE @_outer_CustomerID = [o1].[CustomerID] ORDER BY [o1].[OrderID]"); @@ -944,7 +1173,7 @@ public override void GroupBy_with_element_selector() base.GroupBy_with_element_selector(); AssertSql( - @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] + @"SELECT [o].[CustomerID], [o].[OrderID] FROM [Orders] AS [o] ORDER BY [o].[CustomerID]"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs index 82ec85a2dcb..2423dbfff91 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.ResultOperators.cs @@ -1037,5 +1037,14 @@ public override void Paging_operation_on_string_doesnt_issue_warning() CoreStrings.LogFirstWithoutOrderByAndFilter.GenerateMessage( @"(from char _1 in [c].CustomerID select [_1]).FirstOrDefault()"), Fixture.TestSqlLoggerFactory.Log); } + + public override void Project_constant_Sum() + { + base.Project_constant_Sum(); + + AssertSql( + @"SELECT 1 +FROM [Employees] AS [e]"); + } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs index 941a4821632..a6eaf274c79 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.Select.cs @@ -183,6 +183,15 @@ END AS [IsAvailable] FROM [Products] AS [p]"); } + public override void Select_constant_int() + { + base.Select_constant_int(); + + AssertSql( + @"SELECT 1 +FROM [Customers] AS [c]"); + } + public override void Select_constant_null_string() { base.Select_constant_null_string();