Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 51 additions & 14 deletions src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,7 @@ protected override Expression VisitExtension(Expression node)
{
// Create parameter for value if we didn't create it yet,
// otherwise reuse it.
if (expandedParameters.Count <= i)
{
var parameterName = Uniquifier.Uniquify(valuesParameter.Name, queryParameters, int.MaxValue);
queryParameters.Add(parameterName, values[i]);
var parameterExpression = new SqlParameterExpression(parameterName, values[i]?.GetType() ?? typeof(object), elementTypeMapping);
expandedParameters.Add(parameterExpression);
}
ExpandParameterIfNeeded(valuesParameter.Name, expandedParameters, queryParameters, i, values[i], elementTypeMapping);

processedValues.Add(
new RowValueExpression(
Expand Down Expand Up @@ -832,13 +826,7 @@ InExpression ProcessInExpressionValues(
{
// Create parameter for value if we didn't create it yet,
// otherwise reuse it.
if (expandedParameters.Count <= i)
{
var parameterName = Uniquifier.Uniquify(valuesParameter.Name, parameters, int.MaxValue);
parameters.Add(parameterName, values[i]);
var parameterExpression = new SqlParameterExpression(parameterName, values[i]?.GetType() ?? typeof(object), elementTypeMapping);
expandedParameters.Add(parameterExpression);
}
ExpandParameterIfNeeded(valuesParameter.Name, expandedParameters, parameters, i, values[i], elementTypeMapping);
Comment thread
cincuranet marked this conversation as resolved.
Outdated

// Use separate counter, because we may skip nulls.
processedValues.Add(expandedParameters[expandedParametersCounter++]);
Expand All @@ -857,6 +845,23 @@ InExpression ProcessInExpressionValues(
throw new UnreachableException();
}
}

// Bucketization.
Comment thread
cincuranet marked this conversation as resolved.
Outdated
if ((valuesParameter.TranslationMode ?? CollectionParameterTranslationMode) is ParameterTranslationMode.MultipleParameters)
Comment thread
cincuranet marked this conversation as resolved.
Outdated
{
// For provider to effectively disable bucketization, return always 1 from ParametersPadFactor.
var padFactor = ParametersPadFactor(values.Count);
var padding = (padFactor - (values.Count % padFactor)) % padFactor;
for (var i = 0; i < padding; i++)
{
// Create parameter for value if we didn't create it yet,
// otherwise reuse it.
ExpandParameterIfNeeded(valuesParameter.Name, expandedParameters, parameters, values.Count + i, values[^1], elementTypeMapping);

// Use separate counter, because we may skip nulls.
processedValues.Add(expandedParameters[expandedParametersCounter++]);
}
}
}
else
{
Expand Down Expand Up @@ -1488,6 +1493,21 @@ protected virtual SqlExpression VisitJsonScalar(
protected virtual bool PreferExistsToInWithCoalesce
=> false;

/// <summary>
/// Gets the factor by which the parameters are padded when generating a parameterized collection
/// when using multiple parameters. This helps with query plan bloat.
/// </summary>
/// <param name="count">Number of value parameters are generated for.</param>
protected virtual int ParametersPadFactor(int count)
Comment thread
cincuranet marked this conversation as resolved.
Outdated
Comment thread
cincuranet marked this conversation as resolved.
Outdated
=> count switch
{
<= 5 => 1,
<= 150 => 10,
<= 750 => 50,
<= 2000 => 100,
Comment thread
roji marked this conversation as resolved.
_ => 200,
};

// Note that we can check parameter values for null since we cache by the parameter nullability; but we cannot do the same for bool.
private bool IsNull(SqlExpression? expression)
=> expression is SqlConstantExpression { Value: null }
Expand Down Expand Up @@ -2121,4 +2141,21 @@ private SqlExpression ProcessNullNotNull(SqlExpression sqlExpression, bool opera

private static bool IsLogicalNot(SqlUnaryExpression? sqlUnaryExpression)
=> sqlUnaryExpression is { OperatorType: ExpressionType.Not } && sqlUnaryExpression.Type == typeof(bool);

private static void ExpandParameterIfNeeded(
string valuesParameterName,
List<SqlParameterExpression> expandedParameters,
Dictionary<string, object?> parameters,
int index,
object? value,
RelationalTypeMapping typeMapping)
{
if (expandedParameters.Count <= index)
{
var parameterName = Uniquifier.Uniquify(valuesParameterName, parameters, int.MaxValue);
parameters.Add(parameterName, value);
var parameterExpression = new SqlParameterExpression(parameterName, value?.GetType() ?? typeof(object), typeMapping);
expandedParameters.Add(parameterExpression);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
/// </summary>
public class SqlServerSqlNullabilityProcessor : SqlNullabilityProcessor
{
private const int MaxParameterCount = 2100;
Comment thread
cincuranet marked this conversation as resolved.

/// <summary>
/// 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
Expand Down Expand Up @@ -294,7 +296,7 @@ private bool TryHandleOverLimitParameters(
// SQL Server has limit on number of parameters in a query.
// If we're over that limit, we switch to using single parameter
// and processing it through JSON functions.
if (values.Count > 2098)
if (values.Count > MaxParameterCount)
{
if (_sqlServerSingletonOptions.SupportsJsonFunctions)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,27 @@ public virtual async Task Parameter_collection_Contains_with_default_mode_EF_Mul
Assert.Equivalent(new[] { 2 }, result);
}

[ConditionalFact]
public virtual async Task Parameter_collection_Contains_parameter_bucketization()
{
var contextFactory = await InitializeAsync<TestContext>(
onConfiguring: b => SetParameterizedCollectionMode(b, ParameterTranslationMode.MultipleParameters),
seed: context =>
{
context.AddRange(
new TestEntity { Id = 1 },
new TestEntity { Id = 2 },
new TestEntity { Id = 100 });
return context.SaveChangesAsync();
});

await using var context = contextFactory.CreateContext();

var ints = new[] { 2, 999, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 };
var result = await context.Set<TestEntity>().Where(c => ints.Contains(c.Id)).Select(c => c.Id).ToListAsync();
Assert.Equivalent(new[] { 2 }, result);
}

protected class TestOwner
{
public int Id { get; set; }
Expand Down
Loading