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);
}
}