diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs
index cd46015679b..d520b5fc6e3 100644
--- a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs
+++ b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs
@@ -253,12 +253,6 @@ public static string OneOfTwoValuesMustBeSet(object? param1, object? param2)
GetString("OneOfTwoValuesMustBeSet", nameof(param1), nameof(param2)),
param1, param2);
- ///
- /// Only constants or parameters are currently allowed in Contains.
- ///
- public static string OnlyConstantsAndParametersAllowedInContains
- => GetString("OnlyConstantsAndParametersAllowedInContains");
-
///
/// The entity of type '{entityType}' is mapped as a part of the document mapped to '{missingEntityType}', but there is no tracked entity of this type with the corresponding key value. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values.
///
diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.resx b/src/EFCore.Cosmos/Properties/CosmosStrings.resx
index 2417a7c1a77..9aa9ddd4412 100644
--- a/src/EFCore.Cosmos/Properties/CosmosStrings.resx
+++ b/src/EFCore.Cosmos/Properties/CosmosStrings.resx
@@ -246,9 +246,6 @@
Exactly one of '{param1}' or '{param2}' must be set.
-
- Only constants or parameters are currently allowed in Contains.
-
The entity of type '{entityType}' is mapped as a part of the document mapped to '{missingEntityType}', but there is no tracked entity of this type with the corresponding key value. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the key values.
diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.InExpressionValuesExpandingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.InExpressionValuesExpandingExpressionVisitor.cs
index 01abb10bb82..8e584675de3 100644
--- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.InExpressionValuesExpandingExpressionVisitor.cs
+++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.InExpressionValuesExpandingExpressionVisitor.cs
@@ -10,64 +10,34 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal;
public partial class CosmosShapedQueryCompilingExpressionVisitor
{
- private sealed class InExpressionValuesExpandingExpressionVisitor : ExpressionVisitor
+ private sealed class InExpressionValuesExpandingExpressionVisitor(
+ ISqlExpressionFactory sqlExpressionFactory,
+ IReadOnlyDictionary parametersValues)
+ : ExpressionVisitor
{
- private readonly ISqlExpressionFactory _sqlExpressionFactory;
- private readonly IReadOnlyDictionary _parametersValues;
-
- public InExpressionValuesExpandingExpressionVisitor(
- ISqlExpressionFactory sqlExpressionFactory,
- IReadOnlyDictionary parametersValues)
- {
- _sqlExpressionFactory = sqlExpressionFactory;
- _parametersValues = parametersValues;
- }
-
- public override Expression Visit(Expression expression)
+ protected override Expression VisitExtension(Expression expression)
{
if (expression is InExpression inExpression)
{
- var inValues = new List();
- var hasNullValue = false;
+ IReadOnlyList values;
switch (inExpression)
{
- case { ValuesParameter: SqlParameterExpression valuesParameter }:
- {
- var typeMapping = valuesParameter.TypeMapping;
-
- foreach (var value in (IEnumerable)_parametersValues[valuesParameter.Name])
- {
- if (value is null)
- {
- hasNullValue = true;
- continue;
- }
-
- inValues.Add(_sqlExpressionFactory.Constant(value, typeMapping));
- }
-
+ case { Values: IReadOnlyList values2 }:
+ values = values2;
break;
- }
- case { Values: IReadOnlyList values }:
+ // TODO: IN with subquery (return immediately, nothing to do here)
+
+ case { ValuesParameter: SqlParameterExpression valuesParameter }:
{
- foreach (var value in values)
+ var typeMapping = valuesParameter.TypeMapping;
+ var mutableValues = new List();
+ foreach (var value in (IEnumerable)parametersValues[valuesParameter.Name])
{
- if (value is not (SqlConstantExpression or SqlParameterExpression))
- {
- throw new InvalidOperationException(CosmosStrings.OnlyConstantsAndParametersAllowedInContains);
- }
-
- if (IsNull(value))
- {
- hasNullValue = true;
- continue;
- }
-
- inValues.Add(value);
+ mutableValues.Add(sqlExpressionFactory.Constant(value, typeMapping));
}
-
+ values = mutableValues;
break;
}
@@ -75,34 +45,12 @@ public override Expression Visit(Expression expression)
throw new UnreachableException();
}
- var updatedInExpression = inValues.Count > 0
- ? _sqlExpressionFactory.In((SqlExpression)Visit(inExpression.Item), inValues)
- : null;
-
- var nullCheckExpression = hasNullValue
- ? _sqlExpressionFactory.IsNull(inExpression.Item)
- : null;
-
- if (updatedInExpression != null
- && nullCheckExpression != null)
- {
- return _sqlExpressionFactory.OrElse(updatedInExpression, nullCheckExpression);
- }
-
- if (updatedInExpression == null
- && nullCheckExpression == null)
- {
- return _sqlExpressionFactory.Equal(_sqlExpressionFactory.Constant(true), _sqlExpressionFactory.Constant(false));
- }
-
- return (SqlExpression)updatedInExpression ?? nullCheckExpression;
+ return values.Count == 0
+ ? sqlExpressionFactory.ApplyDefaultTypeMapping(sqlExpressionFactory.Constant(false))
+ : sqlExpressionFactory.In((SqlExpression)Visit(inExpression.Item), values);
}
- return base.Visit(expression);
+ return base.VisitExtension(expression);
}
-
- private bool IsNull(SqlExpression expression)
- => expression is SqlConstantExpression { Value: null }
- || expression is SqlParameterExpression { Name: string parameterName } && _parametersValues[parameterName] is null;
}
}
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs
index eca2f8a19fd..d08ce825887 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindAggregateOperatorsQueryCosmosTest.cs
@@ -1806,9 +1806,8 @@ public override Task Contains_with_local_ordered_read_only_collection_all_null(b
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = null))
-"""
- );
+WHERE ((c["Discriminator"] = "Customer") AND c["CustomerID"] IN (null, null))
+""");
});
public override Task Contains_with_local_read_only_collection_inline(bool async)
@@ -1969,7 +1968,7 @@ public override Task Contains_with_local_collection_empty_inline(bool async)
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND NOT((true = false)))
+WHERE ((c["Discriminator"] = "Customer") AND NOT(false))
""");
});
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs
index 389a10b238b..f57b1a24b00 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs
@@ -4158,7 +4158,7 @@ public override Task Entity_equality_contains_with_list_of_null(bool async)
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] IN ("ALFKI") OR (c["CustomerID"] = null)))
+WHERE ((c["Discriminator"] = "Customer") AND c["CustomerID"] IN (null, "ALFKI"))
""");
});
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs
index 152837b212d..2c40a6e83b5 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs
@@ -55,7 +55,7 @@ public override Task Inline_collection_of_nullable_ints_Contains_null(bool async
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND (c["NullableInt"] IN (999) OR (c["NullableInt"] = null)))
+WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND c["NullableInt"] IN (null, 999))
""");
});
@@ -137,7 +137,7 @@ public override Task Inline_collection_Contains_with_zero_values(bool async)
"""
SELECT c
FROM root c
-WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND (true = false))
+WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND false)
""");
});
@@ -230,13 +230,37 @@ FROM root c
""");
});
- // TODO: Remove incorrect null semantics compensation for Cosmos: #31063
public override Task Inline_collection_Contains_with_mixed_value_types(bool async)
- => Assert.ThrowsAsync(() => base.Inline_collection_Contains_with_mixed_value_types(async));
+ => CosmosTestHelpers.Instance.NoSyncTest(
+ async, async a =>
+ {
+ await base.Inline_collection_Contains_with_mixed_value_types(a);
+
+ AssertSql(
+ """
+@__i_0='11'
+
+SELECT c
+FROM root c
+WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND c["Int"] IN (999, @__i_0, c["Id"], (c["Id"] + c["Int"])))
+""");
+ });
- // TODO: Remove incorrect null semantics compensation for Cosmos: #31063
public override Task Inline_collection_List_Contains_with_mixed_value_types(bool async)
- => Assert.ThrowsAsync(() => base.Inline_collection_List_Contains_with_mixed_value_types(async));
+ => CosmosTestHelpers.Instance.NoSyncTest(
+ async, async a =>
+ {
+ await base.Inline_collection_List_Contains_with_mixed_value_types(a);
+
+ AssertSql(
+ """
+@__i_0='11'
+
+SELECT c
+FROM root c
+WHERE ((c["Discriminator"] = "PrimitiveCollectionsEntity") AND c["Int"] IN (999, @__i_0, c["Id"], (c["Id"] + c["Int"])))
+""");
+ });
public override Task Inline_collection_Contains_as_Any_with_predicate(bool async)
=> CosmosTestHelpers.Instance.NoSyncTest(
diff --git a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
index 2017dd60587..69fa6aeae14 100644
--- a/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs
@@ -147,7 +147,7 @@ public virtual async Task Inline_collection_List_Contains_with_mixed_value_types
await AssertQuery(
async,
- ss => ss.Set().Where(c => new List() { 999, i, c.Id, c.Id + c.Int }.Contains(c.Int)));
+ ss => ss.Set().Where(c => new List { 999, i, c.Id, c.Id + c.Int }.Contains(c.Int)));
}
[ConditionalTheory]