diff --git a/src/EFCore.Relational/EFExtensions.cs b/src/EFCore.Relational/EFExtensions.cs
index c2677cb8706..c790c6256f2 100644
--- a/src/EFCore.Relational/EFExtensions.cs
+++ b/src/EFCore.Relational/EFExtensions.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Collections;
+
namespace Microsoft.EntityFrameworkCore;
///
@@ -25,10 +27,10 @@ public static class EFExtensions
/// Within the context of an EF LINQ query, forces its argument to be inserted into the query as a multiple parameter expressions.
///
/// Note that this is a static method accessed through the top-level static type.
- /// The type of collection element.
+ /// The type of collection.
/// The collection to be integrated as parameters into the query.
/// The same value for further use in the query.
- public static IEnumerable MultipleParameters(IEnumerable argument)
+ public static TSource MultipleParameters(TSource argument) where TSource : IEnumerable
=> throw new InvalidOperationException(RelationalStrings.EFMultipleParametersInvoked);
}
}
diff --git a/src/EFCore.Relational/Infrastructure/RelationalDbContextOptionsBuilder.cs b/src/EFCore.Relational/Infrastructure/RelationalDbContextOptionsBuilder.cs
index 269a68bfb0a..01935a24036 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalDbContextOptionsBuilder.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalDbContextOptionsBuilder.cs
@@ -179,7 +179,7 @@ public virtual TBuilder ExecutionStrategy(
///
[Obsolete("Use UseParameterizedCollectionMode instead.")]
public virtual TBuilder TranslateParameterizedCollectionsToConstants()
- => UseParameterizedCollectionMode(ParameterizedCollectionMode.Constants);
+ => UseParameterizedCollectionMode(ParameterTranslationMode.Constant);
///
/// Configures the context to translate parameterized collections to a single array-like parameter.
@@ -202,13 +202,13 @@ public virtual TBuilder TranslateParameterizedCollectionsToConstants()
///
[Obsolete("Use UseParameterizedCollectionMode instead.")]
public virtual TBuilder TranslateParameterizedCollectionsToParameters()
- => UseParameterizedCollectionMode(ParameterizedCollectionMode.Parameter);
+ => UseParameterizedCollectionMode(ParameterTranslationMode.Parameter);
///
- /// Configures the to use when translating parameterized collections.
+ /// Configures the mode to use when translating parameterized collections.
///
/// The same builder instance so that multiple calls can be chained.
- public virtual TBuilder UseParameterizedCollectionMode(ParameterizedCollectionMode parameterizedCollectionMode)
+ public virtual TBuilder UseParameterizedCollectionMode(ParameterTranslationMode parameterizedCollectionMode)
=> WithOption(e => (TExtension)e.WithUseParameterizedCollectionMode(parameterizedCollectionMode));
///
diff --git a/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs b/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs
index e2cc09b80c8..a052b74c1eb 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs
@@ -36,7 +36,7 @@ public abstract class RelationalOptionsExtension : IDbContextOptionsExtension
private string? _migrationsHistoryTableName;
private string? _migrationsHistoryTableSchema;
private Func? _executionStrategyFactory;
- private ParameterizedCollectionMode? _parameterizedCollectionMode;
+ private ParameterTranslationMode? _parameterizedCollectionMode;
///
/// Creates a new set of options with everything set to default values.
@@ -386,8 +386,8 @@ public virtual RelationalOptionsExtension WithExecutionStrategyFactory(
///
/// Configured translation mode for parameterized collections.
///
- public virtual ParameterizedCollectionMode ParameterizedCollectionMode
- => _parameterizedCollectionMode ?? ParameterizedCollectionMode.MultipleParameters;
+ public virtual ParameterTranslationMode ParameterizedCollectionMode
+ => _parameterizedCollectionMode ?? ParameterTranslationMode.MultipleParameters;
///
/// Creates a new instance with all options the same as for this instance, but with the given option changed.
@@ -395,7 +395,7 @@ public virtual ParameterizedCollectionMode ParameterizedCollectionMode
///
/// The option to change.
public virtual RelationalOptionsExtension WithUseParameterizedCollectionMode(
- ParameterizedCollectionMode parameterizedCollectionMode)
+ ParameterTranslationMode parameterizedCollectionMode)
{
var clone = Clone();
diff --git a/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs b/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs
index 4d19a2b3eaa..e7ba5c61764 100644
--- a/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs
+++ b/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs
@@ -35,13 +35,13 @@ public RelationalCommandCache(
IRelationalParameterBasedSqlProcessorFactory relationalParameterBasedSqlProcessorFactory,
Expression queryExpression,
bool useRelationalNulls,
- ParameterizedCollectionMode parameterizedCollectionMode)
+ ParameterTranslationMode collectionParameterTranslationMode)
{
_memoryCache = memoryCache;
_querySqlGeneratorFactory = querySqlGeneratorFactory;
_queryExpression = queryExpression;
_relationalParameterBasedSqlProcessor = relationalParameterBasedSqlProcessorFactory.Create(
- new RelationalParameterBasedSqlProcessorParameters(useRelationalNulls, parameterizedCollectionMode));
+ new RelationalParameterBasedSqlProcessorParameters(useRelationalNulls, collectionParameterTranslationMode));
}
///
diff --git a/src/EFCore.Relational/Query/Internal/RelationalParameterProcessor.cs b/src/EFCore.Relational/Query/Internal/RelationalParameterProcessor.cs
index b955ccae286..f1ed4b087b1 100644
--- a/src/EFCore.Relational/Query/Internal/RelationalParameterProcessor.cs
+++ b/src/EFCore.Relational/Query/Internal/RelationalParameterProcessor.cs
@@ -128,7 +128,7 @@ private SqlParameterExpression VisitSqlParameter(SqlParameterExpression paramete
uniquifiedName,
parameter.Type,
parameter.IsNullable,
- parameter.ShouldBeConstantized,
+ parameter.TranslationMode,
parameter.TypeMapping);
return _sqlParameters[newParameter.InvariantName] = newParameter;
diff --git a/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessorParameters.cs b/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessorParameters.cs
index 415a8db6e54..d0bc742fd83 100644
--- a/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessorParameters.cs
+++ b/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessorParameters.cs
@@ -16,17 +16,17 @@ public sealed record RelationalParameterBasedSqlProcessorParameters
///
/// Which parametrized collection translation mode should be used.
///
- public ParameterizedCollectionMode ParameterizedCollectionMode { get; init; }
+ public ParameterTranslationMode CollectionParameterTranslationMode { get; init; }
///
/// Creates a new instance of .
///
/// A value indicating if relational nulls should be used.
- /// Which translation mode should be used.
+ /// Which translation mode should be used.
[EntityFrameworkInternal]
- public RelationalParameterBasedSqlProcessorParameters(bool useRelationalNulls, ParameterizedCollectionMode parameterizedCollectionMode)
+ public RelationalParameterBasedSqlProcessorParameters(bool useRelationalNulls, ParameterTranslationMode collectionParameterTranslationMode)
{
UseRelationalNulls = useRelationalNulls;
- ParameterizedCollectionMode = parameterizedCollectionMode;
+ CollectionParameterTranslationMode = collectionParameterTranslationMode;
}
}
diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
index 848973552c6..18bb222f2f1 100644
--- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
@@ -21,7 +21,7 @@ public partial class RelationalQueryableMethodTranslatingExpressionVisitor : Que
private readonly IRelationalTypeMappingSource _typeMappingSource;
private readonly ISqlExpressionFactory _sqlExpressionFactory;
private readonly bool _subquery;
- private readonly ParameterizedCollectionMode _parameterizedCollectionMode;
+ private readonly ParameterTranslationMode _collectionParameterTranslationMode;
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -64,7 +64,7 @@ public RelationalQueryableMethodTranslatingExpressionVisitor(
_typeMappingSource = relationalDependencies.TypeMappingSource;
_sqlExpressionFactory = sqlExpressionFactory;
_subquery = false;
- _parameterizedCollectionMode = RelationalOptionsExtension.Extract(queryCompilationContext.ContextOptions).ParameterizedCollectionMode;
+ _collectionParameterTranslationMode = RelationalOptionsExtension.Extract(queryCompilationContext.ContextOptions).ParameterizedCollectionMode;
}
///
@@ -90,7 +90,7 @@ protected RelationalQueryableMethodTranslatingExpressionVisitor(
_typeMappingSource = parentVisitor._typeMappingSource;
_sqlExpressionFactory = parentVisitor._sqlExpressionFactory;
_subquery = true;
- _parameterizedCollectionMode = RelationalOptionsExtension.Extract(parentVisitor._queryCompilationContext.ContextOptions).ParameterizedCollectionMode;
+ _collectionParameterTranslationMode = RelationalOptionsExtension.Extract(parentVisitor._queryCompilationContext.ContextOptions).ParameterizedCollectionMode;
}
///
@@ -244,7 +244,8 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
&& methodCallExpression.Arguments[0] is ParameterQueryRootExpression parameterSource
&& TranslateExpression(methodCallExpression.Arguments[1]) is SqlExpression item
&& _sqlTranslator.Visit(parameterSource.QueryParameterExpression) is SqlParameterExpression sqlParameterExpression
- && !parameterSource.QueryParameterExpression.ShouldNotBeConstantized)
+ && (parameterSource.QueryParameterExpression.TranslationMode is ParameterTranslationMode.Constant
+ or null))
{
var inExpression = _sqlExpressionFactory.In(item, sqlParameterExpression);
var selectExpression = new SelectExpression(inExpression, _sqlAliasManager);
@@ -298,26 +299,24 @@ JsonScalarExpression jsonScalar
var tableAlias = _sqlAliasManager.GenerateTableAlias(sqlParameterExpression.Name.TrimStart('_'));
- var constants = queryParameter.ShouldBeConstantized
- || (_parameterizedCollectionMode is ParameterizedCollectionMode.Constants
- && !queryParameter.ShouldNotBeConstantized);
- var multipleParameters = _parameterizedCollectionMode is ParameterizedCollectionMode.MultipleParameters
- && !queryParameter.ShouldNotBeConstantized;
- if (constants || multipleParameters)
- {
- var valuesExpression = new ValuesExpression(
- tableAlias,
- sqlParameterExpression,
- [ValuesOrderingColumnName, ValuesValueColumnName]);
- return CreateShapedQueryExpressionForValuesExpression(
- valuesExpression,
- tableAlias,
- parameterQueryRootExpression.ElementType,
- sqlParameterExpression.TypeMapping,
- sqlParameterExpression.IsNullable);
- }
-
- return TranslatePrimitiveCollection(sqlParameterExpression, property: null, tableAlias);
+ return (queryParameter.TranslationMode ?? _collectionParameterTranslationMode) switch
+ {
+ ParameterTranslationMode.Constant or ParameterTranslationMode.MultipleParameters
+ => CreateShapedQueryExpressionForValuesExpression(
+ new ValuesExpression(
+ tableAlias,
+ sqlParameterExpression,
+ [ValuesOrderingColumnName, ValuesValueColumnName]),
+ tableAlias,
+ parameterQueryRootExpression.ElementType,
+ sqlParameterExpression.TypeMapping,
+ sqlParameterExpression.IsNullable),
+
+ ParameterTranslationMode.Parameter
+ => TranslatePrimitiveCollection(sqlParameterExpression, property: null, tableAlias),
+
+ _ => throw new UnreachableException()
+ };
}
///
diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs
index aec6e3cc14d..dbbe1c92358 100644
--- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs
@@ -19,7 +19,7 @@ public partial class RelationalShapedQueryCompilingExpressionVisitor : ShapedQue
private readonly bool _threadSafetyChecksEnabled;
private readonly bool _detailedErrorsEnabled;
private readonly bool _useRelationalNulls;
- private readonly ParameterizedCollectionMode _parameterizedCollectionMode;
+ private readonly ParameterTranslationMode _collectionParameterTranslationMode;
private readonly bool _isPrecompiling;
private readonly RelationalParameterBasedSqlProcessor _relationalParameterBasedSqlProcessor;
@@ -55,7 +55,7 @@ public RelationalShapedQueryCompilingExpressionVisitor(
_relationalParameterBasedSqlProcessor =
relationalDependencies.RelationalParameterBasedSqlProcessorFactory.Create(
- new RelationalParameterBasedSqlProcessorParameters(_useRelationalNulls, _parameterizedCollectionMode));
+ new RelationalParameterBasedSqlProcessorParameters(_useRelationalNulls, _collectionParameterTranslationMode));
_querySqlGeneratorFactory = relationalDependencies.QuerySqlGeneratorFactory;
_contextType = queryCompilationContext.ContextType;
@@ -63,7 +63,7 @@ public RelationalShapedQueryCompilingExpressionVisitor(
_threadSafetyChecksEnabled = dependencies.CoreSingletonOptions.AreThreadSafetyChecksEnabled;
_detailedErrorsEnabled = dependencies.CoreSingletonOptions.AreDetailedErrorsEnabled;
_useRelationalNulls = RelationalOptionsExtension.Extract(queryCompilationContext.ContextOptions).UseRelationalNulls;
- _parameterizedCollectionMode = RelationalOptionsExtension.Extract(queryCompilationContext.ContextOptions).ParameterizedCollectionMode;
+ _collectionParameterTranslationMode = RelationalOptionsExtension.Extract(queryCompilationContext.ContextOptions).ParameterizedCollectionMode;
_isPrecompiling = queryCompilationContext.IsPrecompiling;
}
@@ -500,7 +500,7 @@ private Expression CreateRelationalCommandResolverExpression(Expression queryExp
RelationalDependencies.RelationalParameterBasedSqlProcessorFactory,
queryExpression,
_useRelationalNulls,
- _parameterizedCollectionMode);
+ _collectionParameterTranslationMode);
var commandLiftableConstant = RelationalDependencies.RelationalLiftableConstantFactory.CreateLiftableConstant(
relationalCommandCache,
@@ -751,7 +751,7 @@ Expression> Generate
_relationalDependenciesRelationalParameterBasedSqlProcessorFactoryProperty),
Constant(queryExpression),
Constant(_useRelationalNulls),
- Constant(_parameterizedCollectionMode, typeof(ParameterizedCollectionMode))),
+ Constant(_collectionParameterTranslationMode, typeof(ParameterTranslationMode))),
contextParameter);
}
}
diff --git a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs
index c8f4ec36332..fb89e8c214e 100644
--- a/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalSqlTranslatingExpressionVisitor.cs
@@ -516,7 +516,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
name: queryParameter.Name,
queryParameter.Type,
nullable: false,
- queryParameter.ShouldBeConstantized,
+ queryParameter.TranslationMode,
typeMapping: null);
}
@@ -525,7 +525,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
name: queryParameter.Name,
queryParameter.Type,
queryParameter.Type.IsNullableType(),
- queryParameter.ShouldBeConstantized,
+ queryParameter.TranslationMode,
typeMapping: null);
case StructuralTypeShaperExpression shaper:
diff --git a/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs
index 48c1602fbaa..a279f21889f 100644
--- a/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs
+++ b/src/EFCore.Relational/Query/SqlExpressions/SqlParameterExpression.cs
@@ -17,7 +17,7 @@ public sealed class SqlParameterExpression : SqlExpression
/// The of the expression.
/// The associated with the expression.
public SqlParameterExpression(string name, Type type, RelationalTypeMapping? typeMapping)
- : this(invariantName: name, name: name, type.UnwrapNullableType(), type.IsNullableType(), shouldBeConstantized: false, typeMapping)
+ : this(invariantName: name, name: name, type.UnwrapNullableType(), type.IsNullableType(), translationMode: null, typeMapping)
{
}
@@ -31,21 +31,21 @@ public SqlParameterExpression(string name, Type type, RelationalTypeMapping? typ
///
/// The of the expression.
/// Whether this parameter can have null values.
- /// Whether the user has indicated that this query parameter should be inlined as a constant.
+ /// How the parameter should be handled.
/// The associated with the expression.
public SqlParameterExpression(
string invariantName,
string name,
Type type,
bool nullable,
- bool shouldBeConstantized,
+ ParameterTranslationMode? translationMode,
RelationalTypeMapping? typeMapping)
: base(type.UnwrapNullableType(), typeMapping)
{
InvariantName = invariantName;
Name = name;
IsNullable = nullable;
- ShouldBeConstantized = shouldBeConstantized;
+ TranslationMode = translationMode;
}
///
@@ -65,9 +65,9 @@ public SqlParameterExpression(
public bool IsNullable { get; }
///
- /// Whether the user has indicated that this query parameter should be inlined as a constant.
+ /// How the parameter should be handled.
///
- public bool ShouldBeConstantized { get; }
+ public ParameterTranslationMode? TranslationMode { get; }
///
/// Applies supplied type mapping to this expression.
@@ -75,7 +75,7 @@ public SqlParameterExpression(
/// A relational type mapping to apply.
/// A new expression which has supplied type mapping.
public SqlExpression ApplyTypeMapping(RelationalTypeMapping? typeMapping)
- => new SqlParameterExpression(InvariantName, Name, Type, IsNullable, ShouldBeConstantized, typeMapping);
+ => new SqlParameterExpression(InvariantName, Name, Type, IsNullable, TranslationMode, typeMapping);
///
protected override Expression VisitChildren(ExpressionVisitor visitor)
diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
index c4d182cb886..190d56525b8 100644
--- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
+++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
@@ -4,7 +4,6 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
-using Microsoft.EntityFrameworkCore.Storage;
namespace Microsoft.EntityFrameworkCore.Query;
@@ -40,7 +39,7 @@ public SqlNullabilityProcessor(
{
Dependencies = dependencies;
UseRelationalNulls = parameters.UseRelationalNulls;
- ParameterizedCollectionMode = parameters.ParameterizedCollectionMode;
+ CollectionParameterTranslationMode = parameters.CollectionParameterTranslationMode;
_sqlExpressionFactory = dependencies.SqlExpressionFactory;
_nonNullableColumns = [];
@@ -62,7 +61,7 @@ public SqlNullabilityProcessor(
///
/// A value indicating what translation mode to use.
///
- public virtual ParameterizedCollectionMode ParameterizedCollectionMode { get; }
+ public virtual ParameterTranslationMode CollectionParameterTranslationMode { get; }
///
/// Dictionary of current parameter values in use.
@@ -127,10 +126,9 @@ protected override Expression VisitExtension(Expression node)
var processedValues = new List();
- switch (ParameterizedCollectionMode)
+ switch (valuesParameter.TranslationMode ?? CollectionParameterTranslationMode)
{
- case ParameterizedCollectionMode.MultipleParameters
- when !valuesParameter.ShouldBeConstantized:
+ case ParameterTranslationMode.MultipleParameters:
{
var expandedParameters = _collectionParameterExpansionMap.GetOrAddNew(valuesParameter);
for (var i = 0; i < values.Count; i++)
@@ -156,11 +154,7 @@ protected override Expression VisitExtension(Expression node)
break;
}
- case ParameterizedCollectionMode.Constants:
- case ParameterizedCollectionMode.Parameter
- when valuesParameter.ShouldBeConstantized:
- case ParameterizedCollectionMode.MultipleParameters
- when valuesParameter.ShouldBeConstantized:
+ case ParameterTranslationMode.Constant:
{
foreach (var value in values)
{
@@ -820,18 +814,6 @@ InExpression ProcessInExpressionValues(
processedValues = [];
- var useParameters = ParameterizedCollectionMode is ParameterizedCollectionMode.MultipleParameters
- && !valuesParameter.ShouldBeConstantized;
- var useConstants =
- ParameterizedCollectionMode is ParameterizedCollectionMode.Constants
- ||
- (ParameterizedCollectionMode is ParameterizedCollectionMode.Parameter
- && valuesParameter.ShouldBeConstantized)
- ||
- (ParameterizedCollectionMode is ParameterizedCollectionMode.MultipleParameters
- && valuesParameter.ShouldBeConstantized);
- var useParameter = ParameterizedCollectionMode is ParameterizedCollectionMode.Parameter
- && !valuesParameter.ShouldBeConstantized;
var expandedParameters = _collectionParameterExpansionMap.GetOrAddNew(valuesParameter);
var expandedParametersCounter = 0;
for (var i = 0; i < values.Count; i++)
@@ -842,11 +824,11 @@ ParameterizedCollectionMode is ParameterizedCollectionMode.Constants
continue;
}
- switch (useParameters, useConstants, useParameter)
+ switch (valuesParameter.TranslationMode ?? CollectionParameterTranslationMode)
{
- case (true, false, false):
+ case ParameterTranslationMode.MultipleParameters:
// see #36311 for more info
- case (false, false, true):
+ case ParameterTranslationMode.Parameter:
{
// Create parameter for value if we didn't create it yet,
// otherwise reuse it.
@@ -864,7 +846,7 @@ ParameterizedCollectionMode is ParameterizedCollectionMode.Constants
break;
}
- case (false, true, false):
+ case ParameterTranslationMode.Constant:
{
processedValues.Add(_sqlExpressionFactory.Constant(values[i], values[i]?.GetType() ?? typeof(object), sensitive: true, elementTypeMapping));
@@ -1426,7 +1408,7 @@ protected virtual SqlExpression VisitSqlParameter(
nullable = false;
- if (sqlParameterExpression.ShouldBeConstantized)
+ if (sqlParameterExpression.TranslationMode is ParameterTranslationMode.Constant)
{
var parameters = ParametersFacade.GetParametersAndDisableSqlCaching();
diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs
index 8a97b4514f7..7c89b8987b9 100644
--- a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs
@@ -190,8 +190,7 @@ protected override Expression VisitExtension(Expression node)
switch (node)
{
case ValuesExpression { ValuesParameter: SqlParameterExpression valuesParameter } valuesExpression
- when ParameterizedCollectionMode is ParameterizedCollectionMode.MultipleParameters
- && !valuesParameter.ShouldBeConstantized:
+ when (valuesParameter.TranslationMode ?? CollectionParameterTranslationMode) is ParameterTranslationMode.MultipleParameters:
{
Check.DebugAssert(valuesParameter.TypeMapping is not null);
Check.DebugAssert(valuesParameter.TypeMapping.ElementTypeMapping is not null);
@@ -237,8 +236,7 @@ protected override SqlExpression VisitIn(InExpression inExpression, bool allowOp
switch (inExpression.ValuesParameter)
{
case SqlParameterExpression valuesParameter
- when ParameterizedCollectionMode is ParameterizedCollectionMode.MultipleParameters
- && !valuesParameter.ShouldBeConstantized:
+ when (valuesParameter.TranslationMode ?? CollectionParameterTranslationMode) is ParameterTranslationMode.MultipleParameters:
{
Check.DebugAssert(valuesParameter.TypeMapping is not null);
Check.DebugAssert(valuesParameter.TypeMapping.ElementTypeMapping is not null);
diff --git a/src/EFCore.Relational/ParameterizedCollectionMode.cs b/src/EFCore/ParameterTranslationMode.cs
similarity index 88%
rename from src/EFCore.Relational/ParameterizedCollectionMode.cs
rename to src/EFCore/ParameterTranslationMode.cs
index 89f48bea90a..964a53688e9 100644
--- a/src/EFCore.Relational/ParameterizedCollectionMode.cs
+++ b/src/EFCore/ParameterTranslationMode.cs
@@ -6,8 +6,21 @@ namespace Microsoft.EntityFrameworkCore;
///
/// Indicates how parameterized collections are translated into SQL.
///
-public enum ParameterizedCollectionMode
+public enum ParameterTranslationMode
{
+ ///
+ /// Instructs EF to translate the collection to a set of parameters:
+ /// WHERE [x].[Id] IN (@ids1, @ids2, @ids3).
+ ///
+ ///
+ ///
+ /// Note that it's possible to cause EF to translate a specific collection in a specific query to parameter by wrapping the
+ /// parameterized collection in EF.MultipleParameters: Where(x => EF.MultipleParameters(ids).Contains(x.Id). This overrides
+ /// the default.
+ ///
+ ///
+ MultipleParameters = 0,
+
///
/// Instructs EF to translate the collection to a set of constants:
/// WHERE [x].[Id] IN (1, 2, 3).
@@ -23,7 +36,7 @@ public enum ParameterizedCollectionMode
/// the default.
///
///
- Constants,
+ Constant = 1,
///
/// Instructs EF to translate the collection to a single array-like parameter:
@@ -39,18 +52,5 @@ public enum ParameterizedCollectionMode
/// the default.
///
///
- Parameter,
-
- ///
- /// Instructs EF to translate the collection to a set of parameters:
- /// WHERE [x].[Id] IN (@ids1, @ids2, @ids3).
- ///
- ///
- ///
- /// Note that it's possible to cause EF to translate a specific collection in a specific query to parameter by wrapping the
- /// parameterized collection in : Where(x => EF.MultipleParameters(ids).Contains(x.Id). This overrides
- /// the default.
- ///
- ///
- MultipleParameters,
+ Parameter = 2,
}
diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs
index 61fb66db70f..9032f29286c 100644
--- a/src/EFCore/Properties/CoreStrings.Designer.cs
+++ b/src/EFCore/Properties/CoreStrings.Designer.cs
@@ -358,14 +358,6 @@ public static string CanOnlyConfigureExistingNavigations(object? navigationName,
GetString("CanOnlyConfigureExistingNavigations", "0_navigationName", "1_entityType"),
navigationName, entityType);
- ///
- /// The entity type '{entityType}' is configured to use the '{changeTrackingStrategy}' change tracking strategy, but does not implement the required '{notificationInterface}' interface. Implement '{notificationInterface}' on '{entityType}' or use a different change tracking strategy.
- ///
- public static string ChangeTrackingInterfaceMissing(object? entityType, object? changeTrackingStrategy, object? notificationInterface)
- => string.Format(
- GetString("ChangeTrackingInterfaceMissing", nameof(entityType), nameof(changeTrackingStrategy), nameof(notificationInterface)),
- entityType, changeTrackingStrategy, notificationInterface);
-
///
/// Unable to save changes because a circular dependency was detected in the data to be saved: '{cycle}'.
///
@@ -1013,10 +1005,12 @@ public static string EFConstantNotSupportedInPrecompiledQueries
=> GetString("EFConstantNotSupportedInPrecompiledQueries");
///
- /// The EF.Constant<T> method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities.
+ /// The {methodName} method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities.
///
- public static string EFConstantWithNonEvaluatableArgument
- => GetString("EFConstantWithNonEvaluatableArgument");
+ public static string EFMethodWithNonEvaluatableArgument(object? methodName)
+ => string.Format(
+ GetString("EFMethodWithNonEvaluatableArgument", nameof(methodName)),
+ methodName);
///
/// The EF.Parameter<T> method may only be used within Entity Framework LINQ queries.
@@ -1024,12 +1018,6 @@ public static string EFConstantWithNonEvaluatableArgument
public static string EFParameterInvoked
=> GetString("EFParameterInvoked");
- ///
- /// The EF.Parameter<T> method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities.
- ///
- public static string EFParameterWithNonEvaluatableArgument
- => GetString("EFParameterWithNonEvaluatableArgument");
-
///
/// Complex type '{complexType}' has no properties defines. Configure at least one property or don't include this type in the model.
///
@@ -1340,6 +1328,14 @@ public static string GraphDoesNotContainVertex(object? vertex)
public static string HiLoBadBlockSize
=> GetString("HiLoBadBlockSize");
+ ///
+ /// The entity type '{entityType}' is configured to use the '{changeTrackingStrategy}' change tracking strategy, but does not implement the required '{notificationInterface}' interface. Implement '{notificationInterface}' on '{entityType}' or use a different change tracking strategy.
+ ///
+ public static string ChangeTrackingInterfaceMissing(object? entityType, object? changeTrackingStrategy, object? notificationInterface)
+ => string.Format(
+ GetString("ChangeTrackingInterfaceMissing", nameof(entityType), nameof(changeTrackingStrategy), nameof(notificationInterface)),
+ entityType, changeTrackingStrategy, notificationInterface);
+
///
/// A relationship cycle involving the primary keys of the following entity types was detected: '{entityType}'. This would prevent any entity to be inserted without violating the store constraints. Review the foreign keys defined on the primary keys and either remove or use other properties for at least one of them.
///
diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx
index 9629cf01e81..1656f994a0e 100644
--- a/src/EFCore/Properties/CoreStrings.resx
+++ b/src/EFCore/Properties/CoreStrings.resx
@@ -243,9 +243,6 @@
Navigation '{1_entityType}.{0_navigationName}' was not found. Please add the navigation to the entity type before configuring it.
-
- The entity type '{entityType}' is configured to use the '{changeTrackingStrategy}' change tracking strategy, but does not implement the required '{notificationInterface}' interface. Implement '{notificationInterface}' on '{entityType}' or use a different change tracking strategy.
-
Unable to save changes because a circular dependency was detected in the data to be saved: '{cycle}'.
@@ -495,15 +492,12 @@
The EF.Constant<T> method is not supported when using precompiled queries.
-
- The EF.Constant<T> method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities.
+
+ The {methodName} method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities.The EF.Parameter<T> method may only be used within Entity Framework LINQ queries.
-
- The EF.Parameter<T> method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities.
-
Complex type '{complexType}' has no properties defines. Configure at least one property or don't include this type in the model.
@@ -624,6 +618,9 @@
The block size used for Hi-Lo value generation is not positive. The Hi-Lo generator is usually backed by a SQL sequence and this means that the sequence increment must be positive.
+
+ The entity type '{entityType}' is configured to use the '{changeTrackingStrategy}' change tracking strategy, but does not implement the required '{notificationInterface}' interface. Implement '{notificationInterface}' on '{entityType}' or use a different change tracking strategy.
+
A relationship cycle involving the primary keys of the following entity types was detected: '{entityType}'. This would prevent any entity to be inserted without violating the store constraints. Review the foreign keys defined on the primary keys and either remove or use other properties for at least one of them.
diff --git a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs
index 44fdec7dbc0..afb319d52c0 100644
--- a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs
+++ b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs
@@ -939,7 +939,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall)
if (!argumentState.IsEvaluatable)
{
- throw new InvalidOperationException(CoreStrings.EFConstantWithNonEvaluatableArgument);
+ throw new InvalidOperationException(CoreStrings.EFMethodWithNonEvaluatableArgument("EF.Constant"));
}
// Even EF.Constant will be parameter here.
@@ -952,21 +952,17 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall)
case nameof(EF.Parameter):
{
- var argument = Visit(methodCall.Arguments[0], out var argumentState);
-
- if (!argumentState.IsEvaluatable)
- {
- throw new InvalidOperationException(CoreStrings.EFParameterWithNonEvaluatableArgument);
- }
-
- argumentState = argumentState with { StateType = StateType.EvaluatableWithCapturedVariable };
- var evaluatedArgument = ProcessEvaluatableRoot(argument, ref argumentState, forceEvaluation: true);
- _state = argumentState;
- return Call(method, evaluatedArgument);
+ return HandleParameter(methodCall, "EF.Parameter");
}
}
}
+ // EF.MultipleParameters is defined in Relational, hence the hardcoded values here.
+ if (method is { Name: "MultipleParameters", DeclaringType.FullName: "Microsoft.EntityFrameworkCore.EFExtensions" })
+ {
+ return HandleParameter(methodCall, "EF.MultipleParameters");
+ }
+
// .NET 10 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans").
// Unfortunately, the LINQ interpreter does not support ref structs, so we rewrite e.g. MemoryExtensions.Contains to
// Enumerable.Contains here. See https://github.com/dotnet/runtime/issues/109757,.
@@ -1116,6 +1112,21 @@ static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)]
}
return methodCall.Update(@object, ((IReadOnlyList?)arguments) ?? methodCall.Arguments);
+
+ Expression HandleParameter(MethodCallExpression methodCall, string methodName)
+ {
+ var argument = Visit(methodCall.Arguments[0], out var argumentState);
+
+ if (!argumentState.IsEvaluatable)
+ {
+ throw new InvalidOperationException(CoreStrings.EFMethodWithNonEvaluatableArgument(methodName));
+ }
+
+ argumentState = argumentState with { StateType = StateType.EvaluatableWithCapturedVariable };
+ var evaluatedArgument = ProcessEvaluatableRoot(argument, ref argumentState, forceEvaluation: true);
+ _state = argumentState;
+ return Call(methodCall.Method, evaluatedArgument);
+ }
}
///
@@ -1977,8 +1988,7 @@ private static StateType CombineStateTypes(StateType stateType1, StateType state
return _parameterizedValues[evaluatableRoot] = new QueryParameterExpression(
parameterName,
evaluatableRoot.Type,
- shouldBeConstantized: false,
- shouldNotBeConstantized: false,
+ translationMode: null,
isNonNullableReferenceType);
}
diff --git a/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs b/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs
index c89c7f3d357..067b9dd3cad 100644
--- a/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs
+++ b/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs
@@ -128,20 +128,23 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
var queryParameter = (QueryParameterExpression)Visit(methodCallExpression.Arguments[0]);
return new QueryParameterExpression(
- queryParameter.Name, queryParameter.Type, shouldBeConstantized: true, shouldNotBeConstantized: false,
+ queryParameter.Name, queryParameter.Type, translationMode: ParameterTranslationMode.Constant,
queryParameter.IsNonNullableReferenceType);
}
case nameof(EF.Parameter):
{
- var queryParameter = (QueryParameterExpression)Visit(methodCallExpression.Arguments[0]);
- return new QueryParameterExpression(
- queryParameter.Name, queryParameter.Type, shouldBeConstantized: false, shouldNotBeConstantized: true,
- queryParameter.IsNonNullableReferenceType);
+ return HandleParameter(methodCallExpression, ParameterTranslationMode.Parameter);
}
}
}
+ // EF.MultipleParameters is defined in Relational, hence the hardcoded values here.
+ if (method is { Name: "MultipleParameters", DeclaringType.FullName: "Microsoft.EntityFrameworkCore.EFExtensions" })
+ {
+ return HandleParameter(methodCallExpression, ParameterTranslationMode.MultipleParameters);
+ }
+
// Normalize list[x] to list.ElementAt(x)
if (methodCallExpression is
{
@@ -237,6 +240,14 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
}
return visitedExpression;
+
+ Expression HandleParameter(MethodCallExpression methodCallExpression, ParameterTranslationMode parameterTranslationMode)
+ {
+ var queryParameter = (QueryParameterExpression)Visit(methodCallExpression.Arguments[0]);
+ return new QueryParameterExpression(
+ queryParameter.Name, queryParameter.Type, parameterTranslationMode,
+ queryParameter.IsNonNullableReferenceType);
+ }
}
private static void VerifyReturnType(Expression expression, ParameterExpression lambdaParameter)
diff --git a/src/EFCore/Query/QueryParameterExpression.cs b/src/EFCore/Query/QueryParameterExpression.cs
index 696ff1f3b16..dde4c03c09f 100644
--- a/src/EFCore/Query/QueryParameterExpression.cs
+++ b/src/EFCore/Query/QueryParameterExpression.cs
@@ -14,31 +14,30 @@ namespace Microsoft.EntityFrameworkCore.Query;
public class QueryParameterExpression : Expression, IPrintableExpression
{
///
- /// Creates a new instance of the class with associated query provider.
+ /// Creates a new instance of the class with associated query provider.
///
public QueryParameterExpression(string name, Type type)
- : this(name, type, shouldBeConstantized: false, shouldNotBeConstantized: false, isNonNullableReferenceType: false)
+ : this(name, type, translationMode: null, isNonNullableReferenceType: false)
{
}
///
- /// Creates a new instance of the class with associated query provider.
+ /// Creates a new instance of the class with associated query provider.
///
- public QueryParameterExpression(string name, Type type, bool shouldBeConstantized, bool shouldNotBeConstantized)
- : this(name, type, shouldBeConstantized, shouldNotBeConstantized, isNonNullableReferenceType: false)
+ public QueryParameterExpression(string name, Type type, ParameterTranslationMode translationMode)
+ : this(name, type, translationMode, isNonNullableReferenceType: false)
{
}
///
- /// Creates a new instance of the class with associated query provider.
+ /// Creates a new instance of the class with associated query provider.
///
[Experimental(EFDiagnostics.PrecompiledQueryExperimental)]
- public QueryParameterExpression(string name, Type type, bool shouldBeConstantized, bool shouldNotBeConstantized, bool isNonNullableReferenceType)
+ public QueryParameterExpression(string name, Type type, ParameterTranslationMode? translationMode, bool isNonNullableReferenceType)
{
Name = name;
Type = type;
- ShouldBeConstantized = shouldBeConstantized;
- ShouldNotBeConstantized = shouldNotBeConstantized;
+ TranslationMode = translationMode;
IsNonNullableReferenceType = isNonNullableReferenceType;
}
@@ -61,14 +60,9 @@ public QueryParameterExpression(string name, Type type, bool shouldBeConstantize
public virtual bool IsNonNullableReferenceType { get; }
///
- /// Whether the user has indicated that this query parameter should be inlined as a constant.
+ /// How should the parameter be handled.
///
- public virtual bool ShouldBeConstantized { get; }
-
- ///
- /// Whether the user has indicated that this query parameter shouldn't be inlined as a constant.
- ///
- public virtual bool ShouldNotBeConstantized { get; }
+ public virtual ParameterTranslationMode? TranslationMode { get; }
///
public override ExpressionType NodeType
@@ -92,11 +86,10 @@ public override bool Equals(object? obj)
private bool Equals(QueryParameterExpression queryParameterExpression)
=> Name == queryParameterExpression.Name
&& Type == queryParameterExpression.Type
- && ShouldBeConstantized == queryParameterExpression.ShouldBeConstantized
- && ShouldNotBeConstantized == queryParameterExpression.ShouldNotBeConstantized
+ && TranslationMode == queryParameterExpression.TranslationMode
&& IsNonNullableReferenceType == queryParameterExpression.IsNonNullableReferenceType;
///
public override int GetHashCode()
- => HashCode.Combine(Name, Type, ShouldBeConstantized, ShouldNotBeConstantized, IsNonNullableReferenceType);
+ => HashCode.Combine(Name, Type, TranslationMode, IsNonNullableReferenceType);
}
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs
index cef8b19a3aa..3c3f5196465 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs
@@ -2339,8 +2339,8 @@ public override async Task EF_Constant_does_not_parameterized_as_part_of_bigger_
public override async Task EF_Constant_with_non_evaluatable_argument_throws(bool async)
{
await base.EF_Constant_with_non_evaluatable_argument_throws(async);
- AssertSql(
- );
+
+ AssertSql();
}
public override Task EF_Parameter(bool async)
diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs
index 2496b692e1d..a572b1d9090 100644
--- a/test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocMiscellaneousQueryRelationalTestBase.cs
@@ -20,6 +20,8 @@ protected void ClearLog()
protected void AssertSql(params string[] expected)
=> TestSqlLoggerFactory.AssertBaseline(expected);
+ protected abstract DbContextOptionsBuilder SetParameterizedCollectionMode(DbContextOptionsBuilder optionsBuilder, ParameterTranslationMode parameterizedCollectionMode);
+
#region 2951
[ConditionalFact]
@@ -268,8 +270,6 @@ public class Entity
#region Inlined redacting
- protected abstract DbContextOptionsBuilder SetParameterizedCollectionMode(DbContextOptionsBuilder optionsBuilder, ParameterizedCollectionMode parameterizedCollectionMode);
-
[ConditionalTheory]
[MemberData(nameof(InlinedRedactingData))]
public virtual async Task Check_inlined_constants_redacting(bool async, bool enableSensitiveDataLogging)
@@ -277,7 +277,7 @@ public virtual async Task Check_inlined_constants_redacting(bool async, bool ena
var contextFactory = await InitializeAsync(
onConfiguring: o =>
{
- SetParameterizedCollectionMode(o, ParameterizedCollectionMode.Constants);
+ SetParameterizedCollectionMode(o, ParameterTranslationMode.Constant);
o.EnableSensitiveDataLogging(enableSensitiveDataLogging);
});
using var context = contextFactory.CreateContext();
@@ -324,7 +324,7 @@ public class TestEntity
public async Task Entity_equality_with_Contains_and_Parameter(bool async)
{
var contextFactory = await InitializeAsync(
- onConfiguring: o => SetParameterizedCollectionMode(o, ParameterizedCollectionMode.Parameter));
+ onConfiguring: o => SetParameterizedCollectionMode(o, ParameterTranslationMode.Parameter));
using var context = contextFactory.CreateContext();
List details = [new Context36311.BlogDetails { Id = 1 }, new Context36311.BlogDetails { Id = 2 }];
diff --git a/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs
index 2cc15f6fafd..e5132067c77 100644
--- a/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs
@@ -1,6 +1,8 @@
// 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;
+
namespace Microsoft.EntityFrameworkCore.Query;
#nullable disable
@@ -12,7 +14,7 @@ public abstract class NonSharedPrimitiveCollectionsQueryRelationalTestBase(NonSh
public override Task Array_of_byte()
=> AssertTranslationFailed(() => TestArray((byte)1, (byte)2));
- protected abstract DbContextOptionsBuilder SetParameterizedCollectionMode(DbContextOptionsBuilder optionsBuilder, ParameterizedCollectionMode parameterizedCollectionMode);
+ protected abstract DbContextOptionsBuilder SetParameterizedCollectionMode(DbContextOptionsBuilder optionsBuilder, ParameterTranslationMode parameterizedCollectionMode);
[ConditionalFact]
public virtual async Task Column_collection_inside_json_owned_entity()
@@ -36,11 +38,15 @@ public virtual async Task Column_collection_inside_json_owned_entity()
Assert.Equivalent(new[] { "foo", "bar" }, result.Owned.Strings);
}
- [ConditionalFact]
- public virtual async Task Parameter_collection_Count_with_column_predicate_with_default_constants()
+ protected static IEnumerable