diff --git a/src/EFCore.Relational/Infrastructure/RelationalDbContextOptionsBuilder.cs b/src/EFCore.Relational/Infrastructure/RelationalDbContextOptionsBuilder.cs
index ee0e59414f7..79aa8424ed3 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalDbContextOptionsBuilder.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalDbContextOptionsBuilder.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
+using Microsoft.EntityFrameworkCore.Internal;
namespace Microsoft.EntityFrameworkCore.Infrastructure;
@@ -159,6 +160,54 @@ public virtual TBuilder ExecutionStrategy(
=> WithOption(
e => (TExtension)e.WithExecutionStrategyFactory(Check.NotNull(getExecutionStrategy, nameof(getExecutionStrategy))));
+ ///
+ /// Configures the context to translate parameterized collections to inline constants.
+ ///
+ ///
+ ///
+ /// When a LINQ query contains a parameterized collection, by default EF Core parameterizes the entire collection as a single
+ /// SQL parameter, if possible. For example, on SQL Server, the LINQ query Where(b => ids.Contains(b.Id) is translated to
+ /// WHERE [b].[Id] IN (SELECT [i].[value] FROM OPENJSON(@__ids_0) ...). While this helps with query plan caching, it can
+ /// produce worse query plans for certain query types.
+ ///
+ ///
+ /// instructs EF to translate the collection to a set of constants:
+ /// WHERE [b].[Id] IN (1, 2, 3). This can produce better query plans for certain query types, but can also lead to query
+ /// plan bloat.
+ ///
+ ///
+ /// Note that it's possible to cause EF to translate a specific collection in a specific query to constants by wrapping the
+ /// parameterized collection in : Where(b => EF.Constant(ids).Contains(b.Id). This overrides
+ /// the default.
+ ///
+ ///
+ public virtual TBuilder TranslateParameterizedCollectionsToConstants()
+ => WithOption(e => (TExtension)e.WithParameterizedCollectionTranslationMode(ParameterizedCollectionTranslationMode.Constantize));
+
+ ///
+ /// Configures the context to translate parameterized collections to inline constants.
+ ///
+ ///
+ ///
+ /// When a LINQ query contains a parameterized collection, by default EF Core parameterizes the entire collection as a single
+ /// SQL parameter, if possible. For example, on SQL Server, the LINQ query Where(b => ids.Contains(b.Id) is translated to
+ /// WHERE [b].[Id] IN (SELECT [i].[value] FROM OPENJSON(@__ids_0) ...). While this helps with query plan caching, it can
+ /// produce worse query plans for certain query types.
+ ///
+ ///
+ /// instructs EF to translate the collection to a set of constants:
+ /// WHERE [b].[Id] IN (1, 2, 3). This can produce better query plans for certain query types, but can also lead to query
+ /// plan bloat.
+ ///
+ ///
+ /// Note that it's possible to cause EF to translate a specific collection in a specific query to constants by wrapping the
+ /// parameterized collection in : Where(b => EF.Constant(ids).Contains(b.Id). This overrides
+ /// the default.
+ ///
+ ///
+ public virtual TBuilder TranslateParameterizedCollectionsToParameters()
+ => WithOption(e => (TExtension)e.WithParameterizedCollectionTranslationMode(ParameterizedCollectionTranslationMode.Parameterize));
+
///
/// Sets an option by cloning the extension used to store the settings. This ensures the builder
/// does not modify options that are already in use elsewhere.
diff --git a/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs b/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs
index f8adafd96d9..7309db9eea5 100644
--- a/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs
+++ b/src/EFCore.Relational/Infrastructure/RelationalOptionsExtension.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text;
+using Microsoft.EntityFrameworkCore.Internal;
namespace Microsoft.EntityFrameworkCore.Infrastructure;
@@ -33,10 +34,10 @@ public abstract class RelationalOptionsExtension : IDbContextOptionsExtension
private QuerySplittingBehavior? _querySplittingBehavior;
private string? _migrationsAssembly;
private Assembly? _migrationsAssemblyObject;
-
private string? _migrationsHistoryTableName;
private string? _migrationsHistoryTableSchema;
private Func? _executionStrategyFactory;
+ private ParameterizedCollectionTranslationMode? _parameterizedCollectionTranslationMode;
///
/// Creates a new set of options with everything set to default values.
@@ -63,6 +64,7 @@ protected RelationalOptionsExtension(RelationalOptionsExtension copyFrom)
_migrationsHistoryTableName = copyFrom._migrationsHistoryTableName;
_migrationsHistoryTableSchema = copyFrom._migrationsHistoryTableSchema;
_executionStrategyFactory = copyFrom._executionStrategyFactory;
+ _parameterizedCollectionTranslationMode = copyFrom._parameterizedCollectionTranslationMode;
}
///
@@ -381,6 +383,26 @@ public virtual RelationalOptionsExtension WithExecutionStrategyFactory(
return clone;
}
+ ///
+ /// Configured translation mode for parameterized collections.
+ ///
+ public virtual ParameterizedCollectionTranslationMode? ParameterizedCollectionTranslationMode
+ => _parameterizedCollectionTranslationMode;
+
+ ///
+ /// Creates a new instance with all options the same as for this instance, but with the given option changed.
+ /// It is unusual to call this method directly. Instead use .
+ ///
+ /// The option to change.
+ public virtual RelationalOptionsExtension WithParameterizedCollectionTranslationMode(ParameterizedCollectionTranslationMode parameterizedCollectionTranslationMode)
+ {
+ var clone = Clone();
+
+ clone._parameterizedCollectionTranslationMode = parameterizedCollectionTranslationMode;
+
+ return clone;
+ }
+
///
/// Finds an existing registered on the given options
/// or throws if none has been registered. This is typically used to find some relational
@@ -540,6 +562,11 @@ public override string LogFragment
builder.Append(Extension._migrationsHistoryTableName ?? HistoryRepository.DefaultTableName).Append(' ');
}
+ if (Extension._parameterizedCollectionTranslationMode != null)
+ {
+ builder.Append("ParameterizedCollectionTranslationMode=").Append(Extension._parameterizedCollectionTranslationMode).Append(' ');
+ }
+
_logFragment = builder.ToString();
}
diff --git a/src/EFCore.Relational/Internal/ParameterizedCollectionTranslationMode.cs b/src/EFCore.Relational/Internal/ParameterizedCollectionTranslationMode.cs
new file mode 100644
index 00000000000..cfb59de6f08
--- /dev/null
+++ b/src/EFCore.Relational/Internal/ParameterizedCollectionTranslationMode.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.EntityFrameworkCore.Internal;
+
+///
+/// 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
+/// any release. You should only use it directly in your code with extreme caution and knowing that
+/// doing so can result in application failures when updating to a new Entity Framework Core release.
+///
+public enum ParameterizedCollectionTranslationMode
+{
+ ///
+ /// 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
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ Constantize = 0,
+
+ ///
+ /// 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
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ Parameterize,
+}
diff --git a/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs b/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs
index 19a8c30bdb3..005287f3348 100644
--- a/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs
+++ b/src/EFCore.Relational/Query/Internal/RelationalCommandCache.cs
@@ -36,7 +36,7 @@ public RelationalCommandCache(
IRelationalParameterBasedSqlProcessorFactory relationalParameterBasedSqlProcessorFactory,
Expression queryExpression,
bool useRelationalNulls,
- HashSet parametersToConstantize)
+ IReadOnlySet parametersToConstantize)
{
_memoryCache = memoryCache;
_querySqlGeneratorFactory = querySqlGeneratorFactory;
diff --git a/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessorParameters.cs b/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessorParameters.cs
index bbd689d9ab9..5ae6f011a46 100644
--- a/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessorParameters.cs
+++ b/src/EFCore.Relational/Query/RelationalParameterBasedSqlProcessorParameters.cs
@@ -16,7 +16,7 @@ public sealed record RelationalParameterBasedSqlProcessorParameters
///
/// A collection of parameter names to constantize.
///
- public HashSet ParametersToConstantize { get; init; }
+ public IReadOnlySet ParametersToConstantize { get; init; }
///
/// Creates a new instance of .
@@ -24,7 +24,7 @@ public sealed record RelationalParameterBasedSqlProcessorParameters
/// A value indicating if relational nulls should be used.
/// A collection of parameter names to constantize.
[EntityFrameworkInternal]
- public RelationalParameterBasedSqlProcessorParameters(bool useRelationalNulls, HashSet parametersToConstantize)
+ public RelationalParameterBasedSqlProcessorParameters(bool useRelationalNulls, IReadOnlySet parametersToConstantize)
{
UseRelationalNulls = useRelationalNulls;
ParametersToConstantize = parametersToConstantize;
diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
index a98c0d070c1..141ac9b515c 100644
--- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
+using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Query.Internal;
@@ -242,7 +243,8 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
&& method.GetGenericMethodDefinition() == QueryableMethods.Contains
&& methodCallExpression.Arguments[0] is ParameterQueryRootExpression parameterSource
&& TranslateExpression(methodCallExpression.Arguments[1]) is SqlExpression item
- && _sqlTranslator.Visit(parameterSource.ParameterExpression) is SqlParameterExpression sqlParameterExpression)
+ && _sqlTranslator.Visit(parameterSource.ParameterExpression) is SqlParameterExpression sqlParameterExpression
+ && !QueryCompilationContext.ParametersToNotConstantize.Contains(sqlParameterExpression.Name))
{
var inExpression = _sqlExpressionFactory.In(item, sqlParameterExpression);
var selectExpression = new SelectExpression(inExpression, _sqlAliasManager);
@@ -294,9 +296,13 @@ JsonScalarExpression jsonScalar
Check.DebugAssert(sqlParameterExpression is not null, "sqlParameterExpression is not null");
- var tableAlias = _sqlAliasManager.GenerateTableAlias(sqlParameterExpression.Name.TrimStart('_'));
+ var primitiveCollectionsBehavior = RelationalOptionsExtension.Extract(QueryCompilationContext.ContextOptions)
+ .ParameterizedCollectionTranslationMode;
- if (QueryCompilationContext.ParametersToConstantize.Contains(sqlParameterExpression.Name))
+ var tableAlias = _sqlAliasManager.GenerateTableAlias(sqlParameterExpression.Name.TrimStart('_'));
+ if (QueryCompilationContext.ParametersToConstantize.Contains(sqlParameterExpression.Name)
+ || (primitiveCollectionsBehavior == ParameterizedCollectionTranslationMode.Constantize
+ && !QueryCompilationContext.ParametersToNotConstantize.Contains(sqlParameterExpression.Name)))
{
var valuesExpression = new ValuesExpression(
tableAlias,
diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs
index 494399d8d0b..9934844e44c 100644
--- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs
@@ -14,7 +14,7 @@ namespace Microsoft.EntityFrameworkCore.Query;
///
public partial class RelationalShapedQueryCompilingExpressionVisitor : ShapedQueryCompilingExpressionVisitor
{
- private readonly HashSet _parametersToConstantize;
+ private readonly IReadOnlySet _parametersToConstantize;
private readonly Type _contextType;
private readonly ISet _tags;
private readonly bool _threadSafetyChecksEnabled;
@@ -53,7 +53,7 @@ public RelationalShapedQueryCompilingExpressionVisitor(
{
RelationalDependencies = relationalDependencies;
- _parametersToConstantize = QueryCompilationContext.ParametersToConstantize;
+ _parametersToConstantize = (IReadOnlySet)QueryCompilationContext.ParametersToConstantize;
_relationalParameterBasedSqlProcessor =
relationalDependencies.RelationalParameterBasedSqlProcessorFactory.Create(new RelationalParameterBasedSqlProcessorParameters(_useRelationalNulls, _parametersToConstantize));
diff --git a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
index 3b740ca419e..b0e5a8429e0 100644
--- a/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
+++ b/src/EFCore.Relational/Query/SqlNullabilityProcessor.cs
@@ -56,7 +56,7 @@ public SqlNullabilityProcessor(
///
/// A collection of parameter names to constantize.
///
- protected virtual HashSet ParametersToConstantize { get; }
+ protected virtual IReadOnlySet ParametersToConstantize { get; }
///
/// Dictionary of current parameter values in use.
diff --git a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs
index 492635b383d..455e147dbf9 100644
--- a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs
+++ b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs
@@ -944,9 +944,9 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall)
}
argumentState = argumentState with { StateType = StateType.EvaluatableWithCapturedVariable };
- var evaluatedArgument = ProcessEvaluatableRoot(argument, ref argumentState);
+ var evaluatedArgument = ProcessEvaluatableRoot(argument, ref argumentState, forceEvaluation: true);
_state = argumentState;
- return evaluatedArgument;
+ return Call(method, evaluatedArgument);
}
}
}
@@ -1827,7 +1827,7 @@ private static StateType CombineStateTypes(StateType stateType1, StateType state
}
[return: NotNullIfNotNull(nameof(evaluatableRoot))]
- private Expression? ProcessEvaluatableRoot(Expression? evaluatableRoot, ref State state)
+ private Expression? ProcessEvaluatableRoot(Expression? evaluatableRoot, ref State state, bool forceEvaluation = false)
{
if (evaluatableRoot is null)
{
@@ -1849,7 +1849,7 @@ private static StateType CombineStateTypes(StateType stateType1, StateType state
// We have some cases where a node is evaluatable, but only as part of a larger subtree, and should not be evaluated as a tree root.
// For these cases, the node's state has a notEvaluatableAsRootHandler lambda, which we can invoke to make evaluate the node's
// children (as needed), but not itself.
- if (TryHandleNonEvaluatableAsRoot(evaluatableRoot, state, evaluateAsParameter, out var result))
+ if (!forceEvaluation && TryHandleNonEvaluatableAsRoot(evaluatableRoot, state, evaluateAsParameter, out var result))
{
return result;
}
diff --git a/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs b/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs
index 140637f03cf..38c7dd9f0bb 100644
--- a/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs
+++ b/src/EFCore/Query/Internal/QueryableMethodNormalizingExpressionVisitor.cs
@@ -112,17 +112,29 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
return expression;
}
- if (method.DeclaringType == typeof(EF)
- && method.Name == nameof(EF.Constant))
+ if (method.DeclaringType == typeof(EF))
{
- if (!_isEfConstantSupported)
+ switch (method.Name)
{
- throw new InvalidOperationException(CoreStrings.EFConstantNotSupported);
- }
+ case nameof(EF.Constant):
+ {
+ if (!_isEfConstantSupported)
+ {
+ throw new InvalidOperationException(CoreStrings.EFConstantNotSupported);
+ }
- var parameterExpression = (ParameterExpression)Visit(methodCallExpression.Arguments[0]);
- _queryCompilationContext.ParametersToConstantize.Add(parameterExpression.Name!);
- return parameterExpression;
+ var parameterExpression = (ParameterExpression)Visit(methodCallExpression.Arguments[0]);
+ _queryCompilationContext.ParametersToConstantize.Add(parameterExpression.Name!);
+ return parameterExpression;
+ }
+
+ case nameof(EF.Parameter):
+ {
+ var parameterExpression = (ParameterExpression)Visit(methodCallExpression.Arguments[0]);
+ _queryCompilationContext.ParametersToNotConstantize.Add(parameterExpression.Name!);
+ return parameterExpression;
+ }
+ }
}
// Normalize list[x] to list.ElementAt(x)
diff --git a/src/EFCore/Query/QueryCompilationContext.cs b/src/EFCore/Query/QueryCompilationContext.cs
index 4c1784f9721..8a6005e1923 100644
--- a/src/EFCore/Query/QueryCompilationContext.cs
+++ b/src/EFCore/Query/QueryCompilationContext.cs
@@ -62,7 +62,19 @@ public class QueryCompilationContext
/// not used in application code.
///
///
- public virtual HashSet ParametersToConstantize { get; } = new(StringComparer.Ordinal);
+ public virtual ISet ParametersToConstantize { get; } = new HashSet(StringComparer.Ordinal);
+
+ ///
+ ///
+ /// Names of parameters on which was used. Such parameters are later not transformed into
+ /// constants even when parameterized collection constantization is configured as the default.
+ ///
+ ///
+ /// This property is typically used by database providers (and other extensions). It is generally
+ /// not used in application code.
+ ///
+ ///
+ public virtual ISet ParametersToNotConstantize { get; } = new HashSet(StringComparer.Ordinal);
private static readonly IReadOnlySet EmptySet = new HashSet();
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs
index a931d3dccc6..6c9d017e956 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs
@@ -517,6 +517,41 @@ FROM a IN (SELECT VALUE [@__i_0])
""");
});
+ public override Task Inline_collection_Contains_with_EF_Parameter(bool async)
+ => CosmosTestHelpers.Instance.NoSyncTest(
+ async, async a =>
+ {
+ await base.Inline_collection_Contains_with_EF_Parameter(async);
+
+ AssertSql(
+ """
+@__p_0='[2,999,1000]'
+
+SELECT VALUE c
+FROM root c
+WHERE ARRAY_CONTAINS(@__p_0, c["Id"])
+""");
+ });
+
+ public override Task Inline_collection_Count_with_column_predicate_with_EF_Parameter(bool async)
+ => CosmosTestHelpers.Instance.NoSyncTest(
+ async, async a =>
+ {
+ await base.Inline_collection_Count_with_column_predicate_with_EF_Parameter(async);
+
+ AssertSql(
+ """
+@__p_0='[2,999,1000]'
+
+SELECT VALUE c
+FROM root c
+WHERE ((
+ SELECT VALUE COUNT(1)
+ FROM p IN (SELECT VALUE @__p_0)
+ WHERE (p > c["Id"])) = 2)
+""");
+ });
+
public override Task Parameter_collection_Count(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
async, async a =>
diff --git a/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs
index 83de7279aa4..cafccdba0d1 100644
--- a/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs
+++ b/test/EFCore.Relational.Specification.Tests/Query/NonSharedPrimitiveCollectionsQueryRelationalTestBase.cs
@@ -12,6 +12,10 @@ public abstract class NonSharedPrimitiveCollectionsQueryRelationalTestBase : Non
public override Task Array_of_byte()
=> AssertTranslationFailed(() => TestArray((byte)1, (byte)2));
+ protected abstract DbContextOptionsBuilder SetTranslateParameterizedCollectionsToConstants(DbContextOptionsBuilder optionsBuilder);
+
+ protected abstract DbContextOptionsBuilder SetTranslateParameterizedCollectionsToParameters(DbContextOptionsBuilder optionsBuilder);
+
[ConditionalFact]
public virtual async Task Column_collection_inside_json_owned_entity()
{
@@ -34,6 +38,170 @@ 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()
+ {
+ var contextFactory = await InitializeAsync(
+ onConfiguring: b => SetTranslateParameterizedCollectionsToConstants(b),
+ seed: context =>
+ {
+ context.AddRange(
+ new TestEntity { Id = 1 },
+ new TestEntity { Id = 100 });
+ return context.SaveChangesAsync();
+ });
+
+ await using var context = contextFactory.CreateContext();
+
+ var ids = new[] { 2, 999 };
+ var result = await context.Set().Where(c => ids.Count(i => i > c.Id) == 1).Select(x => x.Id).ToListAsync();
+ Assert.Equivalent(new[] { 100 }, result);
+ }
+
+ [ConditionalFact]
+ public virtual async Task Parameter_collection_of_ints_Contains_int_with_default_constants()
+ {
+ var contextFactory = await InitializeAsync(
+ onConfiguring: b => SetTranslateParameterizedCollectionsToConstants(b),
+ 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 };
+ var result = await context.Set().Where(c => ints.Contains(c.Id)).Select(x => x.Id).ToListAsync();
+ Assert.Equivalent(new[] { 2 }, result);
+ }
+
+ [ConditionalFact]
+ public virtual async Task Parameter_collection_Count_with_column_predicate_with_default_constants_EF_Parameter()
+ {
+ var contextFactory = await InitializeAsync(
+ onConfiguring: b => SetTranslateParameterizedCollectionsToConstants(b),
+ seed: context =>
+ {
+ context.AddRange(
+ new TestEntity { Id = 1 },
+ new TestEntity { Id = 100 });
+ return context.SaveChangesAsync();
+ });
+
+ await using var context = contextFactory.CreateContext();
+
+ var ids = new[] { 2, 999 };
+ var result = await context.Set().Where(c => EF.Parameter(ids).Count(i => i > c.Id) == 1).Select(x => x.Id).ToListAsync();
+ Assert.Equivalent(new[] { 100 }, result);
+ }
+
+ [ConditionalFact]
+ public virtual async Task Parameter_collection_of_ints_Contains_int_with_default_constants_EF_Parameter()
+ {
+ var contextFactory = await InitializeAsync(
+ onConfiguring: b => SetTranslateParameterizedCollectionsToConstants(b),
+ 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 };
+ var result = await context.Set().Where(c => EF.Parameter(ints).Contains(c.Id)).Select(x => x.Id).ToListAsync();
+ Assert.Equivalent(new[] { 2 }, result);
+ }
+
+ [ConditionalFact]
+ public virtual async Task Parameter_collection_Count_with_column_predicate_with_default_parameters()
+ {
+ var contextFactory = await InitializeAsync(
+ onConfiguring: b => SetTranslateParameterizedCollectionsToParameters(b),
+ seed: context =>
+ {
+ context.AddRange(
+ new TestEntity { Id = 1 },
+ new TestEntity { Id = 100 });
+ return context.SaveChangesAsync();
+ });
+
+ await using var context = contextFactory.CreateContext();
+
+ var ids = new[] { 2, 999 };
+ var result = await context.Set().Where(c => ids.Count(i => i > c.Id) == 1).Select(x => x.Id).ToListAsync();
+ Assert.Equivalent(new[] { 100 }, result);
+ }
+
+ [ConditionalFact]
+ public virtual async Task Parameter_collection_of_ints_Contains_int_with_default_parameters()
+ {
+ var contextFactory = await InitializeAsync(
+ onConfiguring: b => SetTranslateParameterizedCollectionsToParameters(b),
+ 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 };
+ var result = await context.Set().Where(c => ints.Contains(c.Id)).Select(x => x.Id).ToListAsync();
+ Assert.Equivalent(new[] { 2 }, result);
+ }
+
+ [ConditionalFact]
+ public virtual async Task Parameter_collection_Count_with_column_predicate_with_default_parameters_EF_Constant()
+ {
+ var contextFactory = await InitializeAsync(
+ onConfiguring: b => SetTranslateParameterizedCollectionsToParameters(b),
+ seed: context =>
+ {
+ context.AddRange(
+ new TestEntity { Id = 1 },
+ new TestEntity { Id = 100 });
+ return context.SaveChangesAsync();
+ });
+
+ await using var context = contextFactory.CreateContext();
+
+ var ids = new[] { 2, 999 };
+ var result = await context.Set().Where(c => EF.Constant(ids).Count(i => i > c.Id) == 1).Select(x => x.Id).ToListAsync();
+ Assert.Equivalent(new[] { 100 }, result);
+ }
+
+ [ConditionalFact]
+ public virtual async Task Parameter_collection_of_ints_Contains_int_with_default_parameters_EF_Constant()
+ {
+ var contextFactory = await InitializeAsync(
+ onConfiguring: b => SetTranslateParameterizedCollectionsToParameters(b),
+ 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 };
+ var result = await context.Set().Where(c => EF.Constant(ints).Contains(c.Id)).Select(x => x.Id).ToListAsync();
+ Assert.Equivalent(new[] { 2 }, result);
+ }
+
protected class TestOwner
{
public int Id { get; set; }
diff --git a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
index 5b041cbfc25..94de007cf69 100644
--- a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
@@ -287,6 +287,26 @@ public virtual Task Inline_collection_with_single_parameter_element_Count(bool a
ss => ss.Set().Where(c => new[] { i }.Count(i => i > c.Id) == 1));
}
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Inline_collection_Contains_with_EF_Parameter(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(c => EF.Parameter(new[] { 2, 999, 1000 }).Contains(c.Id)),
+ ss => ss.Set().Where(c => new[] { 2, 999, 1000 }.Contains(c.Id)));
+ }
+
+ [ConditionalTheory]
+ [MemberData(nameof(IsAsyncData))]
+ public virtual Task Inline_collection_Count_with_column_predicate_with_EF_Parameter(bool async)
+ {
+ return AssertQuery(
+ async,
+ ss => ss.Set().Where(c => EF.Parameter(new[] { 2, 999, 1000 }).Count(i => i > c.Id) == 2),
+ ss => ss.Set().Where(c => new[] { 2, 999, 1000 }.Count(i => i > c.Id) == 2));
+ }
+
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Parameter_collection_Count(bool async)
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs
index b446632b715..31ec7f7f27e 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqlServerTest.cs
@@ -11,6 +11,20 @@ namespace Microsoft.EntityFrameworkCore.Query;
public class NonSharedPrimitiveCollectionsQuerySqlServerTest : NonSharedPrimitiveCollectionsQueryRelationalTestBase
{
+ protected override DbContextOptionsBuilder SetTranslateParameterizedCollectionsToConstants(DbContextOptionsBuilder optionsBuilder)
+ {
+ new SqlServerDbContextOptionsBuilder(optionsBuilder).TranslateParameterizedCollectionsToConstants();
+
+ return optionsBuilder;
+ }
+
+ protected override DbContextOptionsBuilder SetTranslateParameterizedCollectionsToParameters(DbContextOptionsBuilder optionsBuilder)
+ {
+ new SqlServerDbContextOptionsBuilder(optionsBuilder).TranslateParameterizedCollectionsToParameters();
+
+ return optionsBuilder;
+ }
+
#region Support for specific element types
public override async Task Array_of_string()
@@ -779,6 +793,128 @@ FROM [TestEntityWithOwned] AS [t]
""");
}
+ public override async Task Parameter_collection_Count_with_column_predicate_with_default_constants()
+ {
+ await base.Parameter_collection_Count_with_column_predicate_with_default_constants();
+
+ AssertSql(
+ """
+SELECT [t].[Id]
+FROM [TestEntity] AS [t]
+WHERE (
+ SELECT COUNT(*)
+ FROM (VALUES (2), (999)) AS [i]([Value])
+ WHERE [i].[Value] > [t].[Id]) = 1
+""");
+ }
+
+ public override async Task Parameter_collection_of_ints_Contains_int_with_default_constants()
+ {
+ await base.Parameter_collection_of_ints_Contains_int_with_default_constants();
+
+ AssertSql(
+ """
+SELECT [t].[Id]
+FROM [TestEntity] AS [t]
+WHERE [t].[Id] IN (2, 999)
+""");
+ }
+
+ public override async Task Parameter_collection_Count_with_column_predicate_with_default_constants_EF_Parameter()
+ {
+ await base.Parameter_collection_Count_with_column_predicate_with_default_constants_EF_Parameter();
+
+ AssertSql(
+ """
+@__ids_0='[2,999]' (Size = 4000)
+
+SELECT [t].[Id]
+FROM [TestEntity] AS [t]
+WHERE (
+ SELECT COUNT(*)
+ FROM OPENJSON(@__ids_0) WITH ([value] int '$') AS [i]
+ WHERE [i].[value] > [t].[Id]) = 1
+""");
+ }
+
+ public override async Task Parameter_collection_of_ints_Contains_int_with_default_constants_EF_Parameter()
+ {
+ await base.Parameter_collection_of_ints_Contains_int_with_default_constants_EF_Parameter();
+
+ AssertSql(
+ """
+@__ints_0='[2,999]' (Size = 4000)
+
+SELECT [t].[Id]
+FROM [TestEntity] AS [t]
+WHERE [t].[Id] IN (
+ SELECT [i].[value]
+ FROM OPENJSON(@__ints_0) WITH ([value] int '$') AS [i]
+)
+""");
+ }
+
+ public override async Task Parameter_collection_Count_with_column_predicate_with_default_parameters()
+ {
+ await base.Parameter_collection_Count_with_column_predicate_with_default_parameters();
+
+ AssertSql(
+ """
+@__ids_0='[2,999]' (Size = 4000)
+
+SELECT [t].[Id]
+FROM [TestEntity] AS [t]
+WHERE (
+ SELECT COUNT(*)
+ FROM OPENJSON(@__ids_0) WITH ([value] int '$') AS [i]
+ WHERE [i].[value] > [t].[Id]) = 1
+""");
+ }
+
+ public override async Task Parameter_collection_of_ints_Contains_int_with_default_parameters()
+ {
+ await base.Parameter_collection_of_ints_Contains_int_with_default_parameters();
+
+ AssertSql(
+ """
+@__ints_0='[2,999]' (Size = 4000)
+
+SELECT [t].[Id]
+FROM [TestEntity] AS [t]
+WHERE [t].[Id] IN (
+ SELECT [i].[value]
+ FROM OPENJSON(@__ints_0) WITH ([value] int '$') AS [i]
+)
+""");
+ }
+
+ public override async Task Parameter_collection_Count_with_column_predicate_with_default_parameters_EF_Constant()
+ {
+ await base.Parameter_collection_Count_with_column_predicate_with_default_parameters_EF_Constant();
+
+ AssertSql(
+ """
+SELECT [t].[Id]
+FROM [TestEntity] AS [t]
+WHERE (
+ SELECT COUNT(*)
+ FROM (VALUES (2), (999)) AS [i]([Value])
+ WHERE [i].[Value] > [t].[Id]) = 1
+""");
+ }
+
+ public override async Task Parameter_collection_of_ints_Contains_int_with_default_parameters_EF_Constant()
+ {
+ await base.Parameter_collection_of_ints_Contains_int_with_default_parameters_EF_Constant();
+
+ AssertSql(
+ """
+SELECT [t].[Id]
+FROM [TestEntity] AS [t]
+WHERE [t].[Id] IN (2, 999)
+""");
+ }
+
[ConditionalFact]
public virtual async Task Same_parameter_with_different_type_mappings()
{
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs
index 077c136ab9a..c09c7a89029 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQueryOldSqlServerTest.cs
@@ -453,6 +453,12 @@ SELECT COUNT(*)
""");
}
+ public override Task Inline_collection_Contains_with_EF_Parameter(bool async)
+ => AssertCompatibilityLevelTooLow(() => base.Inline_collection_Contains_with_EF_Parameter(async));
+
+ public override Task Inline_collection_Count_with_column_predicate_with_EF_Parameter(bool async)
+ => AssertCompatibilityLevelTooLow(() => base.Inline_collection_Count_with_column_predicate_with_EF_Parameter(async));
+
public override Task Parameter_collection_Count(bool async)
=> AssertCompatibilityLevelTooLow(() => base.Parameter_collection_Count(async));
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs
index b1159553a81..00a87df1eb7 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServer160Test.cs
@@ -418,6 +418,40 @@ SELECT COUNT(*)
""");
}
+ public override async Task Inline_collection_Contains_with_EF_Parameter(bool async)
+ {
+ await base.Inline_collection_Contains_with_EF_Parameter(async);
+
+ AssertSql(
+ """
+@__p_0='[2,999,1000]' (Size = 4000)
+
+SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
+FROM [PrimitiveCollectionsEntity] AS [p]
+WHERE [p].[Id] IN (
+ SELECT [p0].[value]
+ FROM OPENJSON(@__p_0) WITH ([value] int '$') AS [p0]
+)
+""");
+ }
+
+ public override async Task Inline_collection_Count_with_column_predicate_with_EF_Parameter(bool async)
+ {
+ await base.Inline_collection_Count_with_column_predicate_with_EF_Parameter(async);
+
+ AssertSql(
+ """
+@__p_0='[2,999,1000]' (Size = 4000)
+
+SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
+FROM [PrimitiveCollectionsEntity] AS [p]
+WHERE (
+ SELECT COUNT(*)
+ FROM OPENJSON(@__p_0) WITH ([value] int '$') AS [p0]
+ WHERE [p0].[value] > [p].[Id]) = 2
+""");
+ }
+
public override async Task Parameter_collection_Count(bool async)
{
await base.Parameter_collection_Count(async);
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs
index d99fb9ea774..330a3159cf7 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs
@@ -441,6 +441,40 @@ SELECT COUNT(*)
""");
}
+ public override async Task Inline_collection_Contains_with_EF_Parameter(bool async)
+ {
+ await base.Inline_collection_Contains_with_EF_Parameter(async);
+
+ AssertSql(
+ """
+@__p_0='[2,999,1000]' (Size = 4000)
+
+SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
+FROM [PrimitiveCollectionsEntity] AS [p]
+WHERE [p].[Id] IN (
+ SELECT [p0].[value]
+ FROM OPENJSON(@__p_0) WITH ([value] int '$') AS [p0]
+)
+""");
+ }
+
+ public override async Task Inline_collection_Count_with_column_predicate_with_EF_Parameter(bool async)
+ {
+ await base.Inline_collection_Count_with_column_predicate_with_EF_Parameter(async);
+
+ AssertSql(
+ """
+@__p_0='[2,999,1000]' (Size = 4000)
+
+SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
+FROM [PrimitiveCollectionsEntity] AS [p]
+WHERE (
+ SELECT COUNT(*)
+ FROM OPENJSON(@__p_0) WITH ([value] int '$') AS [p0]
+ WHERE [p0].[value] > [p].[Id]) = 2
+""");
+ }
+
public override async Task Parameter_collection_Count(bool async)
{
await base.Parameter_collection_Count(async);
diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs
index 8b2d2dc53f7..ae95f98db33 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Query/NonSharedPrimitiveCollectionsQuerySqliteTest.cs
@@ -7,6 +7,20 @@ namespace Microsoft.EntityFrameworkCore.Query;
public class NonSharedPrimitiveCollectionsQuerySqliteTest : NonSharedPrimitiveCollectionsQueryRelationalTestBase
{
+ protected override DbContextOptionsBuilder SetTranslateParameterizedCollectionsToConstants(DbContextOptionsBuilder optionsBuilder)
+ {
+ new SqliteDbContextOptionsBuilder(optionsBuilder).TranslateParameterizedCollectionsToConstants();
+
+ return optionsBuilder;
+ }
+
+ protected override DbContextOptionsBuilder SetTranslateParameterizedCollectionsToParameters(DbContextOptionsBuilder optionsBuilder)
+ {
+ new SqliteDbContextOptionsBuilder(optionsBuilder).TranslateParameterizedCollectionsToParameters();
+
+ return optionsBuilder;
+ }
+
#region Support for specific element types
public override async Task Array_of_int()
@@ -322,6 +336,128 @@ LIMIT 2
""");
}
+ public override async Task Parameter_collection_Count_with_column_predicate_with_default_constants()
+ {
+ await base.Parameter_collection_Count_with_column_predicate_with_default_constants();
+
+ AssertSql(
+ """
+SELECT "t"."Id"
+FROM "TestEntity" AS "t"
+WHERE (
+ SELECT COUNT(*)
+ FROM (SELECT 2 AS "Value" UNION ALL VALUES (999)) AS "i"
+ WHERE "i"."Value" > "t"."Id") = 1
+""");
+ }
+
+ public override async Task Parameter_collection_of_ints_Contains_int_with_default_constants()
+ {
+ await base.Parameter_collection_of_ints_Contains_int_with_default_constants();
+
+ AssertSql(
+ """
+SELECT "t"."Id"
+FROM "TestEntity" AS "t"
+WHERE "t"."Id" IN (2, 999)
+""");
+ }
+
+ public override async Task Parameter_collection_Count_with_column_predicate_with_default_constants_EF_Parameter()
+ {
+ await base.Parameter_collection_Count_with_column_predicate_with_default_constants_EF_Parameter();
+
+ AssertSql(
+ """
+@__ids_0='[2,999]' (Size = 7)
+
+SELECT "t"."Id"
+FROM "TestEntity" AS "t"
+WHERE (
+ SELECT COUNT(*)
+ FROM json_each(@__ids_0) AS "i"
+ WHERE "i"."value" > "t"."Id") = 1
+""");
+ }
+
+ public override async Task Parameter_collection_of_ints_Contains_int_with_default_constants_EF_Parameter()
+ {
+ await base.Parameter_collection_of_ints_Contains_int_with_default_constants_EF_Parameter();
+
+ AssertSql(
+ """
+@__ints_0='[2,999]' (Size = 7)
+
+SELECT "t"."Id"
+FROM "TestEntity" AS "t"
+WHERE "t"."Id" IN (
+ SELECT "i"."value"
+ FROM json_each(@__ints_0) AS "i"
+)
+""");
+ }
+
+ public override async Task Parameter_collection_Count_with_column_predicate_with_default_parameters()
+ {
+ await base.Parameter_collection_Count_with_column_predicate_with_default_parameters();
+
+ AssertSql(
+ """
+@__ids_0='[2,999]' (Size = 7)
+
+SELECT "t"."Id"
+FROM "TestEntity" AS "t"
+WHERE (
+ SELECT COUNT(*)
+ FROM json_each(@__ids_0) AS "i"
+ WHERE "i"."value" > "t"."Id") = 1
+""");
+ }
+
+ public override async Task Parameter_collection_of_ints_Contains_int_with_default_parameters()
+ {
+ await base.Parameter_collection_of_ints_Contains_int_with_default_parameters();
+
+ AssertSql(
+ """
+@__ints_0='[2,999]' (Size = 7)
+
+SELECT "t"."Id"
+FROM "TestEntity" AS "t"
+WHERE "t"."Id" IN (
+ SELECT "i"."value"
+ FROM json_each(@__ints_0) AS "i"
+)
+""");
+ }
+
+ public override async Task Parameter_collection_Count_with_column_predicate_with_default_parameters_EF_Constant()
+ {
+ await base.Parameter_collection_Count_with_column_predicate_with_default_parameters_EF_Constant();
+
+ AssertSql(
+ """
+SELECT "t"."Id"
+FROM "TestEntity" AS "t"
+WHERE (
+ SELECT COUNT(*)
+ FROM (SELECT 2 AS "Value" UNION ALL VALUES (999)) AS "i"
+ WHERE "i"."Value" > "t"."Id") = 1
+""");
+ }
+
+ public override async Task Parameter_collection_of_ints_Contains_int_with_default_parameters_EF_Constant()
+ {
+ await base.Parameter_collection_of_ints_Contains_int_with_default_parameters_EF_Constant();
+
+ AssertSql(
+ """
+SELECT "t"."Id"
+FROM "TestEntity" AS "t"
+WHERE "t"."Id" IN (2, 999)
+""");
+ }
+
protected override ITestStoreFactory TestStoreFactory
=> SqliteTestStoreFactory.Instance;
}
diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs
index 641a258fd1b..269c5dc9194 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs
@@ -431,6 +431,40 @@ SELECT COUNT(*)
""");
}
+ public override async Task Inline_collection_Contains_with_EF_Parameter(bool async)
+ {
+ await base.Inline_collection_Contains_with_EF_Parameter(async);
+
+ AssertSql(
+ """
+@__p_0='[2,999,1000]' (Size = 12)
+
+SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."NullableString", "p"."NullableStrings", "p"."String", "p"."Strings"
+FROM "PrimitiveCollectionsEntity" AS "p"
+WHERE "p"."Id" IN (
+ SELECT "p0"."value"
+ FROM json_each(@__p_0) AS "p0"
+)
+""");
+ }
+
+ public override async Task Inline_collection_Count_with_column_predicate_with_EF_Parameter(bool async)
+ {
+ await base.Inline_collection_Count_with_column_predicate_with_EF_Parameter(async);
+
+ AssertSql(
+ """
+@__p_0='[2,999,1000]' (Size = 12)
+
+SELECT "p"."Id", "p"."Bool", "p"."Bools", "p"."DateTime", "p"."DateTimes", "p"."Enum", "p"."Enums", "p"."Int", "p"."Ints", "p"."NullableInt", "p"."NullableInts", "p"."NullableString", "p"."NullableStrings", "p"."String", "p"."Strings"
+FROM "PrimitiveCollectionsEntity" AS "p"
+WHERE (
+ SELECT COUNT(*)
+ FROM json_each(@__p_0) AS "p0"
+ WHERE "p0"."value" > "p"."Id") = 2
+""");
+ }
+
public override async Task Parameter_collection_Count(bool async)
{
await base.Parameter_collection_Count(async);