@@ -443,25 +443,7 @@ private ShapedQueryExpression CreateShapedQueryExpression(SelectExpression selec
443
443
/// doing so can result in application failures when updating to a new Entity Framework Core release.
444
444
/// </summary>
445
445
protected override ShapedQueryExpression ? TranslateAverage ( ShapedQueryExpression source , LambdaExpression ? selector , Type resultType )
446
- {
447
- var selectExpression = ( SelectExpression ) source . QueryExpression ;
448
- if ( selectExpression . IsDistinct
449
- || selectExpression . Limit != null
450
- || selectExpression . Offset != null )
451
- {
452
- return null ;
453
- }
454
-
455
- if ( selector != null )
456
- {
457
- source = TranslateSelect ( source , selector ) ;
458
- }
459
-
460
- var projection = ( SqlExpression ) selectExpression . GetMappedProjection ( new ProjectionMember ( ) ) ;
461
- projection = _sqlExpressionFactory . Function ( "AVG" , new [ ] { projection } , resultType , _typeMappingSource . FindMapping ( resultType ) ) ;
462
-
463
- return AggregateResultShaper ( source , projection , throwOnNullResult : true , resultType ) ;
464
- }
446
+ => TranslateAggregate ( source , selector , resultType , "AVG" ) ;
465
447
466
448
/// <summary>
467
449
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -841,26 +823,7 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou
841
823
/// doing so can result in application failures when updating to a new Entity Framework Core release.
842
824
/// </summary>
843
825
protected override ShapedQueryExpression ? TranslateMax ( ShapedQueryExpression source , LambdaExpression ? selector , Type resultType )
844
- {
845
- var selectExpression = ( SelectExpression ) source . QueryExpression ;
846
- if ( selectExpression . IsDistinct
847
- || selectExpression . Limit != null
848
- || selectExpression . Offset != null )
849
- {
850
- return null ;
851
- }
852
-
853
- if ( selector != null )
854
- {
855
- source = TranslateSelect ( source , selector ) ;
856
- }
857
-
858
- var projection = ( SqlExpression ) selectExpression . GetMappedProjection ( new ProjectionMember ( ) ) ;
859
-
860
- projection = _sqlExpressionFactory . Function ( "MAX" , new [ ] { projection } , resultType , projection . TypeMapping ) ;
861
-
862
- return AggregateResultShaper ( source , projection , throwOnNullResult : true , resultType ) ;
863
- }
826
+ => TranslateAggregate ( source , selector , resultType , "MAX" ) ;
864
827
865
828
/// <summary>
866
829
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -869,26 +832,7 @@ protected override ShapedQueryExpression TranslateCast(ShapedQueryExpression sou
869
832
/// doing so can result in application failures when updating to a new Entity Framework Core release.
870
833
/// </summary>
871
834
protected override ShapedQueryExpression ? TranslateMin ( ShapedQueryExpression source , LambdaExpression ? selector , Type resultType )
872
- {
873
- var selectExpression = ( SelectExpression ) source . QueryExpression ;
874
- if ( selectExpression . IsDistinct
875
- || selectExpression . Limit != null
876
- || selectExpression . Offset != null )
877
- {
878
- return null ;
879
- }
880
-
881
- if ( selector != null )
882
- {
883
- source = TranslateSelect ( source , selector ) ;
884
- }
885
-
886
- var projection = ( SqlExpression ) selectExpression . GetMappedProjection ( new ProjectionMember ( ) ) ;
887
-
888
- projection = _sqlExpressionFactory . Function ( "MIN" , new [ ] { projection } , resultType , projection . TypeMapping ) ;
889
-
890
- return AggregateResultShaper ( source , projection , throwOnNullResult : true , resultType ) ;
891
- }
835
+ => TranslateAggregate ( source , selector , resultType , "MIN" ) ;
892
836
893
837
/// <summary>
894
838
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -1241,7 +1185,7 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s
1241
1185
1242
1186
projection = _sqlExpressionFactory . Function ( "SUM" , new [ ] { projection } , serverOutputType , projection . TypeMapping ) ;
1243
1187
1244
- return AggregateResultShaper ( source , projection , throwOnNullResult : false , resultType ) ;
1188
+ return AggregateResultShaper ( source , projection , resultType ) ;
1245
1189
}
1246
1190
1247
1191
/// <summary>
@@ -1515,6 +1459,35 @@ protected override ShapedQueryExpression TranslateSelect(ShapedQueryExpression s
1515
1459
1516
1460
#endregion Queryable collection support
1517
1461
1462
+ private ShapedQueryExpression ? TranslateAggregate ( ShapedQueryExpression source , LambdaExpression ? selector , Type resultType , string functionName )
1463
+ {
1464
+ var selectExpression = ( SelectExpression ) source . QueryExpression ;
1465
+ if ( selectExpression . IsDistinct
1466
+ || selectExpression . Limit != null
1467
+ || selectExpression . Offset != null )
1468
+ {
1469
+ return null ;
1470
+ }
1471
+
1472
+ if ( selector != null )
1473
+ {
1474
+ source = TranslateSelect ( source , selector ) ;
1475
+ }
1476
+
1477
+ if ( ! _subquery && resultType . IsNullableType ( ) )
1478
+ {
1479
+ // For nullable types, we want to return null from Max, Min, and Average, rather than throwing. See Issue #35094.
1480
+ // Note that relational databases typically return null, which propagates. Cosmos will instead return no elements,
1481
+ // and hence for Cosmos only we need to change no elements into null.
1482
+ source = source . UpdateResultCardinality ( ResultCardinality . SingleOrDefault ) ;
1483
+ }
1484
+
1485
+ var projection = ( SqlExpression ) selectExpression . GetMappedProjection ( new ProjectionMember ( ) ) ;
1486
+ projection = _sqlExpressionFactory . Function ( functionName , [ projection ] , resultType , _typeMappingSource . FindMapping ( resultType ) ) ;
1487
+
1488
+ return AggregateResultShaper ( source , projection , resultType ) ;
1489
+ }
1490
+
1518
1491
private bool TryApplyPredicate ( ShapedQueryExpression source , LambdaExpression predicate )
1519
1492
{
1520
1493
var select = ( SelectExpression ) source . QueryExpression ;
@@ -1695,7 +1668,6 @@ private Expression RemapLambdaBody(ShapedQueryExpression shapedQueryExpression,
1695
1668
private static ShapedQueryExpression AggregateResultShaper (
1696
1669
ShapedQueryExpression source ,
1697
1670
Expression projection ,
1698
- bool throwOnNullResult ,
1699
1671
Type resultType )
1700
1672
{
1701
1673
var selectExpression = ( SelectExpression ) source . QueryExpression ;
@@ -1706,29 +1678,7 @@ private static ShapedQueryExpression AggregateResultShaper(
1706
1678
var nullableResultType = resultType . MakeNullable ( ) ;
1707
1679
Expression shaper = new ProjectionBindingExpression ( source . QueryExpression , new ProjectionMember ( ) , nullableResultType ) ;
1708
1680
1709
- if ( throwOnNullResult )
1710
- {
1711
- var resultVariable = Expression . Variable ( nullableResultType , "result" ) ;
1712
- var returnValueForNull = resultType . IsNullableType ( )
1713
- ? ( Expression ) Expression . Constant ( null , resultType )
1714
- : Expression . Throw (
1715
- Expression . New (
1716
- typeof ( InvalidOperationException ) . GetConstructors ( )
1717
- . Single ( ci => ci . GetParameters ( ) . Length == 1 ) ,
1718
- Expression . Constant ( CoreStrings . SequenceContainsNoElements ) ) ,
1719
- resultType ) ;
1720
-
1721
- shaper = Expression . Block (
1722
- new [ ] { resultVariable } ,
1723
- Expression . Assign ( resultVariable , shaper ) ,
1724
- Expression . Condition (
1725
- Expression . Equal ( resultVariable , Expression . Default ( nullableResultType ) ) ,
1726
- returnValueForNull ,
1727
- resultType != resultVariable . Type
1728
- ? Expression . Convert ( resultVariable , resultType )
1729
- : resultVariable ) ) ;
1730
- }
1731
- else if ( resultType != shaper . Type )
1681
+ if ( resultType != shaper . Type )
1732
1682
{
1733
1683
shaper = Expression . Convert ( shaper , resultType ) ;
1734
1684
}
0 commit comments