diff --git a/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs b/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs index c96c0f73d89..6d73fe6dc30 100644 --- a/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/EntityProjectionExpression.cs @@ -148,6 +148,31 @@ public virtual void AddNavigationBinding(INavigation navigation, EntityShaperExp : null; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual EntityProjectionExpression Clone() + { + var readExpressionMap = new Dictionary(); + foreach (var kvp in _readExpressionMap) + { + readExpressionMap.Add(kvp.Key, kvp.Value); + } + var entityProjectionExpression = new EntityProjectionExpression(EntityType, readExpressionMap); + foreach (var kvp in _navigationExpressionsCache) + { + entityProjectionExpression._navigationExpressionsCache[kvp.Key] = new EntityShaperExpression( + kvp.Value.EntityType, + ((EntityProjectionExpression)kvp.Value.ValueBufferExpression).Clone(), + kvp.Value.IsNullable); + } + + return entityProjectionExpression; + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs index 031d79195f8..6faa1a40e9c 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryExpressionTranslatingExpressionVisitor.cs @@ -30,7 +30,7 @@ namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal /// public class InMemoryExpressionTranslatingExpressionVisitor : ExpressionVisitor { - private const string _runtimeParameterPrefix = QueryCompilationContext.QueryParameterPrefix + "entity_equality_"; + private const string RuntimeParameterPrefix = QueryCompilationContext.QueryParameterPrefix + "entity_equality_"; private static readonly MemberInfo _valueBufferIsEmpty = typeof(ValueBuffer).GetMember(nameof(ValueBuffer.IsEmpty))[0]; @@ -280,12 +280,6 @@ protected override Expression VisitExtension(Expression extensionExpression) when projectionBindingExpression.ProjectionMember != null: return ((InMemoryQueryExpression)projectionBindingExpression.QueryExpression).GetProjection(projectionBindingExpression); - case InMemoryGroupByShaperExpression inMemoryGroupByShaperExpression: - return new GroupingElementExpression( - inMemoryGroupByShaperExpression.GroupingParameter, - inMemoryGroupByShaperExpression.ElementSelector, - inMemoryGroupByShaperExpression.ValueBufferParameter); - default: return QueryCompilationContext.NotTranslatedExpression; } @@ -461,246 +455,6 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp ?? QueryCompilationContext.NotTranslatedExpression; } - // GroupBy Aggregate case - if (methodCallExpression.Object == null - && methodCallExpression.Method.DeclaringType == typeof(Enumerable) - && methodCallExpression.Arguments.Count > 0) - { - if (methodCallExpression.Arguments[0].Type.TryGetElementType(typeof(IQueryable<>)) == null - && Visit(methodCallExpression.Arguments[0]) is GroupingElementExpression groupingElementExpression) - { - Expression? result = null; - switch (methodCallExpression.Method.Name) - { - case nameof(Enumerable.Average): - { - if (methodCallExpression.Arguments.Count == 2) - { - groupingElementExpression = ApplySelector( - groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - } - - var expression = ApplySelect(groupingElementExpression); - - result = expression == null - ? null - : Expression.Call( - EnumerableMethods.GetAverageWithoutSelector(expression.Type.GetSequenceType()), expression); - break; - } - - case nameof(Enumerable.Count): - { - if (methodCallExpression.Arguments.Count == 2) - { - var temporaryGroupingElementExpression = ApplyPredicate( - groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - - if (temporaryGroupingElementExpression == null) - { - result = null; - break; - } - - groupingElementExpression = temporaryGroupingElementExpression; - } - - var expression = ApplySelect(groupingElementExpression); - - result = expression == null - ? null - : Expression.Call( - EnumerableMethods.CountWithoutPredicate.MakeGenericMethod(expression.Type.GetSequenceType()), - expression); - break; - } - - case nameof(Enumerable.Distinct): - result = groupingElementExpression.Selector is EntityShaperExpression - ? groupingElementExpression - : groupingElementExpression.IsDistinct - ? null - : groupingElementExpression.ApplyDistinct(); - break; - - case nameof(Enumerable.LongCount): - { - if (methodCallExpression.Arguments.Count == 2) - { - var temporaryGroupingElementExpression = ApplyPredicate( - groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - - if (temporaryGroupingElementExpression == null) - { - result = null; - break; - } - - groupingElementExpression = temporaryGroupingElementExpression; - } - - var expression = ApplySelect(groupingElementExpression); - - result = expression == null - ? null - : Expression.Call( - EnumerableMethods.LongCountWithoutPredicate.MakeGenericMethod(expression.Type.GetSequenceType()), - expression); - break; - } - - case nameof(Enumerable.Max): - { - if (methodCallExpression.Arguments.Count == 2) - { - groupingElementExpression = ApplySelector( - groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - } - - var expression = ApplySelect(groupingElementExpression); - if (expression == null - || expression is ParameterExpression) - { - result = null; - } - else - { - var type = expression.Type.GetSequenceType(); - var aggregateMethod = EnumerableMethods.GetMaxWithoutSelector(type); - if (aggregateMethod.IsGenericMethod) - { - aggregateMethod = aggregateMethod.MakeGenericMethod(type); - } - - result = Expression.Call(aggregateMethod, expression); - } - - break; - } - - case nameof(Enumerable.Min): - { - if (methodCallExpression.Arguments.Count == 2) - { - groupingElementExpression = ApplySelector( - groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - } - - var expression = ApplySelect(groupingElementExpression); - if (expression == null - || expression is ParameterExpression) - { - result = null; - } - else - { - var type = expression.Type.GetSequenceType(); - var aggregateMethod = EnumerableMethods.GetMinWithoutSelector(type); - if (aggregateMethod.IsGenericMethod) - { - aggregateMethod = aggregateMethod.MakeGenericMethod(type); - } - - result = Expression.Call(aggregateMethod, expression); - } - - break; - } - - case nameof(Enumerable.Select): - result = ApplySelector(groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - break; - - case nameof(Enumerable.Sum): - { - if (methodCallExpression.Arguments.Count == 2) - { - groupingElementExpression = ApplySelector( - groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - } - - var expression = ApplySelect(groupingElementExpression); - - result = expression == null - ? null - : Expression.Call( - EnumerableMethods.GetSumWithoutSelector(expression.Type.GetSequenceType()), expression); - break; - } - - case nameof(Enumerable.Where): - result = ApplyPredicate(groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - break; - - default: - result = null; - break; - } - - return result ?? throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print())); - - GroupingElementExpression? ApplyPredicate(GroupingElementExpression groupingElement, LambdaExpression lambdaExpression) - { - var predicate = TranslateInternal(RemapLambda(groupingElement, lambdaExpression)); - - if (predicate == null) - { - return null; - } - - if (predicate.Type != typeof(bool)) - { - predicate = Expression.Equal(predicate, Expression.Constant(true, typeof(bool?))); - } - - return groupingElement.UpdateSource( - Expression.Call( - EnumerableMethods.Where.MakeGenericMethod(typeof(ValueBuffer)), - groupingElement.Source, - Expression.Lambda(predicate, groupingElement.ValueBufferParameter))); - } - - Expression? ApplySelect(GroupingElementExpression groupingElement) - { - var selector = TranslateInternal(groupingElement.Selector); - - if (selector == null) - { - return groupingElement.Selector is EntityShaperExpression - ? groupingElement.Source - : null; - } - - var result = Expression.Call( - EnumerableMethods.Select.MakeGenericMethod(typeof(ValueBuffer), selector.Type), - groupingElement.Source, - Expression.Lambda(selector, groupingElement.ValueBufferParameter)); - - if (groupingElement.IsDistinct) - { - result = Expression.Call( - EnumerableMethods.Distinct.MakeGenericMethod(selector.Type), - result); - } - - return result; - } - - static GroupingElementExpression ApplySelector( - GroupingElementExpression groupingElement, - LambdaExpression lambdaExpression) - { - var selector = RemapLambda(groupingElement, lambdaExpression); - - return groupingElement.ApplySelector(selector); - } - - static Expression RemapLambda(GroupingElementExpression groupingElement, LambdaExpression lambdaExpression) - => ReplacingExpressionVisitor.Replace( - lambdaExpression.Parameters[0], groupingElement.Selector, lambdaExpression.Body); - } - } - // Subquery case var subqueryTranslation = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression); if (subqueryTranslation != null) @@ -1346,7 +1100,7 @@ when methodCallExpression.Method.IsGenericMethod ); var newParameterName = - $"{_runtimeParameterPrefix}" + $"{RuntimeParameterPrefix}" + $"{parameterName[QueryCompilationContext.QueryParameterPrefix.Length..]}_{property.Name}"; rewrittenSource = _queryCompilationContext.RegisterRuntimeParameter(newParameterName, lambda); @@ -1477,7 +1231,7 @@ when methodCallExpression.Method.IsGenericMethod QueryCompilationContext.QueryContextParameter); var newParameterName = - $"{_runtimeParameterPrefix}" + $"{RuntimeParameterPrefix}" + $"{parameterName[QueryCompilationContext.QueryParameterPrefix.Length..]}_{property.Name}"; return _queryCompilationContext.RegisterRuntimeParameter(newParameterName, lambda); @@ -1747,47 +1501,5 @@ public Expression Convert(Type type) : new EntityReferenceExpression(this, derivedEntityType); } } - - private sealed class GroupingElementExpression : Expression - { - public GroupingElementExpression(Expression source, Expression selector, ParameterExpression valueBufferParameter) - { - Source = source; - ValueBufferParameter = valueBufferParameter; - Selector = selector; - } - - public Expression Source { get; private set; } - public bool IsDistinct { get; private set; } - public Expression Selector { get; private set; } - public ParameterExpression ValueBufferParameter { get; } - - public GroupingElementExpression ApplyDistinct() - { - IsDistinct = true; - - return this; - } - - public GroupingElementExpression ApplySelector(Expression expression) - { - Selector = expression; - - return this; - } - - public GroupingElementExpression UpdateSource(Expression source) - { - Source = source; - - return this; - } - - public override Type Type - => typeof(IEnumerable<>).MakeGenericType(Selector.Type); - - public override ExpressionType NodeType - => ExpressionType.Extension; - } } } diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryGroupByShaperExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryGroupByShaperExpression.cs deleted file mode 100644 index 0e7c7697ebc..00000000000 --- a/src/EFCore.InMemory/Query/Internal/InMemoryGroupByShaperExpression.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Linq.Expressions; -using Microsoft.EntityFrameworkCore.Query; - -namespace Microsoft.EntityFrameworkCore.InMemory.Query.Internal -{ - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public class InMemoryGroupByShaperExpression : GroupByShaperExpression - { - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public InMemoryGroupByShaperExpression( - Expression keySelector, - Expression elementSelector, - ParameterExpression groupingParameter, - ParameterExpression valueBufferParameter) - : base(keySelector, elementSelector) - { - GroupingParameter = groupingParameter; - ValueBufferParameter = valueBufferParameter; - } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ParameterExpression GroupingParameter { get; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ParameterExpression ValueBufferParameter { get; } - } -} diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.Helper.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.Helper.cs index 6e152c9060b..cd59c853a86 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.Helper.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.Helper.cs @@ -173,5 +173,64 @@ protected override Expression VisitExtension(Expression extensionExpression) : base.VisitExtension(extensionExpression); } } + + private sealed class QueryExpressionReplacingExpressionVisitor : ExpressionVisitor + { + private readonly Expression _oldQuery; + private readonly Expression _newQuery; + + public QueryExpressionReplacingExpressionVisitor(Expression oldQuery, Expression newQuery) + { + _oldQuery = oldQuery; + _newQuery = newQuery; + } + + [return: NotNullIfNotNull("expression")] + public override Expression? Visit(Expression? expression) + { + return expression is ProjectionBindingExpression projectionBindingExpression + && ReferenceEquals(projectionBindingExpression.QueryExpression, _oldQuery) + ? projectionBindingExpression.ProjectionMember != null + ? new ProjectionBindingExpression( + _newQuery, projectionBindingExpression.ProjectionMember!, projectionBindingExpression.Type) + : new ProjectionBindingExpression( + _newQuery, projectionBindingExpression.Index!.Value, projectionBindingExpression.Type) + : base.Visit(expression); + } + } + + private sealed class CloningExpressionVisitor : ExpressionVisitor + { + [return: NotNullIfNotNull("expression")] + public override Expression? Visit(Expression? expression) + { + if (expression is InMemoryQueryExpression inMemoryQueryExpression) + { + var clonedInMemoryQueryExpression = new InMemoryQueryExpression( + inMemoryQueryExpression.ServerQueryExpression, inMemoryQueryExpression._valueBufferParameter) + { + _groupingParameter = inMemoryQueryExpression._groupingParameter, + _singleResultMethodInfo = inMemoryQueryExpression._singleResultMethodInfo, + _scalarServerQuery = inMemoryQueryExpression._scalarServerQuery + }; + + clonedInMemoryQueryExpression._clientProjections.AddRange(inMemoryQueryExpression._clientProjections.Select(e => Visit(e))); + clonedInMemoryQueryExpression._projectionMappingExpressions.AddRange(inMemoryQueryExpression._projectionMappingExpressions); + foreach (var item in inMemoryQueryExpression._projectionMapping) + { + clonedInMemoryQueryExpression._projectionMapping[item.Key] = Visit(item.Value); + } + + return clonedInMemoryQueryExpression; + } + + if (expression is EntityProjectionExpression entityProjectionExpression) + { + return entityProjectionExpression.Clone(); + } + + return base.Visit(expression); + } + } } } diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs index 64ed98a783a..b693d555579 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs @@ -41,10 +41,20 @@ private static readonly ConstructorInfo _resultEnumerableConstructor private MethodInfo? _singleResultMethodInfo; private bool _scalarServerQuery; + private CloningExpressionVisitor? _cloningExpressionVisitor; + private Dictionary _projectionMapping = new(); private readonly List _clientProjections = new(); private readonly List _projectionMappingExpressions = new(); + private InMemoryQueryExpression( + Expression serverQueryExpression, + ParameterExpression valueBufferParameter) + { + ServerQueryExpression = serverQueryExpression; + _valueBufferParameter = valueBufferParameter; + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -333,15 +343,17 @@ public virtual void ApplyProjection() ServerQueryExpression, selectorLambda); + _groupingParameter = null; + if (_singleResultMethodInfo != null) { ServerQueryExpression = Call( _singleResultMethodInfo.MakeGenericMethod(CurrentParameter.Type), ServerQueryExpression); - _singleResultMethodInfo = null; - ConvertToEnumerable(); + + _singleResultMethodInfo = null; } } @@ -540,7 +552,7 @@ public virtual void ApplyDistinct() /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual InMemoryGroupByShaperExpression ApplyGrouping( + public virtual GroupByShaperExpression ApplyGrouping( Expression groupingKey, Expression shaperExpression, bool defaultElementSelector) @@ -583,11 +595,15 @@ public virtual InMemoryGroupByShaperExpression ApplyGrouping( keySelector, selector); - return new InMemoryGroupByShaperExpression( + var clonedInMemoryQueryExpression = Clone(); + clonedInMemoryQueryExpression.UpdateServerQueryExpression(_groupingParameter); + clonedInMemoryQueryExpression._groupingParameter = null; + + return new GroupByShaperExpression( groupingKey, - shaperExpression, - _groupingParameter, - _valueBufferParameter); + new ShapedQueryExpression( + clonedInMemoryQueryExpression, + new QueryExpressionReplacingExpressionVisitor(this, clonedInMemoryQueryExpression).Visit(shaperExpression))); } /// @@ -711,6 +727,22 @@ public virtual EntityShaperExpression AddNavigationToWeakEntityType( return entityShaper; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ShapedQueryExpression Clone(Expression shaperExpression) + { + var clonedInMemoryQueryExpression = Clone(); + + return new ShapedQueryExpression( + clonedInMemoryQueryExpression, + new QueryExpressionReplacingExpressionVisitor(this, clonedInMemoryQueryExpression).Visit(shaperExpression)); + + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -811,6 +843,16 @@ void IPrintableExpression.Print(ExpressionPrinter expressionPrinter) } } + private InMemoryQueryExpression Clone() + { + if (_cloningExpressionVisitor == null) + { + _cloningExpressionVisitor = new(); + } + + return (InMemoryQueryExpression)_cloningExpressionVisitor.Visit(this); + } + private Expression GetGroupingKey(Expression key, List groupingExpressions, Expression groupingKeyAccessExpression) { switch (key) @@ -1061,7 +1103,7 @@ static Expression MakeNullable(Expression expression, bool nullable) private void ConvertToEnumerable() { - if (ServerQueryExpression.Type.TryGetSequenceType() == null) + if (_scalarServerQuery || _singleResultMethodInfo != null) { if (ServerQueryExpression.Type != typeof(ValueBuffer)) { diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs index c3de097e64b..0e9bcfeef79 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryQueryableMethodTranslatingExpressionVisitor.cs @@ -70,6 +70,24 @@ protected InMemoryQueryableMethodTranslatingExpressionVisitor( protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVisitor() => new InMemoryQueryableMethodTranslatingExpressionVisitor(this); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override Expression VisitExtension(Expression extensionExpression) + { + if (extensionExpression is GroupByShaperExpression groupByShaperExpression) + { + var shapedQueryExpression = groupByShaperExpression.GroupingEnumerable; + return ((InMemoryQueryExpression)shapedQueryExpression.QueryExpression) + .Clone(shapedQueryExpression.ShaperExpression); + } + + return base.VisitExtension(extensionExpression); + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -1513,7 +1531,8 @@ private static Expression AccessField( inMemoryQueryExpression.CurrentParameter) : TranslateLambdaExpression(source, selector, preserveType: true); - if (selector == null) + if (selector == null + || selector.Body is EntityProjectionExpression) { return null; } diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 5a2b35ae572..3bdaec70a74 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -26,6 +27,7 @@ public class RelationalQueryableMethodTranslatingExpressionVisitor : QueryableMe private readonly QueryCompilationContext _queryCompilationContext; private readonly ISqlExpressionFactory _sqlExpressionFactory; private readonly bool _subquery; + private SqlExpression? _groupingElementCorrelationalPredicate; /// /// Creates a new instance of the class. @@ -142,11 +144,35 @@ when queryRootExpression.EntityType.GetSqlQueryMappings().FirstOrDefault(m => m. new FromSqlQueryRootExpression( queryRootExpression.EntityType, sqlQuery.Sql, Expression.Constant(Array.Empty(), typeof(object[])))); + case GroupByShaperExpression groupByShaperExpression: + var shapedQueryExpression = groupByShaperExpression.GroupingEnumerable; + var clonedSelectExpression = ((SelectExpression)shapedQueryExpression.QueryExpression).Clone(); + _groupingElementCorrelationalPredicate = clonedSelectExpression.Predicate; + return new ShapedQueryExpression( + clonedSelectExpression, + new QueryExpressionReplacingExpressionVisitor( + shapedQueryExpression.QueryExpression, clonedSelectExpression).Visit(shapedQueryExpression.ShaperExpression)); + default: return base.VisitExtension(extensionExpression); } } + /// + public override ShapedQueryExpression? TranslateSubquery(Expression expression) + { + Check.NotNull(expression, nameof(expression)); + + var subqueryVisitor = (RelationalQueryableMethodTranslatingExpressionVisitor)CreateSubqueryVisitor(); + var translation = subqueryVisitor.Visit(expression) as ShapedQueryExpression; + if (translation == null && subqueryVisitor.TranslationErrorDetails != null) + { + AddTranslationErrorDetails(subqueryVisitor.TranslationErrorDetails); + } + + return translation; + } + /// protected override QueryableMethodTranslatingExpressionVisitor CreateSubqueryVisitor() => new RelationalQueryableMethodTranslatingExpressionVisitor(this); @@ -320,36 +346,7 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent { Check.NotNull(source, nameof(source)); - var selectExpression = (SelectExpression)source.QueryExpression; - selectExpression.PrepareForAggregate(); - - if (predicate != null) - { - var translatedSource = TranslateWhere(source, predicate); - if (translatedSource == null) - { - return null; - } - source = translatedSource; - } - - HandleGroupByForAggregate(selectExpression, eraseProjection: true); - - var translation = _sqlTranslator.TranslateCount(_sqlExpressionFactory.Fragment("*")); - if (translation == null) - { - return null; - } - - var projectionMapping = new Dictionary { { new ProjectionMember(), translation } }; - - selectExpression.ClearOrdering(); - selectExpression.ReplaceProjection(projectionMapping); - - return source.UpdateShaperExpression( - Expression.Convert( - new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(int?)), - typeof(int))); + return TranslateAggregateWithPredicate(source, predicate, e => _sqlTranslator.TranslateCount(e), typeof(int)); } /// @@ -464,7 +461,6 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent } var remappedKeySelector = RemapLambdaBody(source, keySelector); - var translatedKey = TranslateGroupingKey(remappedKeySelector); if (translatedKey != null) { @@ -473,8 +469,28 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent source = TranslateSelect(source, elementSelector); } - translatedKey = selectExpression.ApplyGrouping(translatedKey); - var groupByShaper = new GroupByShaperExpression(translatedKey, source.ShaperExpression); + if (translatedKey is NewExpression newExpression + && newExpression.Arguments.Count == 0) + { + selectExpression.ApplyGrouping(_sqlExpressionFactory.ApplyDefaultTypeMapping(_sqlExpressionFactory.Constant(1))); + } + else + { + translatedKey = selectExpression.ApplyGrouping(translatedKey); + } + var clonedSelectExpression = selectExpression.Clone(); + // If the grouping key is empty then there may not be any group by terms. + var correlationPredicate = selectExpression.GroupBy.Zip(clonedSelectExpression.GroupBy) + .Select(e => _sqlExpressionFactory.Equal(e.First, e.Second)) + .Aggregate((l, r) => _sqlExpressionFactory.AndAlso(l, r)); + clonedSelectExpression.ClearGroupBy(); + clonedSelectExpression.ApplyPredicate(correlationPredicate); + + var groupByShaper = new GroupByShaperExpression( + translatedKey, + new ShapedQueryExpression( + clonedSelectExpression, + new QueryExpressionReplacingExpressionVisitor(selectExpression, clonedSelectExpression).Visit(source.ShaperExpression))); if (resultSelector == null) { @@ -720,36 +736,7 @@ private SqlExpression CreateJoinPredicate(Expression outerKey, Expression innerK { Check.NotNull(source, nameof(source)); - var selectExpression = (SelectExpression)source.QueryExpression; - selectExpression.PrepareForAggregate(); - - if (predicate != null) - { - var translatedSource = TranslateWhere(source, predicate); - if (translatedSource == null) - { - return null; - } - source = translatedSource; - } - - HandleGroupByForAggregate(selectExpression, eraseProjection: true); - - var translation = _sqlTranslator.TranslateLongCount(_sqlExpressionFactory.Fragment("*")); - if (translation == null) - { - return null; - } - - var projectionMapping = new Dictionary { { new ProjectionMember(), translation } }; - - selectExpression.ClearOrdering(); - selectExpression.ReplaceProjection(projectionMapping); - - return source.UpdateShaperExpression( - Expression.Convert( - new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(long?)), - typeof(long))); + return TranslateAggregateWithPredicate(source, predicate, e => _sqlTranslator.TranslateLongCount(e), typeof(long)); } /// @@ -1241,7 +1228,7 @@ protected override Expression VisitExtension(Expression extensionExpression) private Expression? TryExpand(Expression? source, MemberIdentity member) { source = source.UnwrapTypeConversion(out var convertedType); - if (!(source is EntityShaperExpression entityShaperExpression)) + if (source is not EntityShaperExpression entityShaperExpression) { return null; } @@ -1495,6 +1482,75 @@ private Expression MatchShaperNullabilityForSetOperation(Expression shaper1, Exp } } + private ShapedQueryExpression? TranslateAggregateWithPredicate( + ShapedQueryExpression source, + LambdaExpression? predicate, + Func aggregateTranslator, + Type resultType) + { + var selectExpression = (SelectExpression)source.QueryExpression; + if (_groupingElementCorrelationalPredicate == null) + { + selectExpression.PrepareForAggregate(); + } + + if (predicate != null) + { + var translatedSource = TranslateWhere(source, predicate); + if (translatedSource == null) + { + return null; + } + source = translatedSource; + } + + SqlExpression sqlExpression = _sqlExpressionFactory.Fragment("*"); + + if (_groupingElementCorrelationalPredicate != null) + { + if (selectExpression.IsDistinct) + { + var shaperExpression = source.ShaperExpression; + if (shaperExpression is UnaryExpression unaryExpression + && unaryExpression.NodeType == ExpressionType.Convert) + { + shaperExpression = unaryExpression.Operand; + } + + if (shaperExpression is ProjectionBindingExpression projectionBindingExpression) + { + sqlExpression = (SqlExpression)selectExpression.GetProjection(projectionBindingExpression); + } + else + { + return null; + } + } + + sqlExpression = CombineGroupByAggregateTerms(selectExpression, sqlExpression); + } + else + { + HandleGroupByForAggregate(selectExpression, eraseProjection: true); + } + + var translation = aggregateTranslator(sqlExpression); + if (translation == null) + { + return null; + } + + var projectionMapping = new Dictionary { { new ProjectionMember(), translation } }; + + selectExpression.ClearOrdering(); + selectExpression.ReplaceProjection(projectionMapping); + + return source.UpdateShaperExpression( + Expression.Convert( + new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), resultType.MakeNullable()), + resultType)); + } + private ShapedQueryExpression? TranslateAggregateWithSelector( ShapedQueryExpression source, LambdaExpression? selector, @@ -1503,8 +1559,11 @@ private Expression MatchShaperNullabilityForSetOperation(Expression shaper1, Exp Type resultType) { var selectExpression = (SelectExpression)source.QueryExpression; - selectExpression.PrepareForAggregate(); - HandleGroupByForAggregate(selectExpression); + if (_groupingElementCorrelationalPredicate == null) + { + selectExpression.PrepareForAggregate(); + HandleGroupByForAggregate(selectExpression); + } SqlExpression translatedSelector; if (selector == null @@ -1539,6 +1598,11 @@ private Expression MatchShaperNullabilityForSetOperation(Expression shaper1, Exp } } + if (_groupingElementCorrelationalPredicate != null) + { + translatedSelector = CombineGroupByAggregateTerms(selectExpression, translatedSelector); + } + var projection = aggregateTranslator(translatedSelector); if (projection == null) { @@ -1595,5 +1659,80 @@ private Expression MatchShaperNullabilityForSetOperation(Expression shaper1, Exp return source.UpdateShaperExpression(shaper); } + + private SqlExpression CombineGroupByAggregateTerms(SelectExpression selectExpression, SqlExpression selector) + { + if (selectExpression.Predicate != null + && !selectExpression.Predicate.Equals(_groupingElementCorrelationalPredicate)) + { + if (selector is SqlFragmentExpression sqlFragmentExpression + && sqlFragmentExpression.Sql == "*") + { + selector = _sqlExpressionFactory.Constant(1); + } + + var correlationTerms = new List(); + var predicateTerms = new List(); + PopulatePredicateTerms(_groupingElementCorrelationalPredicate!, correlationTerms); + PopulatePredicateTerms(selectExpression.Predicate, predicateTerms); + var predicate = predicateTerms.Skip(correlationTerms.Count) + .Aggregate((l, r) => _sqlExpressionFactory.AndAlso(l, r)); + selector = _sqlExpressionFactory.Case( + new List { new CaseWhenClause(predicate, selector) }, + elseResult: null); + } + + if (selectExpression.IsDistinct) + { + if (selector is SqlFragmentExpression sqlFragmentExpression + && sqlFragmentExpression.Sql == "*") + { + selector = _sqlExpressionFactory.Constant(1); + } + + selector = new DistinctExpression(selector); + } + + return selector; + + static void PopulatePredicateTerms(SqlExpression predicate, List terms) + { + if (predicate is SqlBinaryExpression sqlBinaryExpression + && sqlBinaryExpression.OperatorType == ExpressionType.AndAlso) + { + PopulatePredicateTerms(sqlBinaryExpression.Left, terms); + PopulatePredicateTerms(sqlBinaryExpression.Right, terms); + } + else + { + terms.Add(predicate); + } + } + } + + private sealed class QueryExpressionReplacingExpressionVisitor : ExpressionVisitor + { + private readonly Expression _oldQuery; + private readonly Expression _newQuery; + + public QueryExpressionReplacingExpressionVisitor(Expression oldQuery, Expression newQuery) + { + _oldQuery = oldQuery; + _newQuery = newQuery; + } + + [return: NotNullIfNotNull("expression")] + public override Expression? Visit(Expression? expression) + { + return expression is ProjectionBindingExpression projectionBindingExpression + && ReferenceEquals(projectionBindingExpression.QueryExpression, _oldQuery) + ? projectionBindingExpression.ProjectionMember != null + ? new ProjectionBindingExpression( + _newQuery, projectionBindingExpression.ProjectionMember!, projectionBindingExpression.Type) + : new ProjectionBindingExpression( + _newQuery, projectionBindingExpression.Index!.Value, projectionBindingExpression.Type) + : base.Visit(expression); + } + } } } diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs index c5cc1024fab..926f64f5877 100644 --- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs @@ -413,9 +413,6 @@ protected override Expression VisitExtension(Expression extensionExpression) return ((SelectExpression)projectionBindingExpression.QueryExpression) .GetProjection(projectionBindingExpression); - case GroupByShaperExpression groupByShaperExpression: - return new GroupingElementExpression(groupByShaperExpression.ElementSelector); - case ShapedQueryExpression shapedQueryExpression: if (shapedQueryExpression.ResultCardinality == ResultCardinality.Enumerable) { @@ -548,180 +545,6 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp } } - // GroupBy Aggregate case - if (methodCallExpression.Object == null - && methodCallExpression.Method.DeclaringType == typeof(Enumerable) - && methodCallExpression.Arguments.Count > 0) - { - if (methodCallExpression.Arguments[0].Type.TryGetElementType(typeof(IQueryable<>)) == null - && Visit(methodCallExpression.Arguments[0]) is GroupingElementExpression groupingElementExpression) - { - Expression? result; - switch (methodCallExpression.Method.Name) - { - case nameof(Enumerable.Average): - if (methodCallExpression.Arguments.Count == 2) - { - groupingElementExpression = ApplySelector( - groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - } - - result = GetExpressionForAggregation(groupingElementExpression) is SqlExpression averageExpression - ? TranslateAverage(averageExpression) - : null; - break; - - case nameof(Enumerable.Count): - if (methodCallExpression.Arguments.Count == 2) - { - var newGroupingElementExpression = ApplyPredicate( - groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - if (newGroupingElementExpression == null) - { - result = null; - break; - } - groupingElementExpression = newGroupingElementExpression; - } - - result = TranslateCount(GetExpressionForAggregation(groupingElementExpression, starProjection: true)!); - break; - - case nameof(Enumerable.Distinct): - result = groupingElementExpression.Element is EntityShaperExpression - ? groupingElementExpression - : groupingElementExpression.IsDistinct - ? null - : groupingElementExpression.ApplyDistinct(); - break; - - case nameof(Enumerable.LongCount): - if (methodCallExpression.Arguments.Count == 2) - { - var newGroupingElementExpression = ApplyPredicate( - groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - if (newGroupingElementExpression == null) - { - result = null; - break; - } - groupingElementExpression = newGroupingElementExpression; - } - - result = TranslateLongCount(GetExpressionForAggregation(groupingElementExpression, starProjection: true)!); - break; - - case nameof(Enumerable.Max): - if (methodCallExpression.Arguments.Count == 2) - { - groupingElementExpression = ApplySelector( - groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - } - - result = GetExpressionForAggregation(groupingElementExpression) is SqlExpression maxExpression - ? TranslateMax(maxExpression) - : null; - break; - - case nameof(Enumerable.Min): - if (methodCallExpression.Arguments.Count == 2) - { - groupingElementExpression = ApplySelector( - groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - } - - result = GetExpressionForAggregation(groupingElementExpression) is SqlExpression minExpression - ? TranslateMin(minExpression) - : null; - break; - - case nameof(Enumerable.Select): - result = ApplySelector(groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - break; - - case nameof(Enumerable.Sum): - if (methodCallExpression.Arguments.Count == 2) - { - groupingElementExpression = ApplySelector( - groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - } - - result = GetExpressionForAggregation(groupingElementExpression) is SqlExpression sumExpression - ? TranslateSum(sumExpression) - : null; - break; - - case nameof(Enumerable.Where): - result = ApplyPredicate(groupingElementExpression, methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); - break; - - default: - result = null; - break; - } - - return result ?? throw new InvalidOperationException(CoreStrings.TranslationFailed(methodCallExpression.Print())); - - GroupingElementExpression? ApplyPredicate(GroupingElementExpression groupingElement, LambdaExpression lambdaExpression) - { - var predicate = TranslateInternal(RemapLambda(groupingElement, lambdaExpression)); - - return predicate == null - ? null - : groupingElement.ApplyPredicate(predicate); - } - - static GroupingElementExpression ApplySelector( - GroupingElementExpression groupingElement, - LambdaExpression lambdaExpression) - { - var selector = RemapLambda(groupingElement, lambdaExpression); - - return groupingElement.ApplySelector(selector); - } - - static Expression RemapLambda(GroupingElementExpression groupingElement, LambdaExpression lambdaExpression) - => ReplacingExpressionVisitor.Replace( - lambdaExpression.Parameters[0], groupingElement.Element, lambdaExpression.Body); - - SqlExpression? GetExpressionForAggregation(GroupingElementExpression groupingElement, bool starProjection = false) - { - var selector = TranslateInternal(groupingElement.Element); - if (selector == null) - { - if (starProjection) - { - selector = _sqlExpressionFactory.Fragment("*"); - } - else - { - return null; - } - } - - if (groupingElement.Predicate != null) - { - if (selector is SqlFragmentExpression) - { - selector = _sqlExpressionFactory.Constant(1); - } - - selector = _sqlExpressionFactory.Case( - new List { new(groupingElement.Predicate, selector) }, - elseResult: null); - } - - if (groupingElement.IsDistinct - && !(selector is SqlFragmentExpression)) - { - selector = new DistinctExpression(selector); - } - - return selector; - } - } - } - // Subquery case var subqueryTranslation = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression); if (subqueryTranslation != null) @@ -1551,61 +1374,6 @@ public Expression Convert(Type type) } } - private sealed class GroupingElementExpression : Expression - { - public GroupingElementExpression(Expression element) - { - Element = element; - } - - public Expression Element { get; private set; } - public bool IsDistinct { get; private set; } - public SqlExpression? Predicate { get; private set; } - - public GroupingElementExpression ApplyDistinct() - { - IsDistinct = true; - - return this; - } - - public GroupingElementExpression ApplySelector(Expression expression) - { - Element = expression; - - return this; - } - - public GroupingElementExpression ApplyPredicate(SqlExpression expression) - { - Check.NotNull(expression, nameof(expression)); - - if (expression is SqlConstantExpression sqlConstant - && sqlConstant.Value is bool boolValue - && boolValue) - { - return this; - } - - Predicate = Predicate == null - ? expression - : new SqlBinaryExpression( - ExpressionType.AndAlso, - Predicate, - expression, - typeof(bool), - expression.TypeMapping); - - return this; - } - - public override Type Type - => typeof(IEnumerable<>).MakeGenericType(Element.Type); - - public override ExpressionType NodeType - => ExpressionType.Extension; - } - private sealed class SqlTypeMappingVerifyingExpressionVisitor : ExpressionVisitor { protected override Expression VisitExtension(Expression extensionExpression) diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.Helper.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.Helper.cs index 0adf12361a2..dd47a931825 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.Helper.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.Helper.cs @@ -633,7 +633,7 @@ public SelectExpressionVerifyingExpressionVisitor(IEnumerable(selectExpression._projectionMapping.Count); + foreach (var keyValuePair in selectExpression._projectionMapping) + { + newProjectionMappings[keyValuePair.Key] = Visit(keyValuePair.Value); + } + // We ignore projection binding related elements as we don't want to copy them over for top level // Nested level will have _projection populated and no binding elements var newProjections = selectExpression._projection.Select(Visit).ToList(); @@ -770,7 +776,8 @@ private sealed class CloningExpressionVisitor : ExpressionVisitor Limit = limit, IsDistinct = selectExpression.IsDistinct, Tags = selectExpression.Tags, - _usedAliases = selectExpression._usedAliases.ToHashSet() + _usedAliases = selectExpression._usedAliases.ToHashSet(), + _projectionMapping = newProjectionMappings }; newSelectExpression._tptLeftJoinTables.AddRange(selectExpression._tptLeftJoinTables); @@ -789,7 +796,6 @@ private sealed class CloningExpressionVisitor : ExpressionVisitor .Visit(newSelectExpression); return newSelectExpression; - } return expression is ICloneable cloneable ? (Expression)cloneable.Clone() : base.Visit(expression); @@ -812,6 +818,7 @@ public ColumnExpressionReplacingExpressionVisitor(SelectExpression oldSelectExpr { return expression is ConcreteColumnExpression concreteColumnExpression && _oldSelectExpression.ContainsTableReference(concreteColumnExpression) + && _newTableReferences.ContainsKey(concreteColumnExpression.TableAlias) ? new ConcreteColumnExpression( concreteColumnExpression.Name, _newTableReferences[concreteColumnExpression.TableAlias], @@ -821,5 +828,126 @@ public ColumnExpressionReplacingExpressionVisitor(SelectExpression oldSelectExpr : base.Visit(expression); } } + + private sealed class GroupByAggregateLiftingExpressionVisitor : ExpressionVisitor + { + private readonly SelectExpression _selectExpression; + + public GroupByAggregateLiftingExpressionVisitor(SelectExpression selectExpression) + { + _selectExpression = selectExpression; + } + + [return: NotNullIfNotNull("expression")] + public override Expression? Visit(Expression? expression) + { + if (expression is SqlExpression sqlExpression + && sqlExpression is ScalarSubqueryExpression scalarSubqueryExpression) + { + // A scalar subquery on a GROUP BY may represent aggregation which can be lifted. + var subquery = scalarSubqueryExpression.Subquery; + if (subquery.Limit == null + && subquery.Offset == null + && subquery._groupBy.Count == 0 + && subquery.Predicate != null) + { + var initialTableCounts = 0; + var potentialTableCount = Math.Min(_selectExpression._tables.Count, subquery._tables.Count); + for (var i = 0; i < potentialTableCount; i++) + { + if (!string.Equals(_selectExpression._tableReferences[i].Alias, + subquery._tableReferences[i].Alias, StringComparison.OrdinalIgnoreCase)) + { + break; + } + + if (_selectExpression._tables[i] is SelectExpression originalNestedSelectExpression + && subquery._tables[i] is SelectExpression subqueryNestedSelectExpression) + { + CopyOverOwnedJoinInSameTable(originalNestedSelectExpression, subqueryNestedSelectExpression); + } + + initialTableCounts++; + } + + if (initialTableCounts > 0) + { + // If there are no initial table then this is not correlated grouping subquery + var columnExpressionReplacingExpressionVisitor = new ColumnExpressionReplacingExpressionVisitor(subquery, _selectExpression); + if (subquery._tables.Count != initialTableCounts) + { + // If subquery has more tables then we expanded join on it. + for (var i = initialTableCounts; i < subquery._tables.Count; i++) + { + // We re-use the same table reference with updated selectExpression + // So we don't need to remap those columns, they will transfer automatically. + var table = subquery._tables[i]; + var tableReference = subquery._tableReferences[i]; + table = (TableExpressionBase)columnExpressionReplacingExpressionVisitor.Visit(table); + tableReference.UpdateTableReference(subquery, _selectExpression); + _selectExpression.AddTable(table, tableReference); + } + } + + var updatedProjection = columnExpressionReplacingExpressionVisitor.Visit(subquery._projection[0].Expression); + + return updatedProjection; + } + } + } + + if (expression is SelectExpression innerSelectExpression + && innerSelectExpression.GroupBy.Count > 0) + { + expression = new GroupByAggregateLiftingExpressionVisitor(innerSelectExpression).Visit(innerSelectExpression); + } + + return base.Visit(expression); + } + + private void CopyOverOwnedJoinInSameTable(SelectExpression target, SelectExpression source) + { + if (target._projection.Count != source._projection.Count) + { + var columnExpressionReplacingExpressionVisitor = new ColumnExpressionReplacingExpressionVisitor(source, target); + var minProjectionCount = Math.Min(target._projection.Count, source._projection.Count); + var initialProjectionCount = 0; + for (var i = 0; i < minProjectionCount; i++) + { + var projectionToCopy = source._projection[i]; + var transformedProjection = (ProjectionExpression)columnExpressionReplacingExpressionVisitor.Visit(projectionToCopy); + if (!transformedProjection.Equals(target._projection[i])) + { + break; + } + + initialProjectionCount++; + } + + if (initialProjectionCount < source._projection.Count) + { + for (var i = initialProjectionCount; i < source._projection.Count; i++) + { + var projectionToCopy = source._projection[i].Expression; + if (projectionToCopy is not ConcreteColumnExpression columnToCopy) + { + continue; + } + + var transformedProjection = (ConcreteColumnExpression)columnExpressionReplacingExpressionVisitor.Visit(projectionToCopy); + if (target._projection.FindIndex(e => e.Expression.Equals(transformedProjection)) == -1) + { + target._projection.Add(new ProjectionExpression(transformedProjection, transformedProjection.Name)); + if (UnwrapJoinExpression(columnToCopy.Table) is SelectExpression innerSelectExpression) + { + var tableIndex = source._tableReferences.FindIndex(e => e.Alias == columnToCopy.TableAlias); + CopyOverOwnedJoinInSameTable((SelectExpression)UnwrapJoinExpression(target._tables[tableIndex]), innerSelectExpression); + } + } + } + } + } + } + } } } diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 656cc9793c2..ff6ed2ebb7d 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -59,6 +59,8 @@ public sealed partial class SelectExpression : TableExpressionBase private List _clientProjections = new(); private readonly List _aliasForClientProjections = new(); + private CloningExpressionVisitor? _cloningExpressionVisitor; + private SelectExpression( string? alias, List projections, @@ -1020,6 +1022,8 @@ public int AddToProjection(SqlExpression sqlExpression) private int AddToProjection(SqlExpression sqlExpression, string? alias, bool assignUniqueTableAlias = true) { + sqlExpression = TryLiftGroupByAggregate(sqlExpression); + var existingIndex = _projection.FindIndex(pe => pe.Expression.Equals(sqlExpression)); if (existingIndex != -1) { @@ -1055,12 +1059,12 @@ private int AddToProjection(SqlExpression sqlExpression, string? alias, bool ass /// /// Applies filter predicate to the . /// - /// An expression to use for filtering. - public void ApplyPredicate(SqlExpression expression) + /// An expression to use for filtering. + public void ApplyPredicate(SqlExpression sqlExpression) { - Check.NotNull(expression, nameof(expression)); + Check.NotNull(sqlExpression, nameof(sqlExpression)); - if (expression is SqlConstantExpression sqlConstant + if (sqlExpression is SqlConstantExpression sqlConstant && sqlConstant.Value is bool boolValue && boolValue) { @@ -1070,32 +1074,33 @@ public void ApplyPredicate(SqlExpression expression) if (Limit != null || Offset != null) { - expression = PushdownIntoSubqueryInternal().Remap(expression); + sqlExpression = PushdownIntoSubqueryInternal().Remap(sqlExpression); } - expression = AssignUniqueAliases(expression); + sqlExpression = TryLiftGroupByAggregate(sqlExpression); + sqlExpression = AssignUniqueAliases(sqlExpression); if (_groupBy.Count > 0) { Having = Having == null - ? expression + ? sqlExpression : new SqlBinaryExpression( ExpressionType.AndAlso, Having, - expression, + sqlExpression, typeof(bool), - expression.TypeMapping); + sqlExpression.TypeMapping); } else { Predicate = Predicate == null - ? expression + ? sqlExpression : new SqlBinaryExpression( ExpressionType.AndAlso, Predicate, - expression, + sqlExpression, typeof(bool), - expression.TypeMapping); + sqlExpression.TypeMapping); } } @@ -1144,6 +1149,14 @@ public Expression ApplyGrouping(Expression keySelector) return keySelector; } + /// + /// Clears existing group by terms. + /// + public void ClearGroupBy() + { + _groupBy.Clear(); + } + private void AppendGroupBy(Expression keySelector, List groupByTerms, List groupByAliases, string? name) { Check.NotNull(keySelector, nameof(keySelector)); @@ -1198,7 +1211,7 @@ public void ApplyOrdering(OrderingExpression orderingExpression) } _orderings.Clear(); - _orderings.Add(orderingExpression.Update(AssignUniqueAliases(orderingExpression.Expression))); + AppendOrdering(orderingExpression); } /// @@ -1209,6 +1222,12 @@ public void AppendOrdering(OrderingExpression orderingExpression) { Check.NotNull(orderingExpression, nameof(orderingExpression)); + if (_groupBy.Count > 0) + { + orderingExpression = orderingExpression.Update( + (SqlExpression)new GroupByAggregateLiftingExpressionVisitor(this).Visit(orderingExpression.Expression)); + } + if (_orderings.FirstOrDefault(o => o.Expression.Equals(orderingExpression.Expression)) == null) { _orderings.Add(orderingExpression.Update(AssignUniqueAliases(orderingExpression.Expression))); @@ -1385,20 +1404,6 @@ private void ApplySetOperation(SetOperationType setOperationType, SelectExpressi select2.PushdownIntoSubquery(); select2.ClearOrdering(); } - // select1 already has unique aliases. We unique-fy select2 and set operation alias. - select2 = (SelectExpression)new AliasUniquefier(_usedAliases).Visit(select2); - var setOperationAlias = GenerateUniqueAlias(_usedAliases, "t"); - - var setExpression = setOperationType switch - { - SetOperationType.Except => (SetOperationBase)new ExceptExpression(setOperationAlias, select1, select2, distinct), - SetOperationType.Intersect => new IntersectExpression(setOperationAlias, select1, select2, distinct), - SetOperationType.Union => new UnionExpression(setOperationAlias, select1, select2, distinct), - _ => throw new InvalidOperationException(CoreStrings.InvalidSwitch(nameof(setOperationType), setOperationType)) - }; - var tableReferenceExpression = new TableReferenceExpression(this, setExpression.Alias); - _tables.Add(setExpression); - _tableReferences.Add(tableReferenceExpression); if (_clientProjections.Count > 0 || select2._clientProjections.Count > 0) @@ -1413,6 +1418,9 @@ private void ApplySetOperation(SetOperationType setOperationType, SelectExpressi throw new InvalidOperationException(RelationalStrings.ProjectionMappingCountMismatch); } + var setOperationAlias = GenerateUniqueAlias(_usedAliases, "t"); + var tableReferenceExpression = new TableReferenceExpression(this, setOperationAlias); + var aliasUniquefier = new AliasUniquefier(_usedAliases); foreach (var joinedMapping in select1._projectionMapping.Join( select2._projectionMapping, @@ -1427,9 +1435,7 @@ private void ApplySetOperation(SetOperationType setOperationType, SelectExpressi continue; } - // We have to unique-fy left side since those projections were never uniquefied - // Right side is unique already when we did it when running select2 through it. - var innerColumn1 = (SqlExpression)aliasUniquefier.Visit(joinedMapping.Value1); + var innerColumn1 = (SqlExpression)joinedMapping.Value1; var innerColumn2 = (SqlExpression)joinedMapping.Value2; // For now, make sure that both sides output the same store type, otherwise the query may fail. // TODO: with #15586 we'll be able to also allow different store types which are implicitly convertible to one another. @@ -1438,6 +1444,13 @@ private void ApplySetOperation(SetOperationType setOperationType, SelectExpressi throw new InvalidOperationException(RelationalStrings.SetOperationsOnDifferentStoreTypes); } + innerColumn1 = select1.TryLiftGroupByAggregate(innerColumn1); + innerColumn2 = select2.TryLiftGroupByAggregate(innerColumn2); + + // We have to unique-fy left side since those projections were never uniquefied + // Right side is unique already when we did it when running select2 through it. + innerColumn1 = (SqlExpression)aliasUniquefier.Visit(innerColumn1); + var alias = GenerateUniqueColumnAlias( joinedMapping.Key.Last?.Name ?? (innerColumn1 as ColumnExpression)?.Name @@ -1479,6 +1492,19 @@ private void ApplySetOperation(SetOperationType setOperationType, SelectExpressi } } + // We generate actual set operation after applying projection to lift group by aggregate + // select1 already has unique aliases. We unique-fy select2 and set operation alias. + select2 = (SelectExpression)aliasUniquefier.Visit(select2); + var setExpression = setOperationType switch + { + SetOperationType.Except => (SetOperationBase)new ExceptExpression(setOperationAlias, select1, select2, distinct), + SetOperationType.Intersect => new IntersectExpression(setOperationAlias, select1, select2, distinct), + SetOperationType.Union => new UnionExpression(setOperationAlias, select1, select2, distinct), + _ => throw new InvalidOperationException(CoreStrings.InvalidSwitch(nameof(setOperationType), setOperationType)) + }; + _tables.Add(setExpression); + _tableReferences.Add(tableReferenceExpression); + // We should apply _identifiers only when it is distinct and actual select expression had identifiers. if (distinct && outerIdentifiers.Length > 0) @@ -1785,7 +1811,6 @@ static IReadOnlyDictionary GetPropertyExpressionsFr } } - private enum JoinType { InnerJoin, @@ -2850,6 +2875,23 @@ public void PrepareForAggregate() } } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public SelectExpression Clone() + { + if (_cloningExpressionVisitor == null) + { + _cloningExpressionVisitor = new(); + } + + return (SelectExpression)_cloningExpressionVisitor.Visit(this); + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -3003,6 +3045,10 @@ private bool ContainsTableReference(ColumnExpression column) // At that point aliases are not unique-fied across so we need to match tables => Tables.Any(e => ReferenceEquals(e, column.Table)); + private SqlExpression TryLiftGroupByAggregate(SqlExpression sqlExpression) + => _groupBy.Count > 0 + ? (SqlExpression)new GroupByAggregateLiftingExpressionVisitor(this).Visit(sqlExpression) + : sqlExpression; private void AddTable(TableExpressionBase tableExpressionBase, TableReferenceExpression tableReferenceExpression) { diff --git a/src/EFCore/Query/GroupByShaperExpression.cs b/src/EFCore/Query/GroupByShaperExpression.cs index c4ea98dfd96..80f080efbcd 100644 --- a/src/EFCore/Query/GroupByShaperExpression.cs +++ b/src/EFCore/Query/GroupByShaperExpression.cs @@ -23,16 +23,16 @@ public class GroupByShaperExpression : Expression, IPrintableExpression /// Creates a new instance of the class. /// /// An expression representing key selector for the grouping element. - /// An expression representing element selector for the grouping element. + /// A expression representing element selector for the grouping element. public GroupByShaperExpression( Expression keySelector, - Expression elementSelector) + ShapedQueryExpression groupingEnumerable) { Check.NotNull(keySelector, nameof(keySelector)); - Check.NotNull(elementSelector, nameof(elementSelector)); + Check.NotNull(groupingEnumerable, nameof(groupingEnumerable)); KeySelector = keySelector; - ElementSelector = elementSelector; + GroupingEnumerable = groupingEnumerable; } /// @@ -43,11 +43,11 @@ public GroupByShaperExpression( /// /// The expression representing the element selector for this grouping element. /// - public virtual Expression ElementSelector { get; } + public virtual ShapedQueryExpression GroupingEnumerable { get; } /// public override Type Type - => typeof(IGrouping<,>).MakeGenericType(KeySelector.Type, ElementSelector.Type); + => typeof(IGrouping<,>).MakeGenericType(KeySelector.Type, GroupingEnumerable.ShaperExpression.Type); /// public sealed override ExpressionType NodeType @@ -59,9 +59,9 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) Check.NotNull(visitor, nameof(visitor)); var keySelector = visitor.Visit(KeySelector); - var elementSelector = visitor.Visit(ElementSelector); + var groupingEnumerable = (ShapedQueryExpression)visitor.Visit(GroupingEnumerable); - return Update(keySelector, elementSelector); + return Update(keySelector, groupingEnumerable); } /// @@ -69,15 +69,15 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) /// return this expression. /// /// The property of the result. - /// The property of the result. + /// The property of the result. /// This expression if no children changed, or an expression with the updated children. - public virtual GroupByShaperExpression Update(Expression keySelector, Expression elementSelector) + public virtual GroupByShaperExpression Update(Expression keySelector, ShapedQueryExpression groupingEnumerable) { Check.NotNull(keySelector, nameof(keySelector)); - Check.NotNull(elementSelector, nameof(elementSelector)); + Check.NotNull(groupingEnumerable, nameof(groupingEnumerable)); - return keySelector != KeySelector || elementSelector != ElementSelector - ? new GroupByShaperExpression(keySelector, elementSelector) + return keySelector != KeySelector || groupingEnumerable != GroupingEnumerable + ? new GroupByShaperExpression(keySelector, groupingEnumerable) : this; } @@ -90,8 +90,8 @@ void IPrintableExpression.Print(ExpressionPrinter expressionPrinter) expressionPrinter.Append("KeySelector: "); expressionPrinter.Visit(KeySelector); expressionPrinter.AppendLine(", "); - expressionPrinter.Append("ElementSelector:"); - expressionPrinter.Visit(ElementSelector); + expressionPrinter.Append("GroupingEnumerable:"); + expressionPrinter.Visit(GroupingEnumerable); expressionPrinter.AppendLine(); } } diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs index e429142d98b..daba7d2903c 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs @@ -1064,6 +1064,111 @@ protected override Expression VisitExtension(Expression extensionExpression) } } + private sealed class CloningExpressionVisitor : ExpressionVisitor + { + private readonly Dictionary _clonedMap = new(ReferenceEqualityComparer.Instance); + + public NavigationTreeNode Clone(NavigationTreeNode navigationTreeNode) + { + _clonedMap.Clear(); + + return (NavigationTreeNode)Visit(navigationTreeNode); + } + + public IReadOnlyDictionary ClonedNodesMap => _clonedMap; + + [return: NotNullIfNotNull("expression")] + public override Expression? Visit(Expression? expression) + { + switch (expression) + { + case EntityReference entityReference: + return entityReference.Snapshot(); + + case NavigationTreeExpression navigationTreeExpression: + if (!_clonedMap.TryGetValue(navigationTreeExpression, out var clonedNavigationTreeExpression)) + { + clonedNavigationTreeExpression = new NavigationTreeExpression(Visit(navigationTreeExpression.Value)); + _clonedMap[navigationTreeExpression] = clonedNavigationTreeExpression; + } + + return clonedNavigationTreeExpression; + + case NavigationTreeNode navigationTreeNode: + if (!_clonedMap.TryGetValue(navigationTreeNode, out var clonedNavigationTreeNode)) + { + clonedNavigationTreeNode = new NavigationTreeNode( + (NavigationTreeNode)Visit(navigationTreeNode.Left!), + (NavigationTreeNode)Visit(navigationTreeNode.Right!)); + _clonedMap[navigationTreeNode] = clonedNavigationTreeNode; + } + + return clonedNavigationTreeNode; + + default: + return base.Visit(expression); + } + } + } + + private sealed class GroupingElementReplacingExpressionVisitor : ExpressionVisitor + { + private readonly CloningExpressionVisitor _cloningExpressionVisitor; + private readonly ParameterExpression _parameterExpression; + private readonly NavigationExpansionExpression _navigationExpansionExpression; + private readonly Expression? _keyAccessExpression; + private readonly MemberInfo? _keyMemberInfo; + + public GroupingElementReplacingExpressionVisitor( + ParameterExpression parameterExpression, + GroupByNavigationExpansionExpression groupByNavigationExpansionExpression) + { + _parameterExpression = parameterExpression; + _navigationExpansionExpression = (NavigationExpansionExpression)groupByNavigationExpansionExpression.GroupingEnumerable; + _keyAccessExpression = Expression.MakeMemberAccess(groupByNavigationExpansionExpression.CurrentParameter, + groupByNavigationExpansionExpression.CurrentParameter.Type.GetRequiredDeclaredProperty(nameof(IGrouping.Key))); + _keyMemberInfo = parameterExpression.Type.GetRequiredDeclaredProperty(nameof(IGrouping.Key)); + _cloningExpressionVisitor = new CloningExpressionVisitor(); + } + + public GroupingElementReplacingExpressionVisitor( + ParameterExpression parameterExpression, + NavigationExpansionExpression navigationExpansionExpression) + { + _parameterExpression = parameterExpression; + _navigationExpansionExpression = navigationExpansionExpression; + _cloningExpressionVisitor = new CloningExpressionVisitor(); + } + + [return: NotNullIfNotNull("expression")] + public override Expression? Visit(Expression? expression) + { + if (expression == _parameterExpression) + { + var currentTree = _cloningExpressionVisitor.Clone(_navigationExpansionExpression.CurrentTree); + + return new NavigationExpansionExpression( + _navigationExpansionExpression.Source, + currentTree, + new ReplacingExpressionVisitor( + _cloningExpressionVisitor.ClonedNodesMap.Keys.ToList(), + _cloningExpressionVisitor.ClonedNodesMap.Values.ToList()) + .Visit(_navigationExpansionExpression.PendingSelector), + _navigationExpansionExpression.CurrentParameter.Name!); + } + + return base.Visit(expression); + } + + protected override Expression VisitMember(MemberExpression memberExpression) + { + return memberExpression.Member == _keyMemberInfo + && memberExpression.Expression == _parameterExpression + ? _keyAccessExpression! + : base.VisitMember(memberExpression); + } + } + private sealed class RemoveRedundantNavigationComparisonExpressionVisitor : ExpressionVisitor { private readonly IDiagnosticsLogger _logger; diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs index 2f917618133..f2c4a5f044b 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs @@ -24,8 +24,7 @@ public EntityReference(IEntityType entityType, QueryRootExpression? queryRootExp public IEntityType EntityType { get; } - public IDictionary<(IForeignKey, bool), Expression> ForeignKeyExpansionMap { get; } = - new Dictionary<(IForeignKey, bool), Expression>(); + public Dictionary<(IForeignKey, bool), Expression> ForeignKeyExpansionMap { get; } = new(); public bool IsOptional { get; private set; } public IncludeTreeNode IncludePaths { get; private set; } @@ -314,6 +313,68 @@ void IPrintableExpression.Print(ExpressionPrinter expressionPrinter) } } + private sealed class GroupByNavigationExpansionExpression : Expression, IPrintableExpression + { + public GroupByNavigationExpansionExpression( + Expression source, + ParameterExpression groupingParameter, + NavigationTreeNode currentTree, + Expression pendingSelector, + string innerParameterName) + { + Source = source; + CurrentParameter = groupingParameter; + Type = source.Type; + GroupingEnumerable = new NavigationExpansionExpression( + Call(QueryableMethods.AsQueryable.MakeGenericMethod(CurrentParameter.Type.GetGenericArguments()[1]), CurrentParameter), + currentTree, + pendingSelector, + innerParameterName); + } + + public Expression Source { get; private set; } + + public ParameterExpression CurrentParameter { get; } + + public Expression GroupingEnumerable { get; private set; } + + public Type SourceElementType + => CurrentParameter.Type; + + public void UpdateSource(Expression source) + { + Source = source; + } + + public override ExpressionType NodeType + => ExpressionType.Extension; + + public override Type Type { get; } + + protected override Expression VisitChildren(ExpressionVisitor visitor) + { + Check.NotNull(visitor, nameof(visitor)); + + return this; + } + + void IPrintableExpression.Print(ExpressionPrinter expressionPrinter) + { + Check.NotNull(expressionPrinter, nameof(expressionPrinter)); + + expressionPrinter.AppendLine(nameof(GroupByNavigationExpansionExpression)); + using (expressionPrinter.Indent()) + { + expressionPrinter.Append("Source: "); + expressionPrinter.Visit(Source); + expressionPrinter.AppendLine(); + expressionPrinter.Append("GroupingEnumerable: "); + expressionPrinter.Visit(GroupingEnumerable); + expressionPrinter.AppendLine(); + } + } + } + /// /// A leaf node on navigation tree, representing projection structures of /// . Contains , diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs index abfc4c0a9f0..4730ada9dd0 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs @@ -273,10 +273,8 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp case nameof(Queryable.Any) when genericMethod == QueryableMethods.AnyWithoutPredicate: - case nameof(Queryable.Count) when genericMethod == QueryableMethods.CountWithoutPredicate: - case nameof(Queryable.LongCount) when genericMethod == QueryableMethods.LongCountWithoutPredicate: return ProcessAllAnyCountLongCount( @@ -567,15 +565,143 @@ when QueryableMethods.IsSumWithSelector(method): } } + if (firstArgument is GroupByNavigationExpansionExpression groupBySource) + { + switch (method.Name) + { + case nameof(Queryable.AsQueryable) + when genericMethod == QueryableMethods.AsQueryable: + return groupBySource; + + case nameof(Queryable.Any) + when genericMethod == QueryableMethods.AnyWithoutPredicate: + case nameof(Queryable.Count) + when genericMethod == QueryableMethods.CountWithoutPredicate: + case nameof(Queryable.LongCount) + when genericMethod == QueryableMethods.LongCountWithoutPredicate: + return ProcessAllAnyCountLongCount( + groupBySource, + genericMethod, + predicate: null); + + case nameof(Queryable.All) + when genericMethod == QueryableMethods.All: + case nameof(Queryable.Any) + when genericMethod == QueryableMethods.AnyWithPredicate: + case nameof(Queryable.Count) + when genericMethod == QueryableMethods.CountWithPredicate: + case nameof(Queryable.LongCount) + when genericMethod == QueryableMethods.LongCountWithPredicate: + return ProcessAllAnyCountLongCount( + groupBySource, + genericMethod, + methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); + + // case nameof(Queryable.Average) + // when QueryableMethods.IsAverageWithoutSelector(method): + // case nameof(Queryable.Max) + // when genericMethod == QueryableMethods.MaxWithoutSelector: + // case nameof(Queryable.Min) + // when genericMethod == QueryableMethods.MinWithoutSelector: + // case nameof(Queryable.Sum) + // when QueryableMethods.IsSumWithoutSelector(method): + // return ProcessAverageMaxMinSum( + // groupBySource, + // genericMethod ?? method, + // selector: null); + + // case nameof(Queryable.Average) + // when QueryableMethods.IsAverageWithSelector(method): + // case nameof(Queryable.Sum) + // when QueryableMethods.IsSumWithSelector(method): + // case nameof(Queryable.Max) + // when genericMethod == QueryableMethods.MaxWithSelector: + // case nameof(Queryable.Min) + // when genericMethod == QueryableMethods.MinWithSelector: + // return ProcessAverageMaxMinSum( + // groupBySource, + // genericMethod ?? method, + // methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); + + case nameof(Queryable.OrderBy) + when genericMethod == QueryableMethods.OrderBy: + case nameof(Queryable.OrderByDescending) + when genericMethod == QueryableMethods.OrderByDescending: + return ProcessOrderByThenBy( + groupBySource, + genericMethod, + methodCallExpression.Arguments[1].UnwrapLambdaFromQuote(), + thenBy: false); + + case nameof(Queryable.ThenBy) + when genericMethod == QueryableMethods.ThenBy: + case nameof(Queryable.ThenByDescending) + when genericMethod == QueryableMethods.ThenByDescending: + return ProcessOrderByThenBy( + groupBySource, + genericMethod, + methodCallExpression.Arguments[1].UnwrapLambdaFromQuote(), + thenBy: true); + + case nameof(Queryable.Select) + when genericMethod == QueryableMethods.Select: + return ProcessSelect( + groupBySource, + methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); + + case nameof(Queryable.Skip) + when genericMethod == QueryableMethods.Skip: + case nameof(Queryable.Take) + when genericMethod == QueryableMethods.Take: + return ProcessSkipTake( + groupBySource, + genericMethod, + methodCallExpression.Arguments[1]); + + case nameof(Queryable.Where) + when genericMethod == QueryableMethods.Where: + return ProcessWhere( + groupBySource, + methodCallExpression.Arguments[1].UnwrapLambdaFromQuote()); + + default: + // Average/Max/Min/Sum + // Distinct + // Contains + // First/Single/Last(OrDefault) + // Join, LeftJoin, GroupJoin + // SelectMany + // Concat/Except/Intersect/Union + // Cast/OfType + // Include/ThenInclude/NotQuiteInclude + // GroupBy + // Reverse + // DefaultIfEmpty + throw new InvalidOperationException( + CoreStrings.TranslationFailed( + _reducingExpressionVisitor.Visit(methodCallExpression).Print())); + } + } + if (genericMethod == QueryableMethods.AsQueryable) { + if (firstArgument is NavigationTreeExpression navigationTreeExpression + && navigationTreeExpression.Type.IsGenericType + && navigationTreeExpression.Type.GetGenericTypeDefinition() == typeof(IGrouping<,>)) + { + // This is groupingElement.AsQueryable so we preserve it + return Expression.Call( + QueryableMethods.AsQueryable.MakeGenericMethod(navigationTreeExpression.Type.GetSequenceType()), + navigationTreeExpression); + } + return UnwrapCollectionMaterialization(firstArgument); } if (firstArgument.Type.TryGetElementType(typeof(IQueryable<>)) == null) { // firstArgument was not an queryable - var visitedArguments = new [] { firstArgument } + var visitedArguments = new[] { firstArgument } .Concat(methodCallExpression.Arguments.Skip(1).Select(e => Visit(e)!)); return ConvertToEnumerable(method, visitedArguments); @@ -799,13 +925,15 @@ private NavigationExpansionExpression ProcessFirstSingleLastOrDefault( return source; } - private NavigationExpansionExpression ProcessGroupBy( + // This returns Expression since it can also return a deferred GroupBy operation + private Expression ProcessGroupBy( NavigationExpansionExpression source, LambdaExpression keySelector, LambdaExpression? elementSelector, LambdaExpression? resultSelector) { var keySelectorBody = ExpandNavigationsForSource(source, RemapLambdaExpression(source, keySelector)); + // Need to generate lambda after processing element/result selector Expression result; if (elementSelector != null) @@ -813,26 +941,43 @@ private NavigationExpansionExpression ProcessGroupBy( source = ProcessSelect(source, elementSelector); } - source = (NavigationExpansionExpression)_pendingSelectorExpandingExpressionVisitor.Visit(source); - // TODO: Flow include in future - //source = (NavigationExpansionExpression)new IncludeApplyingExpressionVisitor( - // this, _queryCompilationContext.IsTracking).Visit(source); keySelector = GenerateLambda(keySelectorBody, source.CurrentParameter); - elementSelector = GenerateLambda(source.PendingSelector, source.CurrentParameter); - result = resultSelector == null - ? Expression.Call( - QueryableMethods.GroupByWithKeyElementSelector.MakeGenericMethod( - source.CurrentParameter.Type, keySelector.ReturnType, elementSelector.ReturnType), + var innerParameterName = GetParameterName("e"); + + if (resultSelector == null) + { + var groupingParameter = Expression.Parameter( + typeof(IGrouping<,>).MakeGenericType(keySelector.ReturnType, source.SourceElementType), + GetParameterName("g")); + var innerSource = Expression.Call( + QueryableMethods.GroupByWithKeySelector.MakeGenericMethod(source.SourceElementType, keySelector.ReturnType), source.Source, - Expression.Quote(keySelector), - Expression.Quote(elementSelector)) - : Expression.Call( - QueryableMethods.GroupByWithKeyElementResultSelector.MakeGenericMethod( - source.CurrentParameter.Type, keySelector.ReturnType, elementSelector.ReturnType, resultSelector.ReturnType), + Expression.Quote(keySelector)); + + return new GroupByNavigationExpansionExpression(innerSource, groupingParameter, source.CurrentTree, source.PendingSelector, innerParameterName); + } + + var enumerableParameter = Expression.Parameter( + typeof(IEnumerable<>).MakeGenericType(source.SourceElementType), + GetParameterName("g")); + var groupingEnumerable = new NavigationExpansionExpression( + Expression.Call(QueryableMethods.AsQueryable.MakeGenericMethod(source.SourceElementType), enumerableParameter), + source.CurrentTree, + source.PendingSelector, + innerParameterName); + + var resultSelectorBody = new GroupingElementReplacingExpressionVisitor( + resultSelector.Parameters[1], groupingEnumerable).Visit(resultSelector.Body); + + resultSelectorBody = Visit(resultSelectorBody); + resultSelector = Expression.Lambda(resultSelectorBody, resultSelector.Parameters[0], enumerableParameter); + + result = Expression.Call( + QueryableMethods.GroupByWithKeyResultSelector.MakeGenericMethod( + source.CurrentParameter.Type, keySelector.ReturnType, resultSelector.ReturnType), source.Source, Expression.Quote(keySelector), - Expression.Quote(elementSelector), - Expression.Quote(Visit(resultSelector))); + Expression.Quote(resultSelector)); var navigationTree = new NavigationTreeExpression(Expression.Default(result.Type.GetSequenceType())); var parameterName = GetParameterName("e"); @@ -1116,24 +1261,6 @@ private Expression ProcessReverse(NavigationExpansionExpression source) private NavigationExpansionExpression ProcessSelect(NavigationExpansionExpression source, LambdaExpression selector) { - // This is to apply aggregate operator on GroupBy right away rather than deferring - if (source.SourceElementType.IsGenericType - && source.SourceElementType.GetGenericTypeDefinition() == typeof(IGrouping<,>) - && !(selector.ReturnType.IsGenericType - && selector.ReturnType.GetGenericTypeDefinition() == typeof(IGrouping<,>))) - { - var selectorLambda = ProcessLambdaExpression(source, selector); - var newSource = Expression.Call( - QueryableMethods.Select.MakeGenericMethod(source.SourceElementType, selectorLambda.ReturnType), - source.Source, - Expression.Quote(selectorLambda)); - - var navigationTree = new NavigationTreeExpression(Expression.Default(selectorLambda.ReturnType)); - var parameterName = GetParameterName("e"); - - return new NavigationExpansionExpression(newSource, navigationTree, navigationTree, parameterName); - } - var selectorBody = ReplacingExpressionVisitor.Replace( selector.Parameters[0], source.PendingSelector, @@ -1289,6 +1416,80 @@ private NavigationExpansionExpression ProcessWhere(NavigationExpansionExpression return source; } + private Expression ProcessAllAnyCountLongCount( + GroupByNavigationExpansionExpression groupBySource, + MethodInfo genericMethod, + LambdaExpression? predicate) + { + if (predicate != null) + { + predicate = ProcessLambdaExpression(groupBySource, predicate); + + return Expression.Call( + genericMethod.MakeGenericMethod(groupBySource.SourceElementType), groupBySource.Source, Expression.Quote(predicate)); + } + + return Expression.Call(genericMethod.MakeGenericMethod(groupBySource.SourceElementType), groupBySource.Source); + } + + private GroupByNavigationExpansionExpression ProcessOrderByThenBy( + GroupByNavigationExpansionExpression groupBySource, + MethodInfo genericMethod, + LambdaExpression keySelector, + bool thenBy) + { + keySelector = ProcessLambdaExpression(groupBySource, keySelector); + + groupBySource.UpdateSource( + Expression.Call( + genericMethod.MakeGenericMethod(groupBySource.SourceElementType, keySelector.ReturnType), + groupBySource.Source, + Expression.Quote(keySelector))); + + return groupBySource; + } + + private NavigationExpansionExpression ProcessSelect(GroupByNavigationExpansionExpression groupBySource, LambdaExpression selector) + { + var selectorBody = new GroupingElementReplacingExpressionVisitor(selector.Parameters[0], groupBySource).Visit(selector.Body); + selectorBody = Visit(selectorBody); + selectorBody = new PendingSelectorExpandingExpressionVisitor(this, _extensibilityHelper, applyIncludes: true).Visit(selectorBody); + selectorBody = Reduce(selectorBody); + selector = Expression.Lambda(selectorBody, groupBySource.CurrentParameter); + + var newSource = Expression.Call( + QueryableMethods.Select.MakeGenericMethod(groupBySource.SourceElementType, selector.ReturnType), + groupBySource.Source, + Expression.Quote(selector)); + + var navigationTree = new NavigationTreeExpression(Expression.Default(selector.ReturnType)); + var parameterName = GetParameterName("e"); + + return new NavigationExpansionExpression(newSource, navigationTree, navigationTree, parameterName); + } + + private GroupByNavigationExpansionExpression ProcessSkipTake( + GroupByNavigationExpansionExpression groupBySource, + MethodInfo genericMethod, + Expression count) + { + groupBySource.UpdateSource(Expression.Call(genericMethod.MakeGenericMethod(groupBySource.SourceElementType), groupBySource.Source, count)); + + return groupBySource; + } + + private GroupByNavigationExpansionExpression ProcessWhere(GroupByNavigationExpansionExpression groupBySource, LambdaExpression predicate) + { + predicate = ProcessLambdaExpression(groupBySource, predicate); + groupBySource.UpdateSource( + Expression.Call( + QueryableMethods.Where.MakeGenericMethod(groupBySource.SourceElementType), + groupBySource.Source, + Expression.Quote(predicate))); + + return groupBySource; + } + private void ApplyPendingOrderings(NavigationExpansionExpression source) { if (source.PendingOrderings.Any()) @@ -1664,6 +1865,13 @@ private Expression RemapLambdaExpression(NavigationExpansionExpression source, L private LambdaExpression ProcessLambdaExpression(NavigationExpansionExpression source, LambdaExpression lambdaExpression) => GenerateLambda(ExpandNavigationsForSource(source, RemapLambdaExpression(source, lambdaExpression)), source.CurrentParameter); + private LambdaExpression ProcessLambdaExpression(GroupByNavigationExpansionExpression groupBySource, LambdaExpression lambdaExpression) + { + return Expression.Lambda( + Visit(new GroupingElementReplacingExpressionVisitor(lambdaExpression.Parameters[0], groupBySource).Visit(lambdaExpression.Body)), + groupBySource.CurrentParameter); + } + private static IEnumerable FindNavigations(IEntityType entityType, string navigationName) { var navigation = entityType.FindNavigation(navigationName); diff --git a/src/EFCore/Query/ProjectionMember.cs b/src/EFCore/Query/ProjectionMember.cs index 14a8cdb88ff..3e3805770eb 100644 --- a/src/EFCore/Query/ProjectionMember.cs +++ b/src/EFCore/Query/ProjectionMember.cs @@ -81,6 +81,7 @@ public MemberInfo? Last => _memberChain.LastOrDefault(); /// + [DebuggerStepThrough] public override int GetHashCode() { var hash = new HashCode(); @@ -94,6 +95,7 @@ public override int GetHashCode() } /// + [DebuggerStepThrough] public override bool Equals(object? obj) => obj != null && (obj is ProjectionMember projectionMember diff --git a/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs b/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs index aa37b8cc649..c157250c775 100644 --- a/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs +++ b/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs @@ -21,7 +21,7 @@ public class TestSqlLoggerFactory : ListLoggerFactory private static readonly string _eol = Environment.NewLine; - private static object _queryBaselineFileLock = new(); + private static readonly object _queryBaselineFileLock = new(); public TestSqlLoggerFactory() : this(_ => true) @@ -129,7 +129,7 @@ public TestSqlLogger(bool shouldLogCommands) public List SqlStatements { get; } = new(); public List Parameters { get; } = new(); - private StringBuilder _stringBuilder = new(); + private readonly StringBuilder _stringBuilder = new(); protected override void UnsafeClear() { diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsCollectionsQueryTestBase.cs index 2f5acadd94b..6353b847bfd 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsCollectionsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsCollectionsQueryTestBase.cs @@ -1197,7 +1197,7 @@ public virtual Task Include_collection_with_multiple_orderbys_complex_repeated_c } } - [ConditionalTheory(Skip = "Issue#12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Include_collection_with_groupby_in_subquery(bool async) { @@ -1210,7 +1210,7 @@ public virtual Task Include_collection_with_groupby_in_subquery(bool async) elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude(e => e.OneToMany_Optional1))); } - [ConditionalTheory(Skip = "Issue#12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Include_collection_with_groupby_in_subquery_and_filter_before_groupby(bool async) { @@ -1224,7 +1224,7 @@ public virtual Task Include_collection_with_groupby_in_subquery_and_filter_befor elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude(e => e.OneToMany_Optional1))); } - [ConditionalTheory(Skip = "Issue#12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Include_collection_with_groupby_in_subquery_and_filter_after_groupby(bool async) { diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs index 00b4569290e..e01a8b72d77 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs @@ -2817,7 +2817,7 @@ public virtual void Entries_for_detached_entities_are_removed() context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; } - [ConditionalTheory(Skip = "Issue#12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Include_reference_with_groupby_in_subquery(bool async) { @@ -2830,7 +2830,7 @@ public virtual Task Include_reference_with_groupby_in_subquery(bool async) elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude(e => e.OneToOne_Optional_FK1))); } - [ConditionalTheory(Skip = "Issue#12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Multi_include_with_groupby_in_subquery(bool async) { diff --git a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index 454533ae4cd..8ad55926fd5 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -5881,7 +5881,7 @@ public virtual Task Group_by_entity_key_with_include_on_that_entity_with_key_in_ }); } - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Group_by_with_include_with_entity_in_result_selector(bool async) { @@ -5910,7 +5910,7 @@ public virtual Task Group_by_with_include_with_entity_in_result_selector(bool as }); } - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Include_with_group_by_and_FirstOrDefault_gets_properly_applied(bool async) { diff --git a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs index fef0dd56864..36225beca3c 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs @@ -2017,17 +2017,20 @@ public virtual Task Distinct_GroupBy_OrderBy_key(bool async) assertOrder: true); } - [ConditionalTheory(Skip = "Issue #21965")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Select_nested_collection_with_groupby(bool async) { return AssertQuery( async, - ss => ss.Set().Where(c => c.CustomerID.StartsWith("A")) + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")) + .OrderBy(c => c.CustomerID) .Select( c => c.Orders.Any() ? c.Orders.GroupBy(o => o.OrderID).Select(g => g.Key).ToArray() - : Array.Empty())); + : Array.Empty()), + assertOrder: true, + elementAsserter: (e, a) => Assert.True(e.SequenceEqual(a))); } [ConditionalTheory] @@ -2263,7 +2266,13 @@ public virtual Task GroupBy_aggregate_over_a_subquery(bool async) async, ss => ss.Set() .GroupBy(o => o.CustomerID) - .Select(g => new { g.Key, Count = (from c in ss.Set() where c.CustomerID == g.Key select c).Count() })); + .Select(g => new { g.Key, Count = (from c in ss.Set() where c.CustomerID == g.Key select c).Count() }), + elementSorter: e => e.Key, + elementAsserter: (e, a) => + { + AssertEqual(e.Key, a.Key); + AssertEqual(e.Count, a.Count); + }); } [ConditionalTheory] @@ -2613,61 +2622,62 @@ public virtual Task GroupBy_group_Where_Select_Distinct_aggregate(bool async) #region GroupByWithoutAggregate - [ConditionalTheory(Skip = "Issue #18923")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task GroupBy_SelectMany(bool async) { - return AssertQuery( + return AssertTranslationFailed(() => AssertQuery( async, ss => ss.Set().GroupBy(c => c.City).SelectMany(g => g), - entryCount: 91); + entryCount: 91)); } - [ConditionalTheory(Skip = "Issue #18923")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task OrderBy_GroupBy_SelectMany(bool async) { - return AssertQuery( + return AssertTranslationFailed(() => AssertQuery( async, ss => ss.Set().OrderBy(o => o.OrderID) .GroupBy(o => o.CustomerID) .SelectMany(g => g), - entryCount: 830); + entryCount: 830)); } - [ConditionalTheory(Skip = "Issue #18923")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task OrderBy_GroupBy_SelectMany_shadow(bool async) { - return AssertQuery( + return AssertTranslationFailed(() => AssertQuery( async, ss => ss.Set().OrderBy(e => e.EmployeeID) .GroupBy(e => e.EmployeeID) .SelectMany(g => g) - .Select(g => EF.Property(g, "Title"))); + .Select(g => EF.Property(g, "Title")))); } - [ConditionalTheory(Skip = "Issue#17761")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task GroupBy_with_orderby_take_skip_distinct_followed_by_group_key_projection(bool async) { - return AssertQuery( + return AssertTranslationFailed(() => AssertQuery( async, ss => ss.Set().GroupBy(o => o.CustomerID).OrderBy(g => g.Key).Take(5).Skip(3).Distinct().Select(g => g.Key), assertOrder: true, - entryCount: 31); + entryCount: 31)); } - [ConditionalTheory(Skip = "Issue #17761")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task GroupBy_Distinct(bool async) { - return AssertQuery( - async, - ss => ss.Set().GroupBy(o => o.CustomerID).Distinct().Select(g => g.Key)); + return AssertTranslationFailed( + () => AssertQuery( + async, + ss => ss.Set().GroupBy(o => o.CustomerID).Distinct().Select(g => g.Key))); } - [ConditionalTheory(Skip = "Issue #18923")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task GroupBy_with_aggregate_through_navigation_property(bool async) { @@ -2682,7 +2692,7 @@ public virtual Task GroupBy_with_aggregate_through_navigation_property(bool asyn #region GroupBySelectFirst - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task GroupBy_Shadow(bool async) { @@ -2693,7 +2703,7 @@ public virtual Task GroupBy_Shadow(bool async) .Select(g => EF.Property(g.First(), "Title"))); } - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task GroupBy_Shadow2(bool async) { @@ -2701,10 +2711,11 @@ public virtual Task GroupBy_Shadow2(bool async) async, ss => ss.Set().Where(e => EF.Property(e, "Title") == "Sales Representative" && e.EmployeeID == 1) .GroupBy(e => EF.Property(e, "Title")) - .Select(g => g.First())); + .Select(g => g.First()), + entryCount: 1); } - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task GroupBy_Shadow3(bool async) { @@ -2943,7 +2954,7 @@ public virtual Task GroupBy_aggregate_followed_by_another_GroupBy_aggregate(bool # region GroupByInSubquery - [ConditionalTheory(Skip = "issue #15279")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Complex_query_with_groupBy_in_subquery1(bool async) { @@ -2967,7 +2978,7 @@ public virtual Task Complex_query_with_groupBy_in_subquery1(bool async) }); } - [ConditionalTheory(Skip = "issue #15279")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Complex_query_with_groupBy_in_subquery2(bool async) { @@ -2991,7 +3002,7 @@ public virtual Task Complex_query_with_groupBy_in_subquery2(bool async) }); } - [ConditionalTheory(Skip = "issue #15279")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Complex_query_with_groupBy_in_subquery3(bool async) { @@ -3011,7 +3022,7 @@ public virtual Task Complex_query_with_groupBy_in_subquery3(bool async) elementAsserter: (e, a) => { Assert.Equal(e.Key, a.Key); - AssertCollection(e.Subquery, a.Subquery); + AssertCollection(e.Subquery, a.Subquery, elementSorter: i => i.Sum); }); } @@ -3076,27 +3087,27 @@ public virtual Task GroupBy_scalar_aggregate_in_set_operation(bool async) [MemberData(nameof(IsAsyncData))] public virtual Task AsEnumerable_in_subquery_for_GroupBy(bool async) { - return AssertTranslationFailed( - () => AssertQuery( - async, - ss => ss.Set() - .Where(c => c.CustomerID.StartsWith("F")) - .Select(c => new - { - Customer = c, - Orders = ss.Set() - .Where(o => o.CustomerID == c.CustomerID) - .AsEnumerable() - .GroupBy(o => o.CustomerID) - .Select(g => g.OrderByDescending(e => e.OrderDate).FirstOrDefault()) - .ToList() - }), - elementSorter: e => e.Customer.CustomerID, - elementAsserter: (e, a) => + return AssertQuery( + async, + ss => ss.Set() + .Where(c => c.CustomerID.StartsWith("F")) + .Select(c => new { - AssertEqual(e.Customer, a.Customer); - AssertCollection(e.Orders, a.Orders); - })); + Customer = c, + Orders = ss.Set() + .Where(o => o.CustomerID == c.CustomerID) + .AsEnumerable() + .GroupBy(o => o.CustomerID) + .Select(g => g.OrderByDescending(e => e.OrderDate).FirstOrDefault()) + .ToList() + }), + elementSorter: e => e.Customer.CustomerID, + elementAsserter: (e, a) => + { + AssertEqual(e.Customer, a.Customer); + AssertCollection(e.Orders, a.Orders); + }, + entryCount: 15); } #endregion diff --git a/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs index e647f8de83c..9c0a7c94146 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs @@ -1273,7 +1273,7 @@ public virtual async Task Include_specified_on_non_entity_not_supported(bool asy ss => ss.Set().Select(c => new Tuple(c, 5)).Include(t => t.Item1.Orders)))).Message); } - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Include_collection_GroupBy_Select(bool async) { @@ -1283,10 +1283,11 @@ public virtual Task Include_collection_GroupBy_Select(bool async) .Where(o => o.OrderID == 10248) .Include(o => o.OrderDetails) .GroupBy(e => e.OrderID) - .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault())); + .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault()), + entryCount: 4); } - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Include_reference_GroupBy_Select(bool async) { @@ -1296,10 +1297,11 @@ public virtual Task Include_reference_GroupBy_Select(bool async) .Where(o => o.OrderID == 10248) .Include(o => o.Customer) .GroupBy(e => e.OrderID) - .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault())); + .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault()), + entryCount: 2); } - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Include_collection_Join_GroupBy_Select(bool async) { @@ -1314,10 +1316,11 @@ public virtual Task Include_collection_Join_GroupBy_Select(bool async) od => od.OrderID, (o, od) => o) .GroupBy(e => e.OrderID) - .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault())); + .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault()), + entryCount: 4); } - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Include_reference_Join_GroupBy_Select(bool async) { @@ -1332,10 +1335,11 @@ public virtual Task Include_reference_Join_GroupBy_Select(bool async) od => od.OrderID, (o, od) => o) .GroupBy(e => e.OrderID) - .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault())); + .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault()), + entryCount: 2); } - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Join_Include_collection_GroupBy_Select(bool async) { @@ -1349,10 +1353,11 @@ public virtual Task Join_Include_collection_GroupBy_Select(bool async) o => o.OrderID, (od, o) => o) .GroupBy(e => e.OrderID) - .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault())); + .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault()), + entryCount: 4); } - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Join_Include_reference_GroupBy_Select(bool async) { @@ -1365,10 +1370,11 @@ public virtual Task Join_Include_reference_GroupBy_Select(bool async) o => o.OrderID, (od, o) => o) .GroupBy(e => e.OrderID) - .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault())); + .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault()), + entryCount: 919); } - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Include_collection_SelectMany_GroupBy_Select(bool async) { @@ -1378,10 +1384,11 @@ public virtual Task Include_collection_SelectMany_GroupBy_Select(bool async) from od in ss.Set() select o) .GroupBy(e => e.OrderID) - .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault())); + .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault()), + entryCount: 4); } - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Include_reference_SelectMany_GroupBy_Select(bool async) { @@ -1391,10 +1398,11 @@ public virtual Task Include_reference_SelectMany_GroupBy_Select(bool async) from od in ss.Set() select o) .GroupBy(e => e.OrderID) - .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault())); + .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault()), + entryCount: 2); } - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task SelectMany_Include_collection_GroupBy_Select(bool async) { @@ -1404,10 +1412,11 @@ public virtual Task SelectMany_Include_collection_GroupBy_Select(bool async) from o in ss.Set().Include(o => o.OrderDetails) select o) .GroupBy(e => e.OrderID) - .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault())); + .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault()), + entryCount: 2985); } - [ConditionalTheory(Skip = "Issue #12088")] + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task SelectMany_Include_reference_GroupBy_Select(bool async) { @@ -1417,7 +1426,8 @@ public virtual Task SelectMany_Include_reference_GroupBy_Select(bool async) from o in ss.Set().Include(o => o.Customer) select o) .GroupBy(e => e.OrderID) - .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault())); + .Select(e => e.OrderBy(o => o.OrderID).FirstOrDefault()), + entryCount: 919); } [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/Query/NorthwindSetOperationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindSetOperationsQueryTestBase.cs index c127cc898c9..d9e520879c2 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindSetOperationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindSetOperationsQueryTestBase.cs @@ -539,49 +539,364 @@ public virtual Task Union_over_columns_with_different_nullability(bool async) } [ConditionalTheory] -#pragma warning disable xUnit1016 // MemberData must reference a public member - [MemberData(nameof(GetSetOperandTestCases))] -#pragma warning restore xUnit1016 // MemberData must reference a public member - public virtual Task Union_over_different_projection_types(bool async, string leftType, string rightType) + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_column_column(bool async) { - var (left, right) = (ExpressionGenerator(leftType), ExpressionGenerator(rightType)); - return AssertQuery(async, ss => left(ss.Set()).Union(right(ss.Set()))); + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderID) + .Union(ss.Set().Select(o => o.OrderID))); + } - static Func, IQueryable> ExpressionGenerator(string expressionType) - { - switch (expressionType) - { - case "Column": - return os => os.Select(o => (object)o.OrderID); - case "Function": - return os => os - .GroupBy(o => o.OrderID) - .Select(g => (object)g.Count()); - case "Constant": - return os => os.Select(o => (object)8); - case "Unary": - return os => os.Select(o => (object)-o.OrderID); - case "Binary": - return os => os.Select(o => (object)(o.OrderID + 1)); - case "ScalarSubquery": - return os => os.Select(o => (object)o.OrderDetails.Count()); - default: - throw new InvalidOperationException(); - } - } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_column_function(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderID) + .Union(ss.Set().GroupBy(o => o.OrderID).Select(g => g.Count()))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_column_constant(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderID) + .Union(ss.Set().Select(o => 8))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_column_unary(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderID) + .Union(ss.Set().Select(o => -o.OrderID))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_column_binary(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderID) + .Union(ss.Set().Select(o => o.OrderID + 1))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_column_scalarsubquery(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderID) + .Union(ss.Set().Select(o => o.OrderDetails.Count()))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_function_column(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().GroupBy(o => o.OrderID).Select(g => g.Count()) + .Union(ss.Set().Select(o => o.OrderID))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_function_function(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().GroupBy(o => o.OrderID).Select(g => g.Count()) + .Union(ss.Set().GroupBy(o => o.OrderID).Select(g => g.Count()))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_function_constant(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().GroupBy(o => o.OrderID).Select(g => g.Count()) + .Union(ss.Set().Select(o => 8))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_function_unary(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().GroupBy(o => o.OrderID).Select(g => g.Count()) + .Union(ss.Set().Select(o => -o.OrderID))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_function_binary(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().GroupBy(o => o.OrderID).Select(g => g.Count()) + .Union(ss.Set().Select(o => o.OrderID + 1))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_function_scalarsubquery(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().GroupBy(o => o.OrderID).Select(g => g.Count()) + .Union(ss.Set().Select(o => o.OrderDetails.Count()))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_constant_column(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => 8) + .Union(ss.Set().Select(o => o.OrderID))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_constant_function(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => 8) + .Union(ss.Set().GroupBy(o => o.OrderID).Select(g => g.Count()))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_constant_constant(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => 8) + .Union(ss.Set().Select(o => 8))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_constant_unary(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => 8) + .Union(ss.Set().Select(o => -o.OrderID))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_constant_binary(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => 8) + .Union(ss.Set().Select(o => o.OrderID + 1))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_constant_scalarsubquery(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => 8) + .Union(ss.Set().Select(o => o.OrderDetails.Count()))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_unary_column(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => -o.OrderID) + .Union(ss.Set().Select(o => o.OrderID))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_unary_function(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => -o.OrderID) + .Union(ss.Set().GroupBy(o => o.OrderID).Select(g => g.Count()))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_unary_constant(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => -o.OrderID) + .Union(ss.Set().Select(o => 8))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_unary_unary(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => -o.OrderID) + .Union(ss.Set().Select(o => -o.OrderID))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_unary_binary(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => -o.OrderID) + .Union(ss.Set().Select(o => o.OrderID + 1))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_unary_scalarsubquery(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => -o.OrderID) + .Union(ss.Set().Select(o => o.OrderDetails.Count()))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_binary_column(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderID + 1) + .Union(ss.Set().Select(o => o.OrderID))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_binary_function(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderID + 1) + .Union(ss.Set().GroupBy(o => o.OrderID).Select(g => g.Count()))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_binary_constant(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderID + 1) + .Union(ss.Set().Select(o => 8))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_binary_unary(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderID + 1) + .Union(ss.Set().Select(o => -o.OrderID))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_binary_binary(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderID + 1) + .Union(ss.Set().Select(o => o.OrderID + 1))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_binary_scalarsubquery(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderID + 1) + .Union(ss.Set().Select(o => o.OrderDetails.Count()))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_scalarsubquery_column(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderDetails.Count()) + .Union(ss.Set().Select(o => o.OrderID))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_scalarsubquery_function(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderDetails.Count()) + .Union(ss.Set().GroupBy(o => o.OrderID).Select(g => g.Count()))); } - private static IEnumerable GetSetOperandTestCases() - => from async in new[] { true, false } - from leftType in _supportedOperandExpressionType - from rightType in _supportedOperandExpressionType - select new object[] { async, leftType, rightType }; + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_scalarsubquery_constant(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderDetails.Count()) + .Union(ss.Set().Select(o => 8))); + } - // ReSharper disable once StaticMemberInGenericType - private static readonly string[] _supportedOperandExpressionType = + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_scalarsubquery_unary(bool async) { - "Column", "Function", "Constant", "Unary", "Binary", "ScalarSubquery" - }; + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderDetails.Count()) + .Union(ss.Set().Select(o => -o.OrderID))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_scalarsubquery_binary(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderDetails.Count()) + .Union(ss.Set().Select(o => o.OrderID + 1))); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Union_over_scalarsubquery_scalarsubquery(bool async) + { + return AssertQueryScalar( + async, + ss => ss.Set().Select(o => o.OrderDetails.Count()) + .Union(ss.Set().Select(o => o.OrderDetails.Count()))); + } [ConditionalTheory] [MemberData(nameof(IsAsyncData))] diff --git a/test/EFCore.Specification.Tests/TestUtilities/QueryTestGeneration/ProceduralQueryExpressionGenerator.cs b/test/EFCore.Specification.Tests/TestUtilities/QueryTestGeneration/ProceduralQueryExpressionGenerator.cs index 95cba7b59fe..fe0cc13371e 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/QueryTestGeneration/ProceduralQueryExpressionGenerator.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/QueryTestGeneration/ProceduralQueryExpressionGenerator.cs @@ -173,10 +173,6 @@ static ProcedurallyGeneratedQueryExecutor() "Collection_select_nav_prop_first_or_default_then_nav_prop_nested_using_property_method", "Unable to cast object of type 'System.String' to type"); // 12601 - AddExpectedFailure("GroupBy_Shadow", "Value does not fall within the expected range."); // 12088 - AddExpectedFailure("GroupBy_Shadow3", "Value does not fall within the expected range."); // 12088 - AddExpectedFailure("GroupBy_SelectMany", "Value does not fall within the expected range."); // 12088 - AddExpectedFailure("GroupJoin_GroupBy_Aggregate_5", "Incorrect syntax near '+'."); // 12656 AddExpectedFailure("GroupBy_Key_as_part_of_element_selector", "Incorrect syntax near '+'."); // 12656 AddExpectedFailure("GroupBy_Property_Select_Key_Min", "Incorrect syntax near '+'."); // 12656 diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsQuerySqlServerTest.cs index 2e36283e717..c6a41f5b5b5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsQuerySqlServerTest.cs @@ -698,6 +698,78 @@ FROM [LevelTwo] AS [l] ORDER BY -[l].[Level1_Required_Id], [l].[Name], [l].[Id], [l0].[Id]"); } + public override async Task Include_collection_with_groupby_in_subquery(bool async) + { + await base.Include_collection_with_groupby_in_subquery(async); + + AssertSql( + @"SELECT [t0].[Id], [t0].[Date], [t0].[Name], [t0].[OneToMany_Optional_Self_Inverse1Id], [t0].[OneToMany_Required_Self_Inverse1Id], [t0].[OneToOne_Optional_Self1Id], [t].[Name], [l1].[Id], [l1].[Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse2Id], [l1].[OneToMany_Optional_Self_Inverse2Id], [l1].[OneToMany_Required_Inverse2Id], [l1].[OneToMany_Required_Self_Inverse2Id], [l1].[OneToOne_Optional_PK_Inverse2Id], [l1].[OneToOne_Optional_Self2Id] +FROM ( + SELECT [l].[Name] + FROM [LevelOne] AS [l] + GROUP BY [l].[Name] +) AS [t] +LEFT JOIN ( + SELECT [t1].[Id], [t1].[Date], [t1].[Name], [t1].[OneToMany_Optional_Self_Inverse1Id], [t1].[OneToMany_Required_Self_Inverse1Id], [t1].[OneToOne_Optional_Self1Id] + FROM ( + SELECT [l0].[Id], [l0].[Date], [l0].[Name], [l0].[OneToMany_Optional_Self_Inverse1Id], [l0].[OneToMany_Required_Self_Inverse1Id], [l0].[OneToOne_Optional_Self1Id], ROW_NUMBER() OVER(PARTITION BY [l0].[Name] ORDER BY [l0].[Id]) AS [row] + FROM [LevelOne] AS [l0] + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Name] = [t0].[Name] +LEFT JOIN [LevelTwo] AS [l1] ON [t0].[Id] = [l1].[OneToMany_Optional_Inverse2Id] +ORDER BY [t].[Name], [t0].[Name], [l1].[Id]"); + } + + public override async Task Include_collection_with_groupby_in_subquery_and_filter_before_groupby(bool async) + { + await base.Include_collection_with_groupby_in_subquery_and_filter_before_groupby(async); + + AssertSql( + @"SELECT [t0].[Id], [t0].[Date], [t0].[Name], [t0].[OneToMany_Optional_Self_Inverse1Id], [t0].[OneToMany_Required_Self_Inverse1Id], [t0].[OneToOne_Optional_Self1Id], [t].[Name], [l1].[Id], [l1].[Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse2Id], [l1].[OneToMany_Optional_Self_Inverse2Id], [l1].[OneToMany_Required_Inverse2Id], [l1].[OneToMany_Required_Self_Inverse2Id], [l1].[OneToOne_Optional_PK_Inverse2Id], [l1].[OneToOne_Optional_Self2Id] +FROM ( + SELECT [l].[Name] + FROM [LevelOne] AS [l] + WHERE [l].[Id] > 3 + GROUP BY [l].[Name] +) AS [t] +LEFT JOIN ( + SELECT [t1].[Id], [t1].[Date], [t1].[Name], [t1].[OneToMany_Optional_Self_Inverse1Id], [t1].[OneToMany_Required_Self_Inverse1Id], [t1].[OneToOne_Optional_Self1Id] + FROM ( + SELECT [l0].[Id], [l0].[Date], [l0].[Name], [l0].[OneToMany_Optional_Self_Inverse1Id], [l0].[OneToMany_Required_Self_Inverse1Id], [l0].[OneToOne_Optional_Self1Id], ROW_NUMBER() OVER(PARTITION BY [l0].[Name] ORDER BY [l0].[Id]) AS [row] + FROM [LevelOne] AS [l0] + WHERE [l0].[Id] > 3 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Name] = [t0].[Name] +LEFT JOIN [LevelTwo] AS [l1] ON [t0].[Id] = [l1].[OneToMany_Optional_Inverse2Id] +ORDER BY [t].[Name], [t0].[Name], [l1].[Id]"); + } + + public override async Task Include_collection_with_groupby_in_subquery_and_filter_after_groupby(bool async) + { + await base.Include_collection_with_groupby_in_subquery_and_filter_after_groupby(async); + + AssertSql( + @"SELECT [t0].[Id], [t0].[Date], [t0].[Name], [t0].[OneToMany_Optional_Self_Inverse1Id], [t0].[OneToMany_Required_Self_Inverse1Id], [t0].[OneToOne_Optional_Self1Id], [t].[Name], [l1].[Id], [l1].[Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse2Id], [l1].[OneToMany_Optional_Self_Inverse2Id], [l1].[OneToMany_Required_Inverse2Id], [l1].[OneToMany_Required_Self_Inverse2Id], [l1].[OneToOne_Optional_PK_Inverse2Id], [l1].[OneToOne_Optional_Self2Id] +FROM ( + SELECT [l].[Name] + FROM [LevelOne] AS [l] + GROUP BY [l].[Name] + HAVING ([l].[Name] <> N'Foo') OR [l].[Name] IS NULL +) AS [t] +LEFT JOIN ( + SELECT [t1].[Id], [t1].[Date], [t1].[Name], [t1].[OneToMany_Optional_Self_Inverse1Id], [t1].[OneToMany_Required_Self_Inverse1Id], [t1].[OneToOne_Optional_Self1Id] + FROM ( + SELECT [l0].[Id], [l0].[Date], [l0].[Name], [l0].[OneToMany_Optional_Self_Inverse1Id], [l0].[OneToMany_Required_Self_Inverse1Id], [l0].[OneToOne_Optional_Self1Id], ROW_NUMBER() OVER(PARTITION BY [l0].[Name] ORDER BY [l0].[Id]) AS [row] + FROM [LevelOne] AS [l0] + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Name] = [t0].[Name] +LEFT JOIN [LevelTwo] AS [l1] ON [t0].[Id] = [l1].[OneToMany_Optional_Inverse2Id] +ORDER BY [t].[Name], [t0].[Name], [l1].[Id]"); + } + public override async Task Include_reference_collection_order_by_reference_navigation(bool async) { await base.Include_reference_collection_order_by_reference_navigation(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQuerySqlServerTest.cs index 0cf51ca17bc..78f1814f64a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsCollectionsSplitQuerySqlServerTest.cs @@ -2151,21 +2151,123 @@ public override async Task Include_collection_with_groupby_in_subquery(bool asyn { await base.Include_collection_with_groupby_in_subquery(async); - AssertSql(" "); + AssertSql( + @"SELECT [t0].[Id], [t0].[Date], [t0].[Name], [t0].[OneToMany_Optional_Self_Inverse1Id], [t0].[OneToMany_Required_Self_Inverse1Id], [t0].[OneToOne_Optional_Self1Id], [t].[Name] +FROM ( + SELECT [l].[Name] + FROM [LevelOne] AS [l] + GROUP BY [l].[Name] +) AS [t] +LEFT JOIN ( + SELECT [t1].[Id], [t1].[Date], [t1].[Name], [t1].[OneToMany_Optional_Self_Inverse1Id], [t1].[OneToMany_Required_Self_Inverse1Id], [t1].[OneToOne_Optional_Self1Id] + FROM ( + SELECT [l0].[Id], [l0].[Date], [l0].[Name], [l0].[OneToMany_Optional_Self_Inverse1Id], [l0].[OneToMany_Required_Self_Inverse1Id], [l0].[OneToOne_Optional_Self1Id], ROW_NUMBER() OVER(PARTITION BY [l0].[Name] ORDER BY [l0].[Id]) AS [row] + FROM [LevelOne] AS [l0] + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Name] = [t0].[Name] +ORDER BY [t].[Name], [t0].[Name]", + // + @"SELECT [l1].[Id], [l1].[Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse2Id], [l1].[OneToMany_Optional_Self_Inverse2Id], [l1].[OneToMany_Required_Inverse2Id], [l1].[OneToMany_Required_Self_Inverse2Id], [l1].[OneToOne_Optional_PK_Inverse2Id], [l1].[OneToOne_Optional_Self2Id], [t].[Name], [t0].[Name] +FROM ( + SELECT [l].[Name] + FROM [LevelOne] AS [l] + GROUP BY [l].[Name] +) AS [t] +LEFT JOIN ( + SELECT [t1].[Id], [t1].[Name] + FROM ( + SELECT [l0].[Id], [l0].[Name], ROW_NUMBER() OVER(PARTITION BY [l0].[Name] ORDER BY [l0].[Id]) AS [row] + FROM [LevelOne] AS [l0] + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Name] = [t0].[Name] +INNER JOIN [LevelTwo] AS [l1] ON [t0].[Id] = [l1].[OneToMany_Optional_Inverse2Id] +ORDER BY [t].[Name], [t0].[Name]"); } public override async Task Include_collection_with_groupby_in_subquery_and_filter_before_groupby(bool async) { await base.Include_collection_with_groupby_in_subquery_and_filter_before_groupby(async); - AssertSql(" "); + AssertSql( + @"SELECT [t0].[Id], [t0].[Date], [t0].[Name], [t0].[OneToMany_Optional_Self_Inverse1Id], [t0].[OneToMany_Required_Self_Inverse1Id], [t0].[OneToOne_Optional_Self1Id], [t].[Name] +FROM ( + SELECT [l].[Name] + FROM [LevelOne] AS [l] + WHERE [l].[Id] > 3 + GROUP BY [l].[Name] +) AS [t] +LEFT JOIN ( + SELECT [t1].[Id], [t1].[Date], [t1].[Name], [t1].[OneToMany_Optional_Self_Inverse1Id], [t1].[OneToMany_Required_Self_Inverse1Id], [t1].[OneToOne_Optional_Self1Id] + FROM ( + SELECT [l0].[Id], [l0].[Date], [l0].[Name], [l0].[OneToMany_Optional_Self_Inverse1Id], [l0].[OneToMany_Required_Self_Inverse1Id], [l0].[OneToOne_Optional_Self1Id], ROW_NUMBER() OVER(PARTITION BY [l0].[Name] ORDER BY [l0].[Id]) AS [row] + FROM [LevelOne] AS [l0] + WHERE [l0].[Id] > 3 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Name] = [t0].[Name] +ORDER BY [t].[Name], [t0].[Name]", + // + @"SELECT [l1].[Id], [l1].[Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse2Id], [l1].[OneToMany_Optional_Self_Inverse2Id], [l1].[OneToMany_Required_Inverse2Id], [l1].[OneToMany_Required_Self_Inverse2Id], [l1].[OneToOne_Optional_PK_Inverse2Id], [l1].[OneToOne_Optional_Self2Id], [t].[Name], [t0].[Name] +FROM ( + SELECT [l].[Name] + FROM [LevelOne] AS [l] + WHERE [l].[Id] > 3 + GROUP BY [l].[Name] +) AS [t] +LEFT JOIN ( + SELECT [t1].[Id], [t1].[Name] + FROM ( + SELECT [l0].[Id], [l0].[Name], ROW_NUMBER() OVER(PARTITION BY [l0].[Name] ORDER BY [l0].[Id]) AS [row] + FROM [LevelOne] AS [l0] + WHERE [l0].[Id] > 3 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Name] = [t0].[Name] +INNER JOIN [LevelTwo] AS [l1] ON [t0].[Id] = [l1].[OneToMany_Optional_Inverse2Id] +ORDER BY [t].[Name], [t0].[Name]"); } public override async Task Include_collection_with_groupby_in_subquery_and_filter_after_groupby(bool async) { await base.Include_collection_with_groupby_in_subquery_and_filter_after_groupby(async); - AssertSql(" "); + AssertSql( + @"SELECT [t0].[Id], [t0].[Date], [t0].[Name], [t0].[OneToMany_Optional_Self_Inverse1Id], [t0].[OneToMany_Required_Self_Inverse1Id], [t0].[OneToOne_Optional_Self1Id], [t].[Name] +FROM ( + SELECT [l].[Name] + FROM [LevelOne] AS [l] + GROUP BY [l].[Name] + HAVING ([l].[Name] <> N'Foo') OR [l].[Name] IS NULL +) AS [t] +LEFT JOIN ( + SELECT [t1].[Id], [t1].[Date], [t1].[Name], [t1].[OneToMany_Optional_Self_Inverse1Id], [t1].[OneToMany_Required_Self_Inverse1Id], [t1].[OneToOne_Optional_Self1Id] + FROM ( + SELECT [l0].[Id], [l0].[Date], [l0].[Name], [l0].[OneToMany_Optional_Self_Inverse1Id], [l0].[OneToMany_Required_Self_Inverse1Id], [l0].[OneToOne_Optional_Self1Id], ROW_NUMBER() OVER(PARTITION BY [l0].[Name] ORDER BY [l0].[Id]) AS [row] + FROM [LevelOne] AS [l0] + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Name] = [t0].[Name] +ORDER BY [t].[Name], [t0].[Name]", + // + @"SELECT [l1].[Id], [l1].[Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse2Id], [l1].[OneToMany_Optional_Self_Inverse2Id], [l1].[OneToMany_Required_Inverse2Id], [l1].[OneToMany_Required_Self_Inverse2Id], [l1].[OneToOne_Optional_PK_Inverse2Id], [l1].[OneToOne_Optional_Self2Id], [t].[Name], [t0].[Name] +FROM ( + SELECT [l].[Name] + FROM [LevelOne] AS [l] + GROUP BY [l].[Name] + HAVING ([l].[Name] <> N'Foo') OR [l].[Name] IS NULL +) AS [t] +LEFT JOIN ( + SELECT [t1].[Id], [t1].[Name] + FROM ( + SELECT [l0].[Id], [l0].[Name], ROW_NUMBER() OVER(PARTITION BY [l0].[Name] ORDER BY [l0].[Id]) AS [row] + FROM [LevelOne] AS [l0] + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Name] = [t0].[Name] +INNER JOIN [LevelTwo] AS [l1] ON [t0].[Id] = [l1].[OneToMany_Optional_Inverse2Id] +ORDER BY [t].[Name], [t0].[Name]"); } public override async Task Include_reference_collection_order_by_reference_navigation(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index 9ce9bf0d096..764effc5202 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -2559,6 +2559,52 @@ WHERE [l0].[Id] > 5 ) AS [t] ON [l].[Id] = [t].[OneToMany_Optional_Inverse2Id]"); } + public override async Task Include_reference_with_groupby_in_subquery(bool async) + { + await base.Include_reference_with_groupby_in_subquery(async); + + AssertSql( + @"SELECT [t0].[Id], [t0].[Date], [t0].[Name], [t0].[OneToMany_Optional_Self_Inverse1Id], [t0].[OneToMany_Required_Self_Inverse1Id], [t0].[OneToOne_Optional_Self1Id], [t0].[Id0], [t0].[Date0], [t0].[Level1_Optional_Id], [t0].[Level1_Required_Id], [t0].[Name0], [t0].[OneToMany_Optional_Inverse2Id], [t0].[OneToMany_Optional_Self_Inverse2Id], [t0].[OneToMany_Required_Inverse2Id], [t0].[OneToMany_Required_Self_Inverse2Id], [t0].[OneToOne_Optional_PK_Inverse2Id], [t0].[OneToOne_Optional_Self2Id] +FROM ( + SELECT [l].[Name] + FROM [LevelOne] AS [l] + GROUP BY [l].[Name] +) AS [t] +LEFT JOIN ( + SELECT [t1].[Id], [t1].[Date], [t1].[Name], [t1].[OneToMany_Optional_Self_Inverse1Id], [t1].[OneToMany_Required_Self_Inverse1Id], [t1].[OneToOne_Optional_Self1Id], [t1].[Id0], [t1].[Date0], [t1].[Level1_Optional_Id], [t1].[Level1_Required_Id], [t1].[Name0], [t1].[OneToMany_Optional_Inverse2Id], [t1].[OneToMany_Optional_Self_Inverse2Id], [t1].[OneToMany_Required_Inverse2Id], [t1].[OneToMany_Required_Self_Inverse2Id], [t1].[OneToOne_Optional_PK_Inverse2Id], [t1].[OneToOne_Optional_Self2Id] + FROM ( + SELECT [l0].[Id], [l0].[Date], [l0].[Name], [l0].[OneToMany_Optional_Self_Inverse1Id], [l0].[OneToMany_Required_Self_Inverse1Id], [l0].[OneToOne_Optional_Self1Id], [l1].[Id] AS [Id0], [l1].[Date] AS [Date0], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Name] AS [Name0], [l1].[OneToMany_Optional_Inverse2Id], [l1].[OneToMany_Optional_Self_Inverse2Id], [l1].[OneToMany_Required_Inverse2Id], [l1].[OneToMany_Required_Self_Inverse2Id], [l1].[OneToOne_Optional_PK_Inverse2Id], [l1].[OneToOne_Optional_Self2Id], ROW_NUMBER() OVER(PARTITION BY [l0].[Name] ORDER BY [l0].[Id]) AS [row] + FROM [LevelOne] AS [l0] + LEFT JOIN [LevelTwo] AS [l1] ON [l0].[Id] = [l1].[Level1_Optional_Id] + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Name] = [t0].[Name]"); + } + + public override async Task Multi_include_with_groupby_in_subquery(bool async) + { + await base.Multi_include_with_groupby_in_subquery(async); + + AssertSql( + @"SELECT [t0].[Id], [t0].[Date], [t0].[Name], [t0].[OneToMany_Optional_Self_Inverse1Id], [t0].[OneToMany_Required_Self_Inverse1Id], [t0].[OneToOne_Optional_Self1Id], [t0].[Id0], [t0].[Date0], [t0].[Level1_Optional_Id], [t0].[Level1_Required_Id], [t0].[Name0], [t0].[OneToMany_Optional_Inverse2Id], [t0].[OneToMany_Optional_Self_Inverse2Id], [t0].[OneToMany_Required_Inverse2Id], [t0].[OneToMany_Required_Self_Inverse2Id], [t0].[OneToOne_Optional_PK_Inverse2Id], [t0].[OneToOne_Optional_Self2Id], [t].[Name], [l2].[Id], [l2].[Level2_Optional_Id], [l2].[Level2_Required_Id], [l2].[Name], [l2].[OneToMany_Optional_Inverse3Id], [l2].[OneToMany_Optional_Self_Inverse3Id], [l2].[OneToMany_Required_Inverse3Id], [l2].[OneToMany_Required_Self_Inverse3Id], [l2].[OneToOne_Optional_PK_Inverse3Id], [l2].[OneToOne_Optional_Self3Id] +FROM ( + SELECT [l].[Name] + FROM [LevelOne] AS [l] + GROUP BY [l].[Name] +) AS [t] +LEFT JOIN ( + SELECT [t1].[Id], [t1].[Date], [t1].[Name], [t1].[OneToMany_Optional_Self_Inverse1Id], [t1].[OneToMany_Required_Self_Inverse1Id], [t1].[OneToOne_Optional_Self1Id], [t1].[Id0], [t1].[Date0], [t1].[Level1_Optional_Id], [t1].[Level1_Required_Id], [t1].[Name0], [t1].[OneToMany_Optional_Inverse2Id], [t1].[OneToMany_Optional_Self_Inverse2Id], [t1].[OneToMany_Required_Inverse2Id], [t1].[OneToMany_Required_Self_Inverse2Id], [t1].[OneToOne_Optional_PK_Inverse2Id], [t1].[OneToOne_Optional_Self2Id] + FROM ( + SELECT [l0].[Id], [l0].[Date], [l0].[Name], [l0].[OneToMany_Optional_Self_Inverse1Id], [l0].[OneToMany_Required_Self_Inverse1Id], [l0].[OneToOne_Optional_Self1Id], [l1].[Id] AS [Id0], [l1].[Date] AS [Date0], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Name] AS [Name0], [l1].[OneToMany_Optional_Inverse2Id], [l1].[OneToMany_Optional_Self_Inverse2Id], [l1].[OneToMany_Required_Inverse2Id], [l1].[OneToMany_Required_Self_Inverse2Id], [l1].[OneToOne_Optional_PK_Inverse2Id], [l1].[OneToOne_Optional_Self2Id], ROW_NUMBER() OVER(PARTITION BY [l0].[Name] ORDER BY [l0].[Id]) AS [row] + FROM [LevelOne] AS [l0] + LEFT JOIN [LevelTwo] AS [l1] ON [l0].[Id] = [l1].[Level1_Optional_Id] + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Name] = [t0].[Name] +LEFT JOIN [LevelThree] AS [l2] ON [t0].[Id0] = [l2].[OneToMany_Optional_Inverse3Id] +ORDER BY [t].[Name], [t0].[Name], [t0].[Id0], [l2].[Id]"); + } + public override async Task String_include_multiple_derived_navigation_with_same_name_and_same_type(bool async) { await base.String_include_multiple_derived_navigation_with_same_name_and_same_type(async); @@ -3407,8 +3453,9 @@ public override async Task Element_selector_with_coalesce_repeated_in_aggregate( FROM [LevelOne] AS [l] LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Id] LEFT JOIN [LevelThree] AS [l1] ON [l0].[Id] = [l1].[Id] +LEFT JOIN [LevelTwo] AS [l2] ON [l].[Id] = [l2].[Id] GROUP BY [l1].[Name] -HAVING MIN(COALESCE([l0].[Id], 0) + COALESCE([l0].[Id], 0)) > 0"); +HAVING MIN(COALESCE([l2].[Id], 0) + COALESCE([l2].[Id], 0)) > 0"); } public override async Task Nested_object_constructed_from_group_key_properties(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs index 2dc8a0b7972..0373cf59881 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs @@ -118,8 +118,14 @@ FROM [Level1] AS [l3] ) AS [t1] ON [l2].[Id] = [t1].[Id] WHERE [l2].[Level2_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse3Id] IS NOT NULL ) AS [t0] ON [t].[Id] = [t0].[Id] +LEFT JOIN ( + SELECT [l5].[Id] + FROM [Level1] AS [l5] + INNER JOIN [Level1] AS [l6] ON [l5].[Id] = [l6].[Id] + WHERE ([l5].[OneToOne_Required_PK_Date] IS NOT NULL AND [l5].[Level1_Required_Id] IS NOT NULL) AND [l5].[OneToMany_Required_Inverse2Id] IS NOT NULL +) AS [t2] ON [l].[Id] = [t2].[Id] GROUP BY [t0].[Level3_Name] -HAVING MIN(COALESCE([t].[Id], 0)) > 0"); +HAVING MIN(COALESCE([t2].[Id], 0)) > 0"); } public override async Task Simple_level1_level2_level3_include(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index eb935223f31..c048d01aacd 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -5564,11 +5564,22 @@ public override async Task Group_by_with_include_with_entity_in_result_selector( await base.Group_by_with_include_with_entity_in_result_selector(async); AssertSql( - @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], [g.CityOfBirth].[Name], [g.CityOfBirth].[Location], [g.CityOfBirth].[Nation] -FROM [Gears] AS [g] -INNER JOIN [Cities] AS [g.CityOfBirth] ON [g].[CityOfBirthName] = [g.CityOfBirth].[Name] -WHERE [g].[Discriminator] IN (N'Officer', N'Gear') -ORDER BY [g].[Rank]"); + @"SELECT [t].[Rank], [t].[c], [t0].[Nickname], [t0].[SquadId], [t0].[AssignedCityName], [t0].[CityOfBirthName], [t0].[Discriminator], [t0].[FullName], [t0].[HasSoulPatch], [t0].[LeaderNickname], [t0].[LeaderSquadId], [t0].[Rank], [t0].[Name], [t0].[Location], [t0].[Nation] +FROM ( + SELECT [g].[Rank], COUNT(*) AS [c] + FROM [Gears] AS [g] + GROUP BY [g].[Rank] +) AS [t] +LEFT JOIN ( + SELECT [t1].[Nickname], [t1].[SquadId], [t1].[AssignedCityName], [t1].[CityOfBirthName], [t1].[Discriminator], [t1].[FullName], [t1].[HasSoulPatch], [t1].[LeaderNickname], [t1].[LeaderSquadId], [t1].[Rank], [t1].[Name], [t1].[Location], [t1].[Nation] + FROM ( + SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[Discriminator], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank], [c].[Name], [c].[Location], [c].[Nation], ROW_NUMBER() OVER(PARTITION BY [g0].[Rank] ORDER BY [g0].[Nickname]) AS [row] + FROM [Gears] AS [g0] + INNER JOIN [Cities] AS [c] ON [g0].[CityOfBirthName] = [c].[Name] + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Rank] = [t0].[Rank] +ORDER BY [t].[Rank]"); } public override async Task GroupBy_Property_Include_Select_Max(bool async) @@ -5586,10 +5597,22 @@ public override async Task Include_with_group_by_and_FirstOrDefault_gets_properl await base.Include_with_group_by_and_FirstOrDefault_gets_properly_applied(async); AssertSql( - @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], [g.CityOfBirth].[Name], [g.CityOfBirth].[Location], [g.CityOfBirth].[Nation] -FROM [Gears] AS [g] -INNER JOIN [Cities] AS [g.CityOfBirth] ON [g].[CityOfBirthName] = [g.CityOfBirth].[Name] -ORDER BY [g].[Rank]"); + @"SELECT [t0].[Nickname], [t0].[SquadId], [t0].[AssignedCityName], [t0].[CityOfBirthName], [t0].[Discriminator], [t0].[FullName], [t0].[HasSoulPatch], [t0].[LeaderNickname], [t0].[LeaderSquadId], [t0].[Rank], [t0].[Name], [t0].[Location], [t0].[Nation] +FROM ( + SELECT [g].[Rank] + FROM [Gears] AS [g] + GROUP BY [g].[Rank] +) AS [t] +LEFT JOIN ( + SELECT [t1].[Nickname], [t1].[SquadId], [t1].[AssignedCityName], [t1].[CityOfBirthName], [t1].[Discriminator], [t1].[FullName], [t1].[HasSoulPatch], [t1].[LeaderNickname], [t1].[LeaderSquadId], [t1].[Rank], [t1].[Name], [t1].[Location], [t1].[Nation] + FROM ( + SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[Discriminator], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank], [c].[Name], [c].[Location], [c].[Nation], ROW_NUMBER() OVER(PARTITION BY [g0].[Rank] ORDER BY [g0].[Rank], [c].[Name]) AS [row] + FROM [Gears] AS [g0] + INNER JOIN [Cities] AS [c] ON [g0].[CityOfBirthName] = [c].[Name] + WHERE [g0].[HasSoulPatch] = CAST(1 AS bit) + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Rank] = [t0].[Rank]"); } public override async Task Include_collection_with_Cast_to_base(bool async) @@ -6383,15 +6406,15 @@ public override async Task Complex_GroupBy_after_set_operator(bool async) FROM ( SELECT [c].[Name], ( SELECT COUNT(*) - FROM [Weapons] AS [w0] - WHERE [g].[FullName] = [w0].[OwnerFullName]) AS [Count] + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName]) AS [Count] FROM [Gears] AS [g] LEFT JOIN [Cities] AS [c] ON [g].[AssignedCityName] = [c].[Name] UNION ALL SELECT [c0].[Name], ( SELECT COUNT(*) - FROM [Weapons] AS [w] - WHERE [g0].[FullName] = [w].[OwnerFullName]) AS [Count] + FROM [Weapons] AS [w0] + WHERE [g0].[FullName] = [w0].[OwnerFullName]) AS [Count] FROM [Gears] AS [g0] INNER JOIN [Cities] AS [c0] ON [g0].[CityOfBirthName] = [c0].[Name] ) AS [t] @@ -6407,15 +6430,15 @@ public override async Task Complex_GroupBy_after_set_operator_using_result_selec FROM ( SELECT [c].[Name], ( SELECT COUNT(*) - FROM [Weapons] AS [w0] - WHERE [g].[FullName] = [w0].[OwnerFullName]) AS [Count] + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName]) AS [Count] FROM [Gears] AS [g] LEFT JOIN [Cities] AS [c] ON [g].[AssignedCityName] = [c].[Name] UNION ALL SELECT [c0].[Name], ( SELECT COUNT(*) - FROM [Weapons] AS [w] - WHERE [g0].[FullName] = [w].[OwnerFullName]) AS [Count] + FROM [Weapons] AS [w0] + WHERE [g0].[FullName] = [w0].[OwnerFullName]) AS [Count] FROM [Gears] AS [g0] INNER JOIN [Cities] AS [c0] ON [g0].[CityOfBirthName] = [c0].[Name] ) AS [t] @@ -6470,8 +6493,6 @@ public override async Task Group_by_over_projection_with_multiple_properties_acc @"SELECT [c].[Name] FROM [Gears] AS [g] INNER JOIN [Cities] AS [c] ON [g].[CityOfBirthName] = [c].[Name] -LEFT JOIN [Cities] AS [c0] ON [g].[AssignedCityName] = [c0].[Name] -INNER JOIN [Squads] AS [s] ON [g].[SquadId] = [s].[Id] GROUP BY [c].[Name]"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs index 61383e433b4..470313d494e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs @@ -722,7 +722,7 @@ public override async Task GroupBy_Property_scalar_element_selector_Count(bool a await base.GroupBy_Property_scalar_element_selector_Count(async); AssertSql( - @"SELECT COUNT([o].[OrderID]) + @"SELECT COUNT(*) FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -732,7 +732,7 @@ public override async Task GroupBy_Property_scalar_element_selector_LongCount(bo await base.GroupBy_Property_scalar_element_selector_LongCount(async); AssertSql( - @"SELECT COUNT_BIG([o].[OrderID]) + @"SELECT COUNT_BIG(*) FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -911,8 +911,12 @@ public override async Task GroupBy_empty_key_Aggregate(bool async) await base.GroupBy_empty_key_Aggregate(async); AssertSql( - @"SELECT COALESCE(SUM([o].[OrderID]), 0) -FROM [Orders] AS [o]"); + @"SELECT COALESCE(SUM([t].[OrderID]), 0) +FROM ( + SELECT [o].[OrderID], 1 AS [Key] + FROM [Orders] AS [o] +) AS [t] +GROUP BY [t].[Key]"); } public override async Task GroupBy_empty_key_Aggregate_Key(bool async) @@ -920,8 +924,12 @@ public override async Task GroupBy_empty_key_Aggregate_Key(bool async) await base.GroupBy_empty_key_Aggregate_Key(async); AssertSql( - @"SELECT COALESCE(SUM([o].[OrderID]), 0) AS [Sum] -FROM [Orders] AS [o]"); + @"SELECT COALESCE(SUM([t].[OrderID]), 0) AS [Sum] +FROM ( + SELECT [o].[OrderID], 1 AS [Key] + FROM [Orders] AS [o] +) AS [t] +GROUP BY [t].[Key]"); } public override async Task OrderBy_GroupBy_Aggregate(bool async) @@ -1545,45 +1553,22 @@ public override async Task Select_nested_collection_with_groupby(bool async) await base.Select_nested_collection_with_groupby(async); AssertSql( - @"SELECT ( - SELECT CASE - WHEN EXISTS ( - SELECT 1 - FROM [Orders] AS [o0] - WHERE [c].[CustomerID] = [o0].[CustomerID]) - THEN CAST(1 AS bit) ELSE CAST(0 AS bit) - END -), [c].[CustomerID] + @"SELECT CASE + WHEN EXISTS ( + SELECT 1 + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID]) THEN CAST(1 AS bit) + ELSE CAST(0 AS bit) +END, [c].[CustomerID], [t].[OrderID] FROM [Customers] AS [c] -WHERE [c].[CustomerID] LIKE N'A%'", - // - @"@_outer_CustomerID='ALFKI' (Size = 5) - -SELECT [o1].[OrderID] -FROM [Orders] AS [o1] -WHERE @_outer_CustomerID = [o1].[CustomerID] -ORDER BY [o1].[OrderID]", - // - @"@_outer_CustomerID='ANATR' (Size = 5) - -SELECT [o1].[OrderID] -FROM [Orders] AS [o1] -WHERE @_outer_CustomerID = [o1].[CustomerID] -ORDER BY [o1].[OrderID]", - // - @"@_outer_CustomerID='ANTON' (Size = 5) - -SELECT [o1].[OrderID] -FROM [Orders] AS [o1] -WHERE @_outer_CustomerID = [o1].[CustomerID] -ORDER BY [o1].[OrderID]", - // - @"@_outer_CustomerID='AROUT' (Size = 5) - -SELECT [o1].[OrderID] -FROM [Orders] AS [o1] -WHERE @_outer_CustomerID = [o1].[CustomerID] -ORDER BY [o1].[OrderID]"); +OUTER APPLY ( + SELECT [o0].[OrderID] + FROM [Orders] AS [o0] + WHERE [c].[CustomerID] = [o0].[CustomerID] + GROUP BY [o0].[OrderID] +) AS [t] +WHERE [c].[CustomerID] LIKE N'F%' +ORDER BY [c].[CustomerID], [t].[OrderID]"); } public override async Task Select_uncorrelated_collection_with_groupby_works(bool async) @@ -1716,7 +1701,7 @@ public override async Task GroupBy_Where_Count_with_predicate(bool async) AssertSql( @"SELECT COUNT(CASE - WHEN ([o].[OrderID] < 10300) AND ([o].[OrderDate] IS NOT NULL AND (DATEPART(year, [o].[OrderDate]) = 1997)) THEN 1 + WHEN (([o].[OrderID] < 10300) AND [o].[OrderDate] IS NOT NULL) AND (DATEPART(year, [o].[OrderDate]) = 1997) THEN 1 END) FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); @@ -1728,7 +1713,7 @@ public override async Task GroupBy_Where_Where_Count(bool async) AssertSql( @"SELECT COUNT(CASE - WHEN ([o].[OrderID] < 10300) AND ([o].[OrderDate] IS NOT NULL AND (DATEPART(year, [o].[OrderDate]) = 1997)) THEN 1 + WHEN (([o].[OrderID] < 10300) AND [o].[OrderDate] IS NOT NULL) AND (DATEPART(year, [o].[OrderDate]) = 1997) THEN 1 END) FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); @@ -1740,7 +1725,7 @@ public override async Task GroupBy_Where_Select_Where_Count(bool async) AssertSql( @"SELECT COUNT(CASE - WHEN ([o].[OrderID] < 10300) AND ([o].[OrderDate] IS NOT NULL AND (DATEPART(year, [o].[OrderDate]) = 1997)) THEN [o].[OrderDate] + WHEN (([o].[OrderID] < 10300) AND [o].[OrderDate] IS NOT NULL) AND (DATEPART(year, [o].[OrderDate]) = 1997) THEN 1 END) FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); @@ -1752,7 +1737,7 @@ public override async Task GroupBy_Where_Select_Where_Select_Min(bool async) AssertSql( @"SELECT MIN(CASE - WHEN ([o].[OrderID] < 10300) AND ([o].[OrderDate] IS NOT NULL AND (DATEPART(year, [o].[OrderDate]) = 1997)) THEN [o].[OrderID] + WHEN (([o].[OrderID] < 10300) AND [o].[OrderDate] IS NOT NULL) AND (DATEPART(year, [o].[OrderDate]) = 1997) THEN [o].[OrderID] END) FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); @@ -1824,68 +1809,15 @@ FROM [Orders] AS [o] GROUP BY [o].[OrderID], [o].[CustomerID]"); } - public override async Task GroupBy_SelectMany(bool async) - { - await base.GroupBy_SelectMany(async); - - AssertSql( - @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] -FROM [Customers] AS [c] -ORDER BY [c].[City]"); - } - - public override async Task OrderBy_GroupBy_SelectMany(bool async) - { - await base.OrderBy_GroupBy_SelectMany(async); - - AssertSql( - @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate] -FROM [Orders] AS [o] -ORDER BY [o].[CustomerID], [o].[OrderID]"); - } - - public override async Task OrderBy_GroupBy_SelectMany_shadow(bool async) - { - await base.OrderBy_GroupBy_SelectMany_shadow(async); - - AssertSql( - @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] -FROM [Employees] AS [e] -ORDER BY [e].[EmployeeID]"); - } - - public override async Task GroupBy_with_orderby_take_skip_distinct_followed_by_group_key_projection(bool async) - { - await base.GroupBy_with_orderby_take_skip_distinct_followed_by_group_key_projection(async); - - AssertSql( - ""); - } - - public override async Task GroupBy_Distinct(bool async) - { - await base.GroupBy_Distinct(async); - - AssertSql( - @"SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] -FROM [Orders] AS [o0] -ORDER BY [o0].[CustomerID]"); - } - public override async Task GroupBy_with_aggregate_through_navigation_property(bool async) { await base.GroupBy_with_aggregate_through_navigation_property(async); AssertSql( - @"SELECT [c].[OrderID], [c].[CustomerID], [c].[EmployeeID], [c].[OrderDate] -FROM [Orders] AS [c] -ORDER BY [c].[EmployeeID]", - // - @"SELECT [i.Customer0].[CustomerID], [i.Customer0].[Region] -FROM [Customers] AS [i.Customer0]", - // - @"SELECT [i.Customer0].[CustomerID], [i.Customer0].[Region] -FROM [Customers] AS [i.Customer0]"); + @"SELECT MAX([c].[Region]) AS [max] +FROM [Orders] AS [o] +LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] +GROUP BY [o].[EmployeeID]"); } public override async Task GroupBy_Shadow(bool async) @@ -1893,10 +1825,13 @@ public override async Task GroupBy_Shadow(bool async) await base.GroupBy_Shadow(async); AssertSql( - @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] + @"SELECT ( + SELECT TOP(1) [e0].[Title] + FROM [Employees] AS [e0] + WHERE (([e0].[Title] = N'Sales Representative') AND ([e0].[EmployeeID] = 1)) AND (([e].[Title] = [e0].[Title]) OR ([e].[Title] IS NULL AND [e0].[Title] IS NULL))) FROM [Employees] AS [e] WHERE ([e].[Title] = N'Sales Representative') AND ([e].[EmployeeID] = 1) -ORDER BY [e].[Title]"); +GROUP BY [e].[Title]"); } public override async Task GroupBy_Shadow2(bool async) @@ -1904,10 +1839,22 @@ public override async Task GroupBy_Shadow2(bool async) await base.GroupBy_Shadow2(async); AssertSql( - @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] -FROM [Employees] AS [e] -WHERE ([e].[Title] = N'Sales Representative') AND ([e].[EmployeeID] = 1) -ORDER BY [e].[Title]"); + @"SELECT [t0].[EmployeeID], [t0].[City], [t0].[Country], [t0].[FirstName], [t0].[ReportsTo], [t0].[Title] +FROM ( + SELECT [e].[Title] + FROM [Employees] AS [e] + WHERE ([e].[Title] = N'Sales Representative') AND ([e].[EmployeeID] = 1) + GROUP BY [e].[Title] +) AS [t] +LEFT JOIN ( + SELECT [t1].[EmployeeID], [t1].[City], [t1].[Country], [t1].[FirstName], [t1].[ReportsTo], [t1].[Title] + FROM ( + SELECT [e0].[EmployeeID], [e0].[City], [e0].[Country], [e0].[FirstName], [e0].[ReportsTo], [e0].[Title], ROW_NUMBER() OVER(PARTITION BY [e0].[Title] ORDER BY [e0].[Title]) AS [row] + FROM [Employees] AS [e0] + WHERE ([e0].[Title] = N'Sales Representative') AND ([e0].[EmployeeID] = 1) + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Title] = [t0].[Title]"); } public override async Task GroupBy_Shadow3(bool async) @@ -1915,10 +1862,13 @@ public override async Task GroupBy_Shadow3(bool async) await base.GroupBy_Shadow3(async); AssertSql( - @"SELECT [e].[EmployeeID], [e].[City], [e].[Country], [e].[FirstName], [e].[ReportsTo], [e].[Title] + @"SELECT ( + SELECT TOP(1) [e0].[Title] + FROM [Employees] AS [e0] + WHERE ([e0].[EmployeeID] = 1) AND ([e].[EmployeeID] = [e0].[EmployeeID])) FROM [Employees] AS [e] WHERE [e].[EmployeeID] = 1 -ORDER BY [e].[EmployeeID]"); +GROUP BY [e].[EmployeeID]"); } public override async Task Select_GroupBy_SelectMany(bool async) @@ -1972,7 +1922,13 @@ public override async Task GroupBy_group_Distinct_Select_Distinct_aggregate(bool await base.GroupBy_group_Distinct_Select_Distinct_aggregate(async); AssertSql( - @"SELECT [o].[CustomerID] AS [Key], MAX(DISTINCT ([o].[OrderDate])) AS [Max] + @"SELECT [o].[CustomerID] AS [Key], ( + SELECT DISTINCT MAX(DISTINCT ([t].[OrderDate])) + FROM ( + SELECT DISTINCT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate] + FROM [Orders] AS [o0] + WHERE ([o].[CustomerID] = [o0].[CustomerID]) OR ([o].[CustomerID] IS NULL AND [o0].[CustomerID] IS NULL) + ) AS [t]) AS [Max] FROM [Orders] AS [o] GROUP BY [o].[CustomerID]"); } @@ -2500,7 +2456,15 @@ public override async Task Complex_query_with_groupBy_in_subquery1(bool async) await base.Complex_query_with_groupBy_in_subquery1(async); AssertSql( - @""); + @"SELECT [c].[CustomerID], [t].[Sum], [t].[CustomerID] +FROM [Customers] AS [c] +OUTER APPLY ( + SELECT COALESCE(SUM([o].[OrderID]), 0) AS [Sum], [o].[CustomerID] + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID] + GROUP BY [o].[CustomerID] +) AS [t] +ORDER BY [c].[CustomerID], [t].[CustomerID]"); } public override async Task Complex_query_with_groupBy_in_subquery2(bool async) @@ -2508,7 +2472,15 @@ public override async Task Complex_query_with_groupBy_in_subquery2(bool async) await base.Complex_query_with_groupBy_in_subquery2(async); AssertSql( - @""); + @"SELECT [c].[CustomerID], [t].[Max], [t].[Sum], [t].[CustomerID] +FROM [Customers] AS [c] +OUTER APPLY ( + SELECT MAX(CAST(LEN([o].[CustomerID]) AS int)) AS [Max], COALESCE(SUM([o].[OrderID]), 0) AS [Sum], [o].[CustomerID] + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID] + GROUP BY [o].[CustomerID] +) AS [t] +ORDER BY [c].[CustomerID], [t].[CustomerID]"); } public override async Task Complex_query_with_groupBy_in_subquery3(bool async) @@ -2516,7 +2488,14 @@ public override async Task Complex_query_with_groupBy_in_subquery3(bool async) await base.Complex_query_with_groupBy_in_subquery3(async); AssertSql( - @""); + @"SELECT [c].[CustomerID], [t].[Max], [t].[Sum], [t].[CustomerID] +FROM [Customers] AS [c] +OUTER APPLY ( + SELECT MAX(CAST(LEN([o].[CustomerID]) AS int)) AS [Max], COALESCE(SUM([o].[OrderID]), 0) AS [Sum], [o].[CustomerID] + FROM [Orders] AS [o] + GROUP BY [o].[CustomerID] +) AS [t] +ORDER BY [c].[CustomerID], [t].[CustomerID]"); } public override async Task Group_by_with_projection_into_DTO(bool async) @@ -2594,6 +2573,35 @@ FROM [Orders] AS [o] GROUP BY [t].[Key]"); } + public override async Task AsEnumerable_in_subquery_for_GroupBy(bool async) + { + await base.AsEnumerable_in_subquery_for_GroupBy(async); + + AssertSql( + @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], [t2].[OrderID], [t2].[CustomerID], [t2].[EmployeeID], [t2].[OrderDate], [t2].[CustomerID0] +FROM [Customers] AS [c] +OUTER APPLY ( + SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t].[CustomerID] AS [CustomerID0] + FROM ( + SELECT [o].[CustomerID] + FROM [Orders] AS [o] + WHERE [o].[CustomerID] = [c].[CustomerID] + GROUP BY [o].[CustomerID] + ) AS [t] + LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate] + FROM ( + SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o0].[CustomerID] ORDER BY [o0].[OrderDate] DESC) AS [row] + FROM [Orders] AS [o0] + WHERE [o0].[CustomerID] = [c].[CustomerID] + ) AS [t1] + WHERE [t1].[row] <= 1 + ) AS [t0] ON [t].[CustomerID] = [t0].[CustomerID] +) AS [t2] +WHERE [c].[CustomerID] LIKE N'F%' +ORDER BY [c].[CustomerID], [t2].[CustomerID0], [t2].[CustomerID]"); + } + public override async Task GroupBy_scalar_aggregate_in_set_operation(bool async) { await base.GroupBy_scalar_aggregate_in_set_operation(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs index 4795dddf519..fbfe36215f8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindIncludeQuerySqlServerTest.cs @@ -1043,6 +1043,265 @@ ELSE 2 END, [c].[CustomerID], [o].[OrderID]"); } + public override async Task Include_collection_GroupBy_Select(bool async) + { + await base.Include_collection_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t].[OrderID], [o1].[OrderID], [o1].[ProductID], [o1].[Discount], [o1].[Quantity], [o1].[UnitPrice] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate] + FROM ( + SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o0].[OrderID] ORDER BY [o0].[OrderID]) AS [row] + FROM [Orders] AS [o0] + WHERE [o0].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +LEFT JOIN [Order Details] AS [o1] ON [t0].[OrderID] = [o1].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID], [o1].[OrderID], [o1].[ProductID]"); + } + + public override async Task Include_reference_GroupBy_Select(bool async) + { + await base.Include_reference_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[CustomerID0], [t0].[Address], [t0].[City], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Country], [t0].[Fax], [t0].[Phone], [t0].[PostalCode], [t0].[Region] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate], [t1].[CustomerID0], [t1].[Address], [t1].[City], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Country], [t1].[Fax], [t1].[Phone], [t1].[PostalCode], [t1].[Region] + FROM ( + SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate], [c].[CustomerID] AS [CustomerID0], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], ROW_NUMBER() OVER(PARTITION BY [o0].[OrderID] ORDER BY [o0].[OrderID]) AS [row] + FROM [Orders] AS [o0] + LEFT JOIN [Customers] AS [c] ON [o0].[CustomerID] = [c].[CustomerID] + WHERE [o0].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID]"); + } + + public override async Task Include_collection_Join_GroupBy_Select(bool async) + { + await base.Include_collection_Join_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t].[OrderID], [o3].[OrderID], [o3].[ProductID], [o3].[Discount], [o3].[Quantity], [o3].[UnitPrice] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + INNER JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate] + FROM ( + SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o1].[OrderID] ORDER BY [o1].[OrderID]) AS [row] + FROM [Orders] AS [o1] + INNER JOIN [Order Details] AS [o2] ON [o1].[OrderID] = [o2].[OrderID] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +LEFT JOIN [Order Details] AS [o3] ON [t0].[OrderID] = [o3].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID], [o3].[OrderID], [o3].[ProductID]"); + } + + public override async Task Include_reference_Join_GroupBy_Select(bool async) + { + await base.Include_reference_Join_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[CustomerID0], [t0].[Address], [t0].[City], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Country], [t0].[Fax], [t0].[Phone], [t0].[PostalCode], [t0].[Region] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + INNER JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate], [t1].[CustomerID0], [t1].[Address], [t1].[City], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Country], [t1].[Fax], [t1].[Phone], [t1].[PostalCode], [t1].[Region] + FROM ( + SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate], [c].[CustomerID] AS [CustomerID0], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], ROW_NUMBER() OVER(PARTITION BY [o1].[OrderID] ORDER BY [o1].[OrderID]) AS [row] + FROM [Orders] AS [o1] + INNER JOIN [Order Details] AS [o2] ON [o1].[OrderID] = [o2].[OrderID] + LEFT JOIN [Customers] AS [c] ON [o1].[CustomerID] = [c].[CustomerID] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID]"); + } + + public override async Task Join_Include_collection_GroupBy_Select(bool async) + { + await base.Join_Include_collection_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t].[OrderID], [o3].[OrderID], [o3].[ProductID], [o3].[Discount], [o3].[Quantity], [o3].[UnitPrice] +FROM ( + SELECT [o0].[OrderID] + FROM [Order Details] AS [o] + INNER JOIN [Orders] AS [o0] ON [o].[OrderID] = [o0].[OrderID] + WHERE [o].[OrderID] = 10248 + GROUP BY [o0].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate] + FROM ( + SELECT [o2].[OrderID], [o2].[CustomerID], [o2].[EmployeeID], [o2].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o2].[OrderID] ORDER BY [o2].[OrderID]) AS [row] + FROM [Order Details] AS [o1] + INNER JOIN [Orders] AS [o2] ON [o1].[OrderID] = [o2].[OrderID] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +LEFT JOIN [Order Details] AS [o3] ON [t0].[OrderID] = [o3].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID], [o3].[OrderID], [o3].[ProductID]"); + } + + public override async Task Join_Include_reference_GroupBy_Select(bool async) + { + await base.Join_Include_reference_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[CustomerID0], [t0].[Address], [t0].[City], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Country], [t0].[Fax], [t0].[Phone], [t0].[PostalCode], [t0].[Region] +FROM ( + SELECT [o0].[OrderID] + FROM [Order Details] AS [o] + INNER JOIN [Orders] AS [o0] ON [o].[OrderID] = [o0].[OrderID] + GROUP BY [o0].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate], [t1].[CustomerID0], [t1].[Address], [t1].[City], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Country], [t1].[Fax], [t1].[Phone], [t1].[PostalCode], [t1].[Region] + FROM ( + SELECT [o2].[OrderID], [o2].[CustomerID], [o2].[EmployeeID], [o2].[OrderDate], [c].[CustomerID] AS [CustomerID0], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], ROW_NUMBER() OVER(PARTITION BY [o2].[OrderID] ORDER BY [o2].[OrderID]) AS [row] + FROM [Order Details] AS [o1] + INNER JOIN [Orders] AS [o2] ON [o1].[OrderID] = [o2].[OrderID] + LEFT JOIN [Customers] AS [c] ON [o2].[CustomerID] = [c].[CustomerID] + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID]"); + } + + public override async Task Include_collection_SelectMany_GroupBy_Select(bool async) + { + await base.Include_collection_SelectMany_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t].[OrderID], [o3].[OrderID], [o3].[ProductID], [o3].[Discount], [o3].[Quantity], [o3].[UnitPrice] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + CROSS JOIN [Order Details] AS [o0] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate] + FROM ( + SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o1].[OrderID] ORDER BY [o1].[OrderID]) AS [row] + FROM [Orders] AS [o1] + CROSS JOIN [Order Details] AS [o2] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +LEFT JOIN [Order Details] AS [o3] ON [t0].[OrderID] = [o3].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID], [o3].[OrderID], [o3].[ProductID]"); + } + + public override async Task Include_reference_SelectMany_GroupBy_Select(bool async) + { + await base.Include_reference_SelectMany_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[CustomerID0], [t0].[Address], [t0].[City], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Country], [t0].[Fax], [t0].[Phone], [t0].[PostalCode], [t0].[Region] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + CROSS JOIN [Order Details] AS [o0] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate], [t1].[CustomerID0], [t1].[Address], [t1].[City], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Country], [t1].[Fax], [t1].[Phone], [t1].[PostalCode], [t1].[Region] + FROM ( + SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate], [c].[CustomerID] AS [CustomerID0], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], ROW_NUMBER() OVER(PARTITION BY [o1].[OrderID] ORDER BY [o1].[OrderID]) AS [row] + FROM [Orders] AS [o1] + CROSS JOIN [Order Details] AS [o2] + LEFT JOIN [Customers] AS [c] ON [o1].[CustomerID] = [c].[CustomerID] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID]"); + } + + public override async Task SelectMany_Include_collection_GroupBy_Select(bool async) + { + await base.SelectMany_Include_collection_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t].[OrderID], [o3].[OrderID], [o3].[ProductID], [o3].[Discount], [o3].[Quantity], [o3].[UnitPrice] +FROM ( + SELECT [o0].[OrderID] + FROM [Order Details] AS [o] + CROSS JOIN [Orders] AS [o0] + WHERE [o].[OrderID] = 10248 + GROUP BY [o0].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate] + FROM ( + SELECT [o2].[OrderID], [o2].[CustomerID], [o2].[EmployeeID], [o2].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o2].[OrderID] ORDER BY [o2].[OrderID]) AS [row] + FROM [Order Details] AS [o1] + CROSS JOIN [Orders] AS [o2] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +LEFT JOIN [Order Details] AS [o3] ON [t0].[OrderID] = [o3].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID], [o3].[OrderID], [o3].[ProductID]"); + } + + public override async Task SelectMany_Include_reference_GroupBy_Select(bool async) + { + await base.SelectMany_Include_reference_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[CustomerID0], [t0].[Address], [t0].[City], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Country], [t0].[Fax], [t0].[Phone], [t0].[PostalCode], [t0].[Region] +FROM ( + SELECT [o0].[OrderID] + FROM [Order Details] AS [o] + CROSS JOIN [Orders] AS [o0] + WHERE [o].[OrderID] = 10248 + GROUP BY [o0].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate], [t1].[CustomerID0], [t1].[Address], [t1].[City], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Country], [t1].[Fax], [t1].[Phone], [t1].[PostalCode], [t1].[Region] + FROM ( + SELECT [o2].[OrderID], [o2].[CustomerID], [o2].[EmployeeID], [o2].[OrderDate], [c].[CustomerID] AS [CustomerID0], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], ROW_NUMBER() OVER(PARTITION BY [o2].[OrderID] ORDER BY [o2].[OrderID]) AS [row] + FROM [Order Details] AS [o1] + CROSS JOIN [Orders] AS [o2] + LEFT JOIN [Customers] AS [c] ON [o2].[CustomerID] = [c].[CustomerID] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID]"); + } + public override async Task Include_reference_distinct_is_server_evaluated(bool async) { await base.Include_reference_distinct_is_server_evaluated(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSetOperationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSetOperationsQuerySqlServerTest.cs index 0db50d146cb..33fbd8bd7c2 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSetOperationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSetOperationsQuerySqlServerTest.cs @@ -386,14 +386,14 @@ public override async Task SubSelect_Union(bool async) AssertSql( @"SELECT [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], ( SELECT COUNT(*) - FROM [Orders] AS [o0] - WHERE [c].[CustomerID] = [o0].[CustomerID]) AS [Orders] + FROM [Orders] AS [o] + WHERE [c].[CustomerID] = [o].[CustomerID]) AS [Orders] FROM [Customers] AS [c] UNION SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region], ( SELECT COUNT(*) - FROM [Orders] AS [o] - WHERE [c0].[CustomerID] = [o].[CustomerID]) AS [Orders] + FROM [Orders] AS [o0] + WHERE [c0].[CustomerID] = [o0].[CustomerID]) AS [Orders] FROM [Customers] AS [c0]"); } @@ -425,99 +425,484 @@ SELECT NULL AS [c] FROM [Customers] AS [c0]"); } - public override async Task Union_over_different_projection_types(bool async, string leftType, string rightType) - { - await base.Union_over_different_projection_types(async, leftType, rightType); - - var leftSql = GenerateSql(leftType); - var rightSql = GenerateSql(rightType); - - switch (leftType) - { - case "Column": - leftSql = leftSql.Replace("{Alias}", ""); - break; - - case "Binary": - case "Constant": - case "Function": - case "ScalarSubquery": - case "Unary": - leftSql = leftSql.Replace("{Alias}", " AS [c]"); - break; - - default: - throw new ArgumentException("Unexpected type: " + leftType); - } - - switch (rightType) - { - case "Column": - rightSql = rightSql.Replace("{Alias}", leftType == "Column" ? "" : " AS [c]"); - break; - - case "Binary": - case "Constant": - case "Function": - case "ScalarSubquery": - case "Unary": - rightSql = rightSql.Replace("{Alias}", leftType == "Column" ? " AS [OrderID]" : " AS [c]"); - break; - default: - throw new ArgumentException("Unexpected type: " + rightType); - } - - // Fix up right-side SQL as table aliases shift - if (leftType == "ScalarSubquery") - { - if (rightType == "ScalarSubquery") - { - leftSql = leftSql.Replace("[o0]", "[o2]"); - rightSql = rightSql.Replace("[o0]", "[o1]").Replace("[o]", "[o0]"); - } - else - { - leftSql = leftSql.Replace("[o0]", "[o1]"); - rightSql = rightSql.Replace("[o]", "[o0]"); - } - } - else - { - rightSql = rightSql.Replace("[o0]", "[o1]").Replace("[o]", "[o0]"); - } - - AssertSql(leftSql + Environment.NewLine + "UNION" + Environment.NewLine + rightSql); - - static string GenerateSql(string expressionType) - { - switch (expressionType) - { - case "Column": - return @"SELECT [o].[OrderID]{Alias} -FROM [Orders] AS [o]"; - case "Function": - return @"SELECT COUNT(*){Alias} -FROM [Orders] AS [o] -GROUP BY [o].[OrderID]"; - case "Constant": - return @"SELECT 8{Alias} -FROM [Orders] AS [o]"; - case "Unary": - return @"SELECT -[o].[OrderID]{Alias} -FROM [Orders] AS [o]"; - case "Binary": - return @"SELECT [o].[OrderID] + 1{Alias} -FROM [Orders] AS [o]"; - case "ScalarSubquery": - return @"SELECT ( + public override async Task Union_over_column_column(bool async) + { + await base.Union_over_column_column(async); + + AssertSql( + @"SELECT [o].[OrderID] +FROM [Orders] AS [o] +UNION +SELECT [o0].[OrderID] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_column_function(bool async) + { + await base.Union_over_column_function(async); + + AssertSql( + @"SELECT [o].[OrderID] +FROM [Orders] AS [o] +UNION +SELECT COUNT(*) AS [OrderID] +FROM [Orders] AS [o0] +GROUP BY [o0].[OrderID]"); + } + + public override async Task Union_over_column_constant(bool async) + { + await base.Union_over_column_constant(async); + + AssertSql( + @"SELECT [o].[OrderID] +FROM [Orders] AS [o] +UNION +SELECT 8 AS [OrderID] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_column_unary(bool async) + { + await base.Union_over_column_unary(async); + + AssertSql( + @"SELECT [o].[OrderID] +FROM [Orders] AS [o] +UNION +SELECT -[o0].[OrderID] AS [OrderID] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_column_binary(bool async) + { + await base.Union_over_column_binary(async); + + AssertSql( + @"SELECT [o].[OrderID] +FROM [Orders] AS [o] +UNION +SELECT [o0].[OrderID] + 1 AS [OrderID] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_column_scalarsubquery(bool async) + { + await base.Union_over_column_scalarsubquery(async); + + AssertSql( + @"SELECT [o].[OrderID] +FROM [Orders] AS [o] +UNION +SELECT ( + SELECT COUNT(*) + FROM [Order Details] AS [o1] + WHERE [o0].[OrderID] = [o1].[OrderID]) AS [OrderID] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_function_column(bool async) + { + await base.Union_over_function_column(async); + + AssertSql( + @"SELECT COUNT(*) AS [c] +FROM [Orders] AS [o] +GROUP BY [o].[OrderID] +UNION +SELECT [o0].[OrderID] AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_function_function(bool async) + { + await base.Union_over_function_function(async); + + AssertSql( + @"SELECT COUNT(*) AS [c] +FROM [Orders] AS [o] +GROUP BY [o].[OrderID] +UNION +SELECT COUNT(*) AS [c] +FROM [Orders] AS [o0] +GROUP BY [o0].[OrderID]"); + } + + public override async Task Union_over_function_constant(bool async) + { + await base.Union_over_function_constant(async); + + AssertSql( + @"SELECT COUNT(*) AS [c] +FROM [Orders] AS [o] +GROUP BY [o].[OrderID] +UNION +SELECT 8 AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_function_unary(bool async) + { + await base.Union_over_function_unary(async); + + AssertSql( + @"SELECT COUNT(*) AS [c] +FROM [Orders] AS [o] +GROUP BY [o].[OrderID] +UNION +SELECT -[o0].[OrderID] AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_function_binary(bool async) + { + await base.Union_over_function_binary(async); + + AssertSql( + @"SELECT COUNT(*) AS [c] +FROM [Orders] AS [o] +GROUP BY [o].[OrderID] +UNION +SELECT [o0].[OrderID] + 1 AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_function_scalarsubquery(bool async) + { + await base.Union_over_function_scalarsubquery(async); + + AssertSql( + @"SELECT COUNT(*) AS [c] +FROM [Orders] AS [o] +GROUP BY [o].[OrderID] +UNION +SELECT ( + SELECT COUNT(*) + FROM [Order Details] AS [o1] + WHERE [o0].[OrderID] = [o1].[OrderID]) AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_constant_column(bool async) + { + await base.Union_over_constant_column(async); + + AssertSql( + @"SELECT 8 AS [c] +FROM [Orders] AS [o] +UNION +SELECT [o0].[OrderID] AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_constant_function(bool async) + { + await base.Union_over_constant_function(async); + + AssertSql( + @"SELECT 8 AS [c] +FROM [Orders] AS [o] +UNION +SELECT COUNT(*) AS [c] +FROM [Orders] AS [o0] +GROUP BY [o0].[OrderID]"); + } + + public override async Task Union_over_constant_constant(bool async) + { + await base.Union_over_constant_constant(async); + + AssertSql( + @"SELECT 8 AS [c] +FROM [Orders] AS [o] +UNION +SELECT 8 AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_constant_unary(bool async) + { + await base.Union_over_constant_unary(async); + + AssertSql( + @"SELECT 8 AS [c] +FROM [Orders] AS [o] +UNION +SELECT -[o0].[OrderID] AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_constant_binary(bool async) + { + await base.Union_over_constant_binary(async); + + AssertSql( + @"SELECT 8 AS [c] +FROM [Orders] AS [o] +UNION +SELECT [o0].[OrderID] + 1 AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_constant_scalarsubquery(bool async) + { + await base.Union_over_constant_scalarsubquery(async); + + AssertSql( + @"SELECT 8 AS [c] +FROM [Orders] AS [o] +UNION +SELECT ( + SELECT COUNT(*) + FROM [Order Details] AS [o1] + WHERE [o0].[OrderID] = [o1].[OrderID]) AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_unary_column(bool async) + { + await base.Union_over_unary_column(async); + + AssertSql( + @"SELECT -[o].[OrderID] AS [c] +FROM [Orders] AS [o] +UNION +SELECT [o0].[OrderID] AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_unary_function(bool async) + { + await base.Union_over_unary_function(async); + + AssertSql( + @"SELECT -[o].[OrderID] AS [c] +FROM [Orders] AS [o] +UNION +SELECT COUNT(*) AS [c] +FROM [Orders] AS [o0] +GROUP BY [o0].[OrderID]"); + } + + public override async Task Union_over_unary_constant(bool async) + { + await base.Union_over_unary_constant(async); + + AssertSql( + @"SELECT -[o].[OrderID] AS [c] +FROM [Orders] AS [o] +UNION +SELECT 8 AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_unary_unary(bool async) + { + await base.Union_over_unary_unary(async); + + AssertSql( + @"SELECT -[o].[OrderID] AS [c] +FROM [Orders] AS [o] +UNION +SELECT -[o0].[OrderID] AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_unary_binary(bool async) + { + await base.Union_over_unary_binary(async); + + AssertSql( + @"SELECT -[o].[OrderID] AS [c] +FROM [Orders] AS [o] +UNION +SELECT [o0].[OrderID] + 1 AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_unary_scalarsubquery(bool async) + { + await base.Union_over_unary_scalarsubquery(async); + + AssertSql( + @"SELECT -[o].[OrderID] AS [c] +FROM [Orders] AS [o] +UNION +SELECT ( + SELECT COUNT(*) + FROM [Order Details] AS [o1] + WHERE [o0].[OrderID] = [o1].[OrderID]) AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_binary_column(bool async) + { + await base.Union_over_binary_column(async); + + AssertSql( + @"SELECT [o].[OrderID] + 1 AS [c] +FROM [Orders] AS [o] +UNION +SELECT [o0].[OrderID] AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_binary_function(bool async) + { + await base.Union_over_binary_function(async); + + AssertSql( + @"SELECT [o].[OrderID] + 1 AS [c] +FROM [Orders] AS [o] +UNION +SELECT COUNT(*) AS [c] +FROM [Orders] AS [o0] +GROUP BY [o0].[OrderID]"); + } + + public override async Task Union_over_binary_constant(bool async) + { + await base.Union_over_binary_constant(async); + + AssertSql( + @"SELECT [o].[OrderID] + 1 AS [c] +FROM [Orders] AS [o] +UNION +SELECT 8 AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_binary_unary(bool async) + { + await base.Union_over_binary_unary(async); + + AssertSql( + @"SELECT [o].[OrderID] + 1 AS [c] +FROM [Orders] AS [o] +UNION +SELECT -[o0].[OrderID] AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_binary_binary(bool async) + { + await base.Union_over_binary_binary(async); + + AssertSql( + @"SELECT [o].[OrderID] + 1 AS [c] +FROM [Orders] AS [o] +UNION +SELECT [o0].[OrderID] + 1 AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_binary_scalarsubquery(bool async) + { + await base.Union_over_binary_scalarsubquery(async); + + AssertSql( + @"SELECT [o].[OrderID] + 1 AS [c] +FROM [Orders] AS [o] +UNION +SELECT ( + SELECT COUNT(*) + FROM [Order Details] AS [o1] + WHERE [o0].[OrderID] = [o1].[OrderID]) AS [c] +FROM [Orders] AS [o0]"); + } + + public override async Task Union_over_scalarsubquery_column(bool async) + { + await base.Union_over_scalarsubquery_column(async); + + AssertSql( + @"SELECT ( SELECT COUNT(*) FROM [Order Details] AS [o0] - WHERE [o].[OrderID] = [o0].[OrderID]){Alias} -FROM [Orders] AS [o]"; - default: - throw new ArgumentException("Unexpected type: " + expressionType); - } - } + WHERE [o].[OrderID] = [o0].[OrderID]) AS [c] +FROM [Orders] AS [o] +UNION +SELECT [o1].[OrderID] AS [c] +FROM [Orders] AS [o1]"); + } + + public override async Task Union_over_scalarsubquery_function(bool async) + { + await base.Union_over_scalarsubquery_function(async); + + AssertSql( + @"SELECT ( + SELECT COUNT(*) + FROM [Order Details] AS [o0] + WHERE [o].[OrderID] = [o0].[OrderID]) AS [c] +FROM [Orders] AS [o] +UNION +SELECT COUNT(*) AS [c] +FROM [Orders] AS [o1] +GROUP BY [o1].[OrderID]"); + } + + public override async Task Union_over_scalarsubquery_constant(bool async) + { + await base.Union_over_scalarsubquery_constant(async); + + AssertSql( + @"SELECT ( + SELECT COUNT(*) + FROM [Order Details] AS [o0] + WHERE [o].[OrderID] = [o0].[OrderID]) AS [c] +FROM [Orders] AS [o] +UNION +SELECT 8 AS [c] +FROM [Orders] AS [o1]"); + } + + public override async Task Union_over_scalarsubquery_unary(bool async) + { + await base.Union_over_scalarsubquery_unary(async); + + AssertSql( + @"SELECT ( + SELECT COUNT(*) + FROM [Order Details] AS [o0] + WHERE [o].[OrderID] = [o0].[OrderID]) AS [c] +FROM [Orders] AS [o] +UNION +SELECT -[o1].[OrderID] AS [c] +FROM [Orders] AS [o1]"); + } + + public override async Task Union_over_scalarsubquery_binary(bool async) + { + await base.Union_over_scalarsubquery_binary(async); + + AssertSql( + @"SELECT ( + SELECT COUNT(*) + FROM [Order Details] AS [o0] + WHERE [o].[OrderID] = [o0].[OrderID]) AS [c] +FROM [Orders] AS [o] +UNION +SELECT [o1].[OrderID] + 1 AS [c] +FROM [Orders] AS [o1]"); + } + + public override async Task Union_over_scalarsubquery_scalarsubquery(bool async) + { + await base.Union_over_scalarsubquery_scalarsubquery(async); + + AssertSql( + @"SELECT ( + SELECT COUNT(*) + FROM [Order Details] AS [o0] + WHERE [o].[OrderID] = [o0].[OrderID]) AS [c] +FROM [Orders] AS [o] +UNION +SELECT ( + SELECT COUNT(*) + FROM [Order Details] AS [o2] + WHERE [o1].[OrderID] = [o2].[OrderID]) AS [c] +FROM [Orders] AS [o1]"); } public override async Task OrderBy_Take_Union(bool async) @@ -534,12 +919,12 @@ FROM [Customers] AS [c] ORDER BY [c].[ContactName] ) AS [t] UNION -SELECT [t0].[CustomerID], [t0].[Address], [t0].[City], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Country], [t0].[Fax], [t0].[Phone], [t0].[PostalCode], [t0].[Region] +SELECT [t1].[CustomerID], [t1].[Address], [t1].[City], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Country], [t1].[Fax], [t1].[Phone], [t1].[PostalCode], [t1].[Region] FROM ( SELECT TOP(@__p_0) [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region] FROM [Customers] AS [c0] ORDER BY [c0].[ContactName] -) AS [t0]"); +) AS [t1]"); } public override async Task Collection_projection_after_set_operation(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs index 96cac71538c..15f238a0fc6 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindSplitIncludeQuerySqlServerTest.cs @@ -1405,6 +1405,363 @@ ELSE 2 END, [c].[CustomerID]"); } + public override async Task Include_collection_GroupBy_Select(bool async) + { + await base.Include_collection_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t].[OrderID] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate] + FROM ( + SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o0].[OrderID] ORDER BY [o0].[OrderID]) AS [row] + FROM [Orders] AS [o0] + WHERE [o0].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID]", + // + @"SELECT [o1].[OrderID], [o1].[ProductID], [o1].[Discount], [o1].[Quantity], [o1].[UnitPrice], [t].[OrderID], [t0].[OrderID] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID] + FROM ( + SELECT [o0].[OrderID], ROW_NUMBER() OVER(PARTITION BY [o0].[OrderID] ORDER BY [o0].[OrderID]) AS [row] + FROM [Orders] AS [o0] + WHERE [o0].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +INNER JOIN [Order Details] AS [o1] ON [t0].[OrderID] = [o1].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID]"); + } + + public override async Task Include_reference_GroupBy_Select(bool async) + { + await base.Include_reference_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[CustomerID0], [t0].[Address], [t0].[City], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Country], [t0].[Fax], [t0].[Phone], [t0].[PostalCode], [t0].[Region] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate], [t1].[CustomerID0], [t1].[Address], [t1].[City], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Country], [t1].[Fax], [t1].[Phone], [t1].[PostalCode], [t1].[Region] + FROM ( + SELECT [o0].[OrderID], [o0].[CustomerID], [o0].[EmployeeID], [o0].[OrderDate], [c].[CustomerID] AS [CustomerID0], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], ROW_NUMBER() OVER(PARTITION BY [o0].[OrderID] ORDER BY [o0].[OrderID]) AS [row] + FROM [Orders] AS [o0] + LEFT JOIN [Customers] AS [c] ON [o0].[CustomerID] = [c].[CustomerID] + WHERE [o0].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID]"); + } + + public override async Task Include_collection_Join_GroupBy_Select(bool async) + { + await base.Include_collection_Join_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t].[OrderID] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + INNER JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate] + FROM ( + SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o1].[OrderID] ORDER BY [o1].[OrderID]) AS [row] + FROM [Orders] AS [o1] + INNER JOIN [Order Details] AS [o2] ON [o1].[OrderID] = [o2].[OrderID] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID]", + // + @"SELECT [o3].[OrderID], [o3].[ProductID], [o3].[Discount], [o3].[Quantity], [o3].[UnitPrice], [t].[OrderID], [t0].[OrderID] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + INNER JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID] + FROM ( + SELECT [o1].[OrderID], ROW_NUMBER() OVER(PARTITION BY [o1].[OrderID] ORDER BY [o1].[OrderID]) AS [row] + FROM [Orders] AS [o1] + INNER JOIN [Order Details] AS [o2] ON [o1].[OrderID] = [o2].[OrderID] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +INNER JOIN [Order Details] AS [o3] ON [t0].[OrderID] = [o3].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID]"); + } + + public override async Task Include_reference_Join_GroupBy_Select(bool async) + { + await base.Include_reference_Join_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[CustomerID0], [t0].[Address], [t0].[City], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Country], [t0].[Fax], [t0].[Phone], [t0].[PostalCode], [t0].[Region] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + INNER JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate], [t1].[CustomerID0], [t1].[Address], [t1].[City], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Country], [t1].[Fax], [t1].[Phone], [t1].[PostalCode], [t1].[Region] + FROM ( + SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate], [c].[CustomerID] AS [CustomerID0], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], ROW_NUMBER() OVER(PARTITION BY [o1].[OrderID] ORDER BY [o1].[OrderID]) AS [row] + FROM [Orders] AS [o1] + INNER JOIN [Order Details] AS [o2] ON [o1].[OrderID] = [o2].[OrderID] + LEFT JOIN [Customers] AS [c] ON [o1].[CustomerID] = [c].[CustomerID] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID]"); + } + + public override async Task Join_Include_collection_GroupBy_Select(bool async) + { + await base.Join_Include_collection_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t].[OrderID] +FROM ( + SELECT [o0].[OrderID] + FROM [Order Details] AS [o] + INNER JOIN [Orders] AS [o0] ON [o].[OrderID] = [o0].[OrderID] + WHERE [o].[OrderID] = 10248 + GROUP BY [o0].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate] + FROM ( + SELECT [o2].[OrderID], [o2].[CustomerID], [o2].[EmployeeID], [o2].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o2].[OrderID] ORDER BY [o2].[OrderID]) AS [row] + FROM [Order Details] AS [o1] + INNER JOIN [Orders] AS [o2] ON [o1].[OrderID] = [o2].[OrderID] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID]", + // + @"SELECT [o3].[OrderID], [o3].[ProductID], [o3].[Discount], [o3].[Quantity], [o3].[UnitPrice], [t].[OrderID], [t0].[OrderID] +FROM ( + SELECT [o0].[OrderID] + FROM [Order Details] AS [o] + INNER JOIN [Orders] AS [o0] ON [o].[OrderID] = [o0].[OrderID] + WHERE [o].[OrderID] = 10248 + GROUP BY [o0].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID] + FROM ( + SELECT [o2].[OrderID], ROW_NUMBER() OVER(PARTITION BY [o2].[OrderID] ORDER BY [o2].[OrderID]) AS [row] + FROM [Order Details] AS [o1] + INNER JOIN [Orders] AS [o2] ON [o1].[OrderID] = [o2].[OrderID] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +INNER JOIN [Order Details] AS [o3] ON [t0].[OrderID] = [o3].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID]"); + } + + public override async Task Join_Include_reference_GroupBy_Select(bool async) + { + await base.Join_Include_reference_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[CustomerID0], [t0].[Address], [t0].[City], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Country], [t0].[Fax], [t0].[Phone], [t0].[PostalCode], [t0].[Region] +FROM ( + SELECT [o0].[OrderID] + FROM [Order Details] AS [o] + INNER JOIN [Orders] AS [o0] ON [o].[OrderID] = [o0].[OrderID] + GROUP BY [o0].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate], [t1].[CustomerID0], [t1].[Address], [t1].[City], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Country], [t1].[Fax], [t1].[Phone], [t1].[PostalCode], [t1].[Region] + FROM ( + SELECT [o2].[OrderID], [o2].[CustomerID], [o2].[EmployeeID], [o2].[OrderDate], [c].[CustomerID] AS [CustomerID0], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], ROW_NUMBER() OVER(PARTITION BY [o2].[OrderID] ORDER BY [o2].[OrderID]) AS [row] + FROM [Order Details] AS [o1] + INNER JOIN [Orders] AS [o2] ON [o1].[OrderID] = [o2].[OrderID] + LEFT JOIN [Customers] AS [c] ON [o2].[CustomerID] = [c].[CustomerID] + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID]"); + } + + public override async Task Include_collection_SelectMany_GroupBy_Select(bool async) + { + await base.Include_collection_SelectMany_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t].[OrderID] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + CROSS JOIN [Order Details] AS [o0] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate] + FROM ( + SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o1].[OrderID] ORDER BY [o1].[OrderID]) AS [row] + FROM [Orders] AS [o1] + CROSS JOIN [Order Details] AS [o2] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID]", + // + @"SELECT [o3].[OrderID], [o3].[ProductID], [o3].[Discount], [o3].[Quantity], [o3].[UnitPrice], [t].[OrderID], [t0].[OrderID] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + CROSS JOIN [Order Details] AS [o0] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID] + FROM ( + SELECT [o1].[OrderID], ROW_NUMBER() OVER(PARTITION BY [o1].[OrderID] ORDER BY [o1].[OrderID]) AS [row] + FROM [Orders] AS [o1] + CROSS JOIN [Order Details] AS [o2] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +INNER JOIN [Order Details] AS [o3] ON [t0].[OrderID] = [o3].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID]"); + } + + public override async Task Include_reference_SelectMany_GroupBy_Select(bool async) + { + await base.Include_reference_SelectMany_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[CustomerID0], [t0].[Address], [t0].[City], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Country], [t0].[Fax], [t0].[Phone], [t0].[PostalCode], [t0].[Region] +FROM ( + SELECT [o].[OrderID] + FROM [Orders] AS [o] + CROSS JOIN [Order Details] AS [o0] + WHERE [o].[OrderID] = 10248 + GROUP BY [o].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate], [t1].[CustomerID0], [t1].[Address], [t1].[City], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Country], [t1].[Fax], [t1].[Phone], [t1].[PostalCode], [t1].[Region] + FROM ( + SELECT [o1].[OrderID], [o1].[CustomerID], [o1].[EmployeeID], [o1].[OrderDate], [c].[CustomerID] AS [CustomerID0], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], ROW_NUMBER() OVER(PARTITION BY [o1].[OrderID] ORDER BY [o1].[OrderID]) AS [row] + FROM [Orders] AS [o1] + CROSS JOIN [Order Details] AS [o2] + LEFT JOIN [Customers] AS [c] ON [o1].[CustomerID] = [c].[CustomerID] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID]"); + } + + public override async Task SelectMany_Include_collection_GroupBy_Select(bool async) + { + await base.SelectMany_Include_collection_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t].[OrderID] +FROM ( + SELECT [o0].[OrderID] + FROM [Order Details] AS [o] + CROSS JOIN [Orders] AS [o0] + WHERE [o].[OrderID] = 10248 + GROUP BY [o0].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate] + FROM ( + SELECT [o2].[OrderID], [o2].[CustomerID], [o2].[EmployeeID], [o2].[OrderDate], ROW_NUMBER() OVER(PARTITION BY [o2].[OrderID] ORDER BY [o2].[OrderID]) AS [row] + FROM [Order Details] AS [o1] + CROSS JOIN [Orders] AS [o2] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID]", + // + @"SELECT [o3].[OrderID], [o3].[ProductID], [o3].[Discount], [o3].[Quantity], [o3].[UnitPrice], [t].[OrderID], [t0].[OrderID] +FROM ( + SELECT [o0].[OrderID] + FROM [Order Details] AS [o] + CROSS JOIN [Orders] AS [o0] + WHERE [o].[OrderID] = 10248 + GROUP BY [o0].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID] + FROM ( + SELECT [o2].[OrderID], ROW_NUMBER() OVER(PARTITION BY [o2].[OrderID] ORDER BY [o2].[OrderID]) AS [row] + FROM [Order Details] AS [o1] + CROSS JOIN [Orders] AS [o2] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID] +INNER JOIN [Order Details] AS [o3] ON [t0].[OrderID] = [o3].[OrderID] +ORDER BY [t].[OrderID], [t0].[OrderID]"); + } + + public override async Task SelectMany_Include_reference_GroupBy_Select(bool async) + { + await base.SelectMany_Include_reference_GroupBy_Select(async); + + AssertSql( + @"SELECT [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate], [t0].[CustomerID0], [t0].[Address], [t0].[City], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Country], [t0].[Fax], [t0].[Phone], [t0].[PostalCode], [t0].[Region] +FROM ( + SELECT [o0].[OrderID] + FROM [Order Details] AS [o] + CROSS JOIN [Orders] AS [o0] + WHERE [o].[OrderID] = 10248 + GROUP BY [o0].[OrderID] +) AS [t] +LEFT JOIN ( + SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate], [t1].[CustomerID0], [t1].[Address], [t1].[City], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Country], [t1].[Fax], [t1].[Phone], [t1].[PostalCode], [t1].[Region] + FROM ( + SELECT [o2].[OrderID], [o2].[CustomerID], [o2].[EmployeeID], [o2].[OrderDate], [c].[CustomerID] AS [CustomerID0], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region], ROW_NUMBER() OVER(PARTITION BY [o2].[OrderID] ORDER BY [o2].[OrderID]) AS [row] + FROM [Order Details] AS [o1] + CROSS JOIN [Orders] AS [o2] + LEFT JOIN [Customers] AS [c] ON [o2].[CustomerID] = [c].[CustomerID] + WHERE [o1].[OrderID] = 10248 + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[OrderID] = [t0].[OrderID]"); + } + public override async Task Include_reference_distinct_is_server_evaluated(bool async) { await base.Include_reference_distinct_is_server_evaluated(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs index 8fbd40a241c..68286db5f89 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs @@ -1075,13 +1075,13 @@ public override async Task GroupBy_with_multiple_aggregates_on_owned_navigation_ await base.GroupBy_with_multiple_aggregates_on_owned_navigation_properties(async); AssertSql( - @"SELECT AVG(CAST([t].[Id] AS float)) AS [p1], COALESCE(SUM([t].[Id]), 0) AS [p2], MAX(CAST(LEN([t].[Name]) AS int)) AS [p3] + @"SELECT AVG(CAST([s].[Id] AS float)) AS [p1], COALESCE(SUM([s].[Id]), 0) AS [p2], MAX(CAST(LEN([s].[Name]) AS int)) AS [p3] FROM ( - SELECT [s].[Id], [s].[Name], 1 AS [Key] + SELECT 1 AS [Key], [o].[PersonAddress_Country_PlanetId] FROM [OwnedPerson] AS [o] - LEFT JOIN [Planet] AS [p] ON [o].[PersonAddress_Country_PlanetId] = [p].[Id] - LEFT JOIN [Star] AS [s] ON [p].[StarId] = [s].[Id] ) AS [t] +LEFT JOIN [Planet] AS [p] ON [t].[PersonAddress_Country_PlanetId] = [p].[Id] +LEFT JOIN [Star] AS [s] ON [p].[StarId] = [s].[Id] GROUP BY [t].[Key]"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index 34aff4a0ac5..32e5e606517 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -6509,11 +6509,25 @@ public override async Task Group_by_with_include_with_entity_in_result_selector( await base.Group_by_with_include_with_entity_in_result_selector(async); AssertSql( - @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], [g.CityOfBirth].[Name], [g.CityOfBirth].[Location], [g.CityOfBirth].[Nation] -FROM [Gears] AS [g] -INNER JOIN [Cities] AS [g.CityOfBirth] ON [g].[CityOfBirthName] = [g.CityOfBirth].[Name] -WHERE [g].[Discriminator] IN (N'Officer', N'Gear') -ORDER BY [g].[Rank]"); + @"SELECT [t].[Rank], [t].[c], [t0].[Nickname], [t0].[SquadId], [t0].[AssignedCityName], [t0].[CityOfBirthName], [t0].[FullName], [t0].[HasSoulPatch], [t0].[LeaderNickname], [t0].[LeaderSquadId], [t0].[Rank], [t0].[Discriminator], [t0].[Name], [t0].[Location], [t0].[Nation] +FROM ( + SELECT [g].[Rank], COUNT(*) AS [c] + FROM [Gears] AS [g] + GROUP BY [g].[Rank] +) AS [t] +LEFT JOIN ( + SELECT [t1].[Nickname], [t1].[SquadId], [t1].[AssignedCityName], [t1].[CityOfBirthName], [t1].[FullName], [t1].[HasSoulPatch], [t1].[LeaderNickname], [t1].[LeaderSquadId], [t1].[Rank], [t1].[Discriminator], [t1].[Name], [t1].[Location], [t1].[Nation] + FROM ( + SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank], CASE + WHEN [o0].[Nickname] IS NOT NULL THEN N'Officer' + END AS [Discriminator], [c].[Name], [c].[Location], [c].[Nation], ROW_NUMBER() OVER(PARTITION BY [g0].[Rank] ORDER BY [g0].[Nickname]) AS [row] + FROM [Gears] AS [g0] + LEFT JOIN [Officers] AS [o0] ON ([g0].[Nickname] = [o0].[Nickname]) AND ([g0].[SquadId] = [o0].[SquadId]) + INNER JOIN [Cities] AS [c] ON [g0].[CityOfBirthName] = [c].[Name] + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Rank] = [t0].[Rank] +ORDER BY [t].[Rank]"); } public override async Task GroupBy_Property_Include_Select_Max(bool async) @@ -6531,10 +6545,25 @@ public override async Task Include_with_group_by_and_FirstOrDefault_gets_properl await base.Include_with_group_by_and_FirstOrDefault_gets_properly_applied(async); AssertSql( - @"SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], [g.CityOfBirth].[Name], [g.CityOfBirth].[Location], [g.CityOfBirth].[Nation] -FROM [Gears] AS [g] -INNER JOIN [Cities] AS [g.CityOfBirth] ON [g].[CityOfBirthName] = [g.CityOfBirth].[Name] -ORDER BY [g].[Rank]"); + @"SELECT [t0].[Nickname], [t0].[SquadId], [t0].[AssignedCityName], [t0].[CityOfBirthName], [t0].[FullName], [t0].[HasSoulPatch], [t0].[LeaderNickname], [t0].[LeaderSquadId], [t0].[Rank], [t0].[Discriminator], [t0].[Name], [t0].[Location], [t0].[Nation] +FROM ( + SELECT [g].[Rank] + FROM [Gears] AS [g] + GROUP BY [g].[Rank] +) AS [t] +LEFT JOIN ( + SELECT [t1].[Nickname], [t1].[SquadId], [t1].[AssignedCityName], [t1].[CityOfBirthName], [t1].[FullName], [t1].[HasSoulPatch], [t1].[LeaderNickname], [t1].[LeaderSquadId], [t1].[Rank], [t1].[Discriminator], [t1].[Name], [t1].[Location], [t1].[Nation] + FROM ( + SELECT [g0].[Nickname], [g0].[SquadId], [g0].[AssignedCityName], [g0].[CityOfBirthName], [g0].[FullName], [g0].[HasSoulPatch], [g0].[LeaderNickname], [g0].[LeaderSquadId], [g0].[Rank], CASE + WHEN [o0].[Nickname] IS NOT NULL THEN N'Officer' + END AS [Discriminator], [c].[Name], [c].[Location], [c].[Nation], ROW_NUMBER() OVER(PARTITION BY [g0].[Rank] ORDER BY [g0].[Rank], [c].[Name]) AS [row] + FROM [Gears] AS [g0] + LEFT JOIN [Officers] AS [o0] ON ([g0].[Nickname] = [o0].[Nickname]) AND ([g0].[SquadId] = [o0].[SquadId]) + INNER JOIN [Cities] AS [c] ON [g0].[CityOfBirthName] = [c].[Name] + WHERE [g0].[HasSoulPatch] = CAST(1 AS bit) + ) AS [t1] + WHERE [t1].[row] <= 1 +) AS [t0] ON [t].[Rank] = [t0].[Rank]"); } public override async Task Include_collection_with_Cast_to_base(bool async) @@ -7421,16 +7450,16 @@ public override async Task Complex_GroupBy_after_set_operator(bool async) FROM ( SELECT [c].[Name], ( SELECT COUNT(*) - FROM [Weapons] AS [w0] - WHERE [g].[FullName] = [w0].[OwnerFullName]) AS [Count] + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName]) AS [Count] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) LEFT JOIN [Cities] AS [c] ON [g].[AssignedCityName] = [c].[Name] UNION ALL SELECT [c0].[Name], ( SELECT COUNT(*) - FROM [Weapons] AS [w] - WHERE [g0].[FullName] = [w].[OwnerFullName]) AS [Count] + FROM [Weapons] AS [w0] + WHERE [g0].[FullName] = [w0].[OwnerFullName]) AS [Count] FROM [Gears] AS [g0] LEFT JOIN [Officers] AS [o0] ON ([g0].[Nickname] = [o0].[Nickname]) AND ([g0].[SquadId] = [o0].[SquadId]) INNER JOIN [Cities] AS [c0] ON [g0].[CityOfBirthName] = [c0].[Name] @@ -7447,16 +7476,16 @@ public override async Task Complex_GroupBy_after_set_operator_using_result_selec FROM ( SELECT [c].[Name], ( SELECT COUNT(*) - FROM [Weapons] AS [w0] - WHERE [g].[FullName] = [w0].[OwnerFullName]) AS [Count] + FROM [Weapons] AS [w] + WHERE [g].[FullName] = [w].[OwnerFullName]) AS [Count] FROM [Gears] AS [g] LEFT JOIN [Officers] AS [o] ON ([g].[Nickname] = [o].[Nickname]) AND ([g].[SquadId] = [o].[SquadId]) LEFT JOIN [Cities] AS [c] ON [g].[AssignedCityName] = [c].[Name] UNION ALL SELECT [c0].[Name], ( SELECT COUNT(*) - FROM [Weapons] AS [w] - WHERE [g0].[FullName] = [w].[OwnerFullName]) AS [Count] + FROM [Weapons] AS [w0] + WHERE [g0].[FullName] = [w0].[OwnerFullName]) AS [Count] FROM [Gears] AS [g0] LEFT JOIN [Officers] AS [o0] ON ([g0].[Nickname] = [o0].[Nickname]) AND ([g0].[SquadId] = [o0].[SquadId]) INNER JOIN [Cities] AS [c0] ON [g0].[CityOfBirthName] = [c0].[Name] @@ -7515,8 +7544,6 @@ public override async Task Group_by_over_projection_with_multiple_properties_acc @"SELECT [c].[Name] FROM [Gears] AS [g] INNER JOIN [Cities] AS [c] ON [g].[CityOfBirthName] = [c].[Name] -LEFT JOIN [Cities] AS [c0] ON [g].[AssignedCityName] = [c0].[Name] -INNER JOIN [Squads] AS [s] ON [g].[SquadId] = [s].[Id] GROUP BY [c].[Name]"); } diff --git a/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs index 27a575063c6..3df55f93984 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TableSplittingSqlServerTest.cs @@ -125,15 +125,15 @@ WHERE [v].[Capacity] IS NOT NULL AND [v].[FuelTank_Discriminator] IS NOT NULL SELECT [v1].[Name], [v1].[Capacity], [v1].[FuelTank_Discriminator], [v1].[FuelType], [v1].[GrainGeometry] FROM [Vehicles] AS [v1] INNER JOIN ( - SELECT [v2].[Name], [v2].[Computed], [v2].[Description], [v2].[Engine_Discriminator], [t1].[Name] AS [Name0] + SELECT [v2].[Name], [v2].[Computed], [v2].[Description], [v2].[Engine_Discriminator], [t2].[Name] AS [Name0] FROM [Vehicles] AS [v2] INNER JOIN ( SELECT [v3].[Name], [v3].[Discriminator], [v3].[SeatingCapacity], [v3].[AttachedVehicleName] FROM [Vehicles] AS [v3] WHERE [v3].[Discriminator] IN (N'PoweredVehicle', N'CompositeVehicle') - ) AS [t1] ON [v2].[Name] = [t1].[Name] + ) AS [t2] ON [v2].[Name] = [t2].[Name] WHERE [v2].[Engine_Discriminator] IN (N'ContinuousCombustionEngine', N'IntermittentCombustionEngine', N'SolidRocket') -) AS [t0] ON [v1].[Name] = [t0].[Name] +) AS [t1] ON [v1].[Name] = [t1].[Name] WHERE [v1].[Capacity] IS NOT NULL AND [v1].[FuelTank_Discriminator] IS NOT NULL"); } @@ -154,15 +154,15 @@ WHERE [v].[Capacity] IS NOT NULL SELECT [v1].[Name], [v1].[Capacity], [v1].[FuelType] FROM [Vehicles] AS [v1] INNER JOIN ( - SELECT [v2].[Name], [v2].[Computed], [v2].[Description], [v2].[Engine_Discriminator], [t1].[Name] AS [Name0] + SELECT [v2].[Name], [v2].[Computed], [v2].[Description], [v2].[Engine_Discriminator], [t2].[Name] AS [Name0] FROM [Vehicles] AS [v2] INNER JOIN ( SELECT [v3].[Name], [v3].[Discriminator], [v3].[SeatingCapacity], [v3].[AttachedVehicleName] FROM [Vehicles] AS [v3] WHERE [v3].[Discriminator] IN (N'PoweredVehicle', N'CompositeVehicle') - ) AS [t1] ON [v2].[Name] = [t1].[Name] + ) AS [t2] ON [v2].[Name] = [t2].[Name] WHERE [v2].[Engine_Discriminator] IN (N'ContinuousCombustionEngine', N'IntermittentCombustionEngine', N'SolidRocket') -) AS [t0] ON [v1].[Name] = [t0].[Name] +) AS [t1] ON [v1].[Name] = [t1].[Name] WHERE [v1].[Capacity] IS NOT NULL"); } @@ -183,15 +183,15 @@ WHERE [v].[Capacity] IS NOT NULL AND [v].[FuelType] IS NOT NULL SELECT [v1].[Name], [v1].[Capacity], [v1].[FuelType] FROM [Vehicles] AS [v1] INNER JOIN ( - SELECT [v2].[Name], [v2].[Computed], [v2].[Description], [v2].[Engine_Discriminator], [t1].[Name] AS [Name0] + SELECT [v2].[Name], [v2].[Computed], [v2].[Description], [v2].[Engine_Discriminator], [t2].[Name] AS [Name0] FROM [Vehicles] AS [v2] INNER JOIN ( SELECT [v3].[Name], [v3].[Discriminator], [v3].[SeatingCapacity], [v3].[AttachedVehicleName] FROM [Vehicles] AS [v3] WHERE [v3].[Discriminator] IN (N'PoweredVehicle', N'CompositeVehicle') - ) AS [t1] ON [v2].[Name] = [t1].[Name] + ) AS [t2] ON [v2].[Name] = [t2].[Name] WHERE [v2].[Engine_Discriminator] IN (N'ContinuousCombustionEngine', N'IntermittentCombustionEngine', N'SolidRocket') -) AS [t0] ON [v1].[Name] = [t0].[Name] +) AS [t1] ON [v1].[Name] = [t1].[Name] WHERE [v1].[Capacity] IS NOT NULL AND [v1].[FuelType] IS NOT NULL"); } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindGroupByQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindGroupByQuerySqliteTest.cs index 075f7973507..a7fecc7253c 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindGroupByQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/NorthwindGroupByQuerySqliteTest.cs @@ -37,5 +37,35 @@ public override async Task Select_uncorrelated_collection_with_groupby_when_oute SqliteStrings.ApplyNotSupported, (await Assert.ThrowsAsync( () => base.Select_uncorrelated_collection_with_groupby_when_outer_is_distinct(async))).Message); + + public override async Task AsEnumerable_in_subquery_for_GroupBy(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.AsEnumerable_in_subquery_for_GroupBy(async))).Message); + + public override async Task Complex_query_with_groupBy_in_subquery1(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Complex_query_with_groupBy_in_subquery1(async))).Message); + + public override async Task Complex_query_with_groupBy_in_subquery2(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Complex_query_with_groupBy_in_subquery2(async))).Message); + + public override async Task Complex_query_with_groupBy_in_subquery3(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Complex_query_with_groupBy_in_subquery3(async))).Message); + + public override async Task Select_nested_collection_with_groupby(bool async) + => Assert.Equal( + SqliteStrings.ApplyNotSupported, + (await Assert.ThrowsAsync( + () => base.Select_nested_collection_with_groupby(async))).Message); } }