@@ -21,6 +21,9 @@ public class SqlServerSqlNullabilityProcessor : SqlNullabilityProcessor
2121 private static readonly bool UseOldBehavior37151 =
2222 AppContext . TryGetSwitch ( "Microsoft.EntityFrameworkCore.Issue37151" , out var enabled ) && enabled ;
2323
24+ private static readonly bool UseOldBehavior37185 =
25+ AppContext . TryGetSwitch ( "Microsoft.EntityFrameworkCore.Issue37185" , out var enabled ) && enabled ;
26+
2427 /// <summary>
2528 /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
2629 /// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -33,6 +36,7 @@ public class SqlServerSqlNullabilityProcessor : SqlNullabilityProcessor
3336 private readonly ISqlServerSingletonOptions _sqlServerSingletonOptions ;
3437
3538 private int _openJsonAliasCounter ;
39+ private int _totalParameterCount ;
3640
3741 /// <summary>
3842 /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -55,6 +59,18 @@ public SqlServerSqlNullabilityProcessor(
5559 /// </summary>
5660 public override Expression Process ( Expression queryExpression , ParametersCacheDecorator parametersDecorator )
5761 {
62+ if ( ! UseOldBehavior37185 )
63+ {
64+ var parametersCounter = new ParametersCounter (
65+ parametersDecorator ,
66+ CollectionParameterTranslationMode ,
67+ #pragma warning disable EF1001
68+ ( count , elementTypeMapping ) => CalculatePadding ( count , CalculateParameterBucketSize ( count , elementTypeMapping ) ) ) ;
69+ #pragma warning restore EF1001
70+ parametersCounter . Visit ( queryExpression ) ;
71+ _totalParameterCount = parametersCounter . Count ;
72+ }
73+
5874 var result = base . Process ( queryExpression , parametersDecorator ) ;
5975 _openJsonAliasCounter = 0 ;
6076 return result ;
@@ -313,7 +329,9 @@ private bool TryHandleOverLimitParameters(
313329 // SQL Server has limit on number of parameters in a query.
314330 // If we're over that limit, we switch to using single parameter
315331 // and processing it through JSON functions.
316- if ( values . Count > MaxParameterCount )
332+ if ( UseOldBehavior37185
333+ ? values . Count > MaxParameterCount
334+ : _totalParameterCount > MaxParameterCount )
317335 {
318336 if ( _sqlServerSingletonOptions . SupportsJsonFunctions )
319337 {
@@ -372,3 +390,107 @@ valuesExpression is not null
372390 }
373391#pragma warning restore EF1001
374392}
393+
394+ /// <summary>
395+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
396+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
397+ /// any release. You should only use it directly in your code with extreme caution and knowing that
398+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
399+ /// </summary>
400+ public class ParametersCounter (
401+ ParametersCacheDecorator parametersDecorator ,
402+ ParameterTranslationMode collectionParameterTranslationMode ,
403+ Func < int , RelationalTypeMapping , int > bucketizationPadding ) : ExpressionVisitor
404+ {
405+ /// <summary>
406+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
407+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
408+ /// any release. You should only use it directly in your code with extreme caution and knowing that
409+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
410+ /// </summary>
411+ public virtual int Count { get ; private set ; }
412+
413+ private readonly HashSet < SqlParameterExpression > _visitedSqlParameters =
414+ new ( EqualityComparer < SqlParameterExpression > . Create (
415+ ( lhs , rhs ) =>
416+ ReferenceEquals ( lhs , rhs )
417+ || ( lhs is not null && rhs is not null
418+ && lhs . InvariantName == rhs . InvariantName
419+ && lhs . TranslationMode == rhs . TranslationMode ) ,
420+ x => HashCode . Combine ( x . InvariantName , x . TranslationMode ) ) ) ;
421+
422+ private readonly HashSet < QueryParameterExpression > _visitedQueryParameters =
423+ new ( EqualityComparer < QueryParameterExpression > . Create (
424+ ( lhs , rhs ) =>
425+ ReferenceEquals ( lhs , rhs )
426+ || ( lhs is not null && rhs is not null
427+ && lhs . Name == rhs . Name
428+ && lhs . TranslationMode == rhs . TranslationMode ) ,
429+ x => HashCode . Combine ( x . Name , x . TranslationMode ) ) ) ;
430+
431+ /// <inheritdoc/>
432+ protected override Expression VisitExtension ( Expression node )
433+ {
434+ switch ( node )
435+ {
436+ case ValuesExpression { ValuesParameter : { } valuesParameter } :
437+ ProcessCollectionParameter ( valuesParameter , bucketization : false ) ;
438+ break ;
439+
440+ case InExpression { ValuesParameter : { } valuesParameter } :
441+ ProcessCollectionParameter ( valuesParameter , bucketization : true ) ;
442+ break ;
443+
444+ case FromSqlExpression { Arguments : QueryParameterExpression queryParameter } :
445+ if ( _visitedQueryParameters . Add ( queryParameter ) )
446+ {
447+ var parameters = parametersDecorator . GetAndDisableCaching ( ) ;
448+ Count += ( ( object ? [ ] ) parameters [ queryParameter . Name ] ! ) . Length ;
449+ }
450+ break ;
451+
452+ case SqlParameterExpression sqlParameterExpression :
453+ if ( _visitedSqlParameters . Add ( sqlParameterExpression ) )
454+ {
455+ Count ++ ;
456+ }
457+ break ;
458+ }
459+
460+ return base . VisitExtension ( node ) ;
461+ }
462+
463+ private void ProcessCollectionParameter ( SqlParameterExpression sqlParameterExpression , bool bucketization )
464+ {
465+ if ( ! _visitedSqlParameters . Add ( sqlParameterExpression ) )
466+ {
467+ return ;
468+ }
469+
470+ switch ( sqlParameterExpression . TranslationMode ?? collectionParameterTranslationMode )
471+ {
472+ case ParameterTranslationMode . MultipleParameters :
473+ var parameters = parametersDecorator . GetAndDisableCaching ( ) ;
474+ var count = ( ( IEnumerable ? ) parameters [ sqlParameterExpression . Name ] ) ? . Cast < object ? > ( ) . Count ( ) ?? 0 ;
475+ Count += count ;
476+
477+ if ( bucketization )
478+ {
479+ var elementTypeMapping = ( RelationalTypeMapping ) sqlParameterExpression . TypeMapping ! . ElementTypeMapping ! ;
480+ Count += bucketizationPadding ( count , elementTypeMapping ) ;
481+ }
482+
483+ break ;
484+
485+ case ParameterTranslationMode . Parameter :
486+ Count ++ ;
487+ break ;
488+
489+ case ParameterTranslationMode . Constant :
490+ break ;
491+
492+ default :
493+ throw new UnreachableException ( ) ;
494+ }
495+ }
496+ }
0 commit comments