@@ -18,7 +18,7 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
1818/// </summary>
1919public class SqlServerQueryableMethodTranslatingExpressionVisitor : RelationalQueryableMethodTranslatingExpressionVisitor
2020{
21- private readonly QueryCompilationContext _queryCompilationContext ;
21+ private readonly SqlServerQueryCompilationContext _queryCompilationContext ;
2222 private readonly IRelationalTypeMappingSource _typeMappingSource ;
2323 private readonly ISqlExpressionFactory _sqlExpressionFactory ;
2424 private readonly int _sqlServerCompatibilityLevel ;
@@ -34,7 +34,7 @@ public class SqlServerQueryableMethodTranslatingExpressionVisitor : RelationalQu
3434 public SqlServerQueryableMethodTranslatingExpressionVisitor (
3535 QueryableMethodTranslatingExpressionVisitorDependencies dependencies ,
3636 RelationalQueryableMethodTranslatingExpressionVisitorDependencies relationalDependencies ,
37- QueryCompilationContext queryCompilationContext ,
37+ SqlServerQueryCompilationContext queryCompilationContext ,
3838 ISqlServerSingletonOptions sqlServerSingletonOptions )
3939 : base ( dependencies , relationalDependencies , queryCompilationContext )
4040 {
@@ -121,6 +121,103 @@ protected override Expression VisitExtension(Expression extensionExpression)
121121 return base . VisitExtension ( extensionExpression ) ;
122122 }
123123
124+ #region Aggregate functions
125+
126+ // We override these for SQL Server to add tracking whether we're inside an aggregate function context, since SQL Server doesn't
127+ // support subqueries (or aggregates) within them.
128+
129+ /// <summary>
130+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
131+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
132+ /// any release. You should only use it directly in your code with extreme caution and knowing that
133+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
134+ /// </summary>
135+ protected override ShapedQueryExpression ? TranslateAverage ( ShapedQueryExpression source , LambdaExpression ? selector , Type resultType )
136+ {
137+ var previousInAggregateFunction = _queryCompilationContext . InAggregateFunction ;
138+ _queryCompilationContext . InAggregateFunction = true ;
139+ var result = base . TranslateAverage ( source , selector , resultType ) ;
140+ _queryCompilationContext . InAggregateFunction = previousInAggregateFunction ;
141+ return result ;
142+ }
143+
144+ /// <summary>
145+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
146+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
147+ /// any release. You should only use it directly in your code with extreme caution and knowing that
148+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
149+ /// </summary>
150+ protected override ShapedQueryExpression ? TranslateSum ( ShapedQueryExpression source , LambdaExpression ? selector , Type resultType )
151+ {
152+ var previousInAggregateFunction = _queryCompilationContext . InAggregateFunction ;
153+ _queryCompilationContext . InAggregateFunction = true ;
154+ var result = base . TranslateSum ( source , selector , resultType ) ;
155+ _queryCompilationContext . InAggregateFunction = previousInAggregateFunction ;
156+ return result ;
157+ }
158+
159+ /// <summary>
160+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
161+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
162+ /// any release. You should only use it directly in your code with extreme caution and knowing that
163+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
164+ /// </summary>
165+ protected override ShapedQueryExpression ? TranslateCount ( ShapedQueryExpression source , LambdaExpression ? predicate )
166+ {
167+ var previousInAggregateFunction = _queryCompilationContext . InAggregateFunction ;
168+ _queryCompilationContext . InAggregateFunction = true ;
169+ var result = base . TranslateCount ( source , predicate ) ;
170+ _queryCompilationContext . InAggregateFunction = previousInAggregateFunction ;
171+ return result ;
172+ }
173+
174+ /// <summary>
175+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
176+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
177+ /// any release. You should only use it directly in your code with extreme caution and knowing that
178+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
179+ /// </summary>
180+ protected override ShapedQueryExpression ? TranslateLongCount ( ShapedQueryExpression source , LambdaExpression ? predicate )
181+ {
182+ var previousInAggregateFunction = _queryCompilationContext . InAggregateFunction ;
183+ _queryCompilationContext . InAggregateFunction = true ;
184+ var result = base . TranslateLongCount ( source , predicate ) ;
185+ _queryCompilationContext . InAggregateFunction = previousInAggregateFunction ;
186+ return result ;
187+ }
188+
189+ /// <summary>
190+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
191+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
192+ /// any release. You should only use it directly in your code with extreme caution and knowing that
193+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
194+ /// </summary>
195+ protected override ShapedQueryExpression ? TranslateMax ( ShapedQueryExpression source , LambdaExpression ? selector , Type resultType )
196+ {
197+ var previousInAggregateFunction = _queryCompilationContext . InAggregateFunction ;
198+ _queryCompilationContext . InAggregateFunction = true ;
199+ var result = base . TranslateMax ( source , selector , resultType ) ;
200+ _queryCompilationContext . InAggregateFunction = previousInAggregateFunction ;
201+ return result ;
202+ }
203+
204+ /// <summary>
205+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
206+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
207+ /// any release. You should only use it directly in your code with extreme caution and knowing that
208+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
209+ /// </summary>
210+ protected override ShapedQueryExpression ? TranslateMin ( ShapedQueryExpression source , LambdaExpression ? selector , Type resultType )
211+ {
212+ var previousInAggregateFunction = _queryCompilationContext . InAggregateFunction ;
213+ _queryCompilationContext . InAggregateFunction = true ;
214+ var result = base . TranslateMin ( source , selector , resultType ) ;
215+ _queryCompilationContext . InAggregateFunction = previousInAggregateFunction ;
216+ return result ;
217+ }
218+
219+ #endregion Aggregate functions
220+
124221 /// <summary>
125222 /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
126223 /// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -315,6 +412,47 @@ static IEnumerable<INavigation> GetAllNavigationsInHierarchy(IEntityType entityT
315412 . SelectMany ( t => t . GetDeclaredNavigations ( ) ) ;
316413 }
317414
415+ /// <summary>
416+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
417+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
418+ /// any release. You should only use it directly in your code with extreme caution and knowing that
419+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
420+ /// </summary>
421+ protected override ShapedQueryExpression ? TranslateContains ( ShapedQueryExpression source , Expression item )
422+ {
423+ var translatedSource = base . TranslateContains ( source , item ) ;
424+
425+ // SQL Server does not support subqueries inside aggregate functions (e.g. COUNT(SELECT * FROM OPENJSON(@p)...)).
426+ // As a result, we track whether we're within an aggregate function; if we are, and we see the regular Contains translation
427+ // (which uses IN with an OPENJSON subquery - incompatible), we transform it to the old-style IN+constants translation (as if a
428+ // low SQL Server compatibility level were defined)
429+ if ( _queryCompilationContext . InAggregateFunction
430+ && translatedSource is not null
431+ && TryGetProjection ( translatedSource , out var projection )
432+ && projection is InExpression
433+ {
434+ Item : var translatedItem ,
435+ Subquery :
436+ {
437+ Tables : [ SqlServerOpenJsonExpression { Arguments : [ SqlParameterExpression parameter ] } openJsonExpression ] ,
438+ GroupBy : [ ] ,
439+ Having : null ,
440+ IsDistinct : false ,
441+ Limit : null ,
442+ Offset : null ,
443+ Orderings : [ ] ,
444+ Projection : [ { Expression : ColumnExpression { Name : "value" , Table : var projectionColumnTable } } ]
445+ }
446+ }
447+ && projectionColumnTable == openJsonExpression )
448+ {
449+ var newInExpression = _sqlExpressionFactory . In ( translatedItem , parameter ) ;
450+ return source . UpdateQueryExpression ( _sqlExpressionFactory . Select ( newInExpression ) ) ;
451+ }
452+
453+ return translatedSource ;
454+ }
455+
318456 /// <summary>
319457 /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
320458 /// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -504,6 +642,29 @@ protected override bool IsValidSelectExpressionForExecuteUpdate(
504642 return false ;
505643 }
506644
645+ private bool TryGetProjection ( ShapedQueryExpression shapedQueryExpression , [ NotNullWhen ( true ) ] out SqlExpression ? projection )
646+ {
647+ var shaperExpression = shapedQueryExpression . ShaperExpression ;
648+ // No need to check ConvertChecked since this is convert node which we may have added during projection
649+ if ( shaperExpression is UnaryExpression { NodeType : ExpressionType . Convert } unaryExpression
650+ && unaryExpression . Operand . Type . IsNullableType ( )
651+ && unaryExpression . Operand . Type . UnwrapNullableType ( ) == unaryExpression . Type )
652+ {
653+ shaperExpression = unaryExpression . Operand ;
654+ }
655+
656+ if ( shapedQueryExpression . QueryExpression is SelectExpression selectExpression
657+ && shaperExpression is ProjectionBindingExpression projectionBindingExpression
658+ && selectExpression . GetProjection ( projectionBindingExpression ) is SqlExpression sqlExpression )
659+ {
660+ projection = sqlExpression ;
661+ return true ;
662+ }
663+
664+ projection = null ;
665+ return false ;
666+ }
667+
507668 private sealed class TemporalAnnotationApplyingExpressionVisitor : ExpressionVisitor
508669 {
509670 private readonly Func < TableExpression , TableExpressionBase > _annotationApplyingFunc ;
0 commit comments