From ca239e9137e0f341e5b5e638b1015620dfdd5610 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 04:42:58 +0000 Subject: [PATCH 1/8] Initial plan From 15a05bcd943e7ec3365ae80e6243b9717b4c4c4d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 04:57:34 +0000 Subject: [PATCH 2/8] Don't transform to ReadItem when WithPartitionKey conflicts with predicate partition key Fixes #38238 Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- ...CosmosReadItemAndPartitionKeysExtractor.cs | 11 ++++ ...mPartitionKeyQueryDiscriminatorInIdTest.cs | 38 +++++++++++++- ...temPartitionKeyQueryInheritanceTestBase.cs | 10 ++++ ...artitionKeyQueryNoDiscriminatorInIdTest.cs | 52 +++++++++++++++++-- ...titionKeyQueryRootDiscriminatorInIdTest.cs | 52 +++++++++++++++++-- .../Query/ReadItemPartitionKeyQueryTest.cs | 29 ++++++++++- .../ReadItemPartitionKeyQueryTestBase.cs | 10 ++++ 7 files changed, 190 insertions(+), 12 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs index c0221b87e81..813343a51d2 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs @@ -21,6 +21,7 @@ public class CosmosReadItemAndPartitionKeysExtractor : ExpressionVisitor private IEntityType _entityType = null!; private string _rootAlias = null!; private bool _isPredicateCompatibleWithReadItem; + private bool _partitionKeyComparisonInPredicate; private bool _nonRootDiscriminatorInJsonId; private string? _discriminatorJsonPropertyName; private Dictionary _jsonIdPropertyValues = null!; @@ -60,6 +61,7 @@ public virtual Expression ExtractPartitionKeysAndId( // from the tree (either constants or parameters). // We also want to ignore the discriminator property if it's compared to our entity type's discriminator value (see below). _isPredicateCompatibleWithReadItem = true; + _partitionKeyComparisonInPredicate = false; var jsonIdDefinition = _entityType.GetJsonIdDefinition(); var jsonIdProperties = jsonIdDefinition?.Properties ?? []; @@ -95,6 +97,9 @@ public virtual Expression ExtractPartitionKeysAndId( // this case, we skip lifting the predicate comparisons and leave the predicate exactly as it is (it may conflict with the values // given in WithPartitionKey and return zero results - that's the expected behavior). var liftPartitionKeys = queryCompilationContext.PartitionKeyPropertyValues.Count == 0; + + // Whether partition key values were provided via WithPartitionKey() (as opposed to being lifted from the predicate below). + var partitionKeyValuesProvidedViaApi = !liftPartitionKeys; foreach (var property in partitionKeyProperties) { if (liftPartitionKeys && _partitionKeyPropertyValues[property].ValueExpression is { } valueExpression) @@ -115,6 +120,11 @@ public virtual Expression ExtractPartitionKeysAndId( // predicate, and *all* partition key values are specified(in the predicate or via WithPartitionKey) if (_isPredicateCompatibleWithReadItem && allIdPropertiesSpecified + // If partition key values were provided via WithPartitionKey() and the predicate also contains partition key comparisons, + // we don't transform to ReadItem: the two may specify conflicting values, and a ReadItem would silently ignore the predicate + // and return a result for the WithPartitionKey() value. Executing as a regular query instead correctly yields zero results on + // conflict (see #38238). + && !(partitionKeyValuesProvidedViaApi && _partitionKeyComparisonInPredicate) && queryCompilationContext.PartitionKeyPropertyValues.Count == partitionKeyProperties.Count && select is { @@ -323,6 +333,7 @@ void ProcessPropertyComparison(string propertyName, SqlExpression propertyValue, && (previousValues.ValueExpression is null || previousValues.Equals(propertyValue))) { _partitionKeyPropertyValues[property] = (ValueExpression: propertyValue, OriginalExpression: originalExpression); + _partitionKeyComparisonInPredicate = true; return; } } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryDiscriminatorInIdTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryDiscriminatorInIdTest.cs index 2d142ed1fe3..f65552e7d37 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryDiscriminatorInIdTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryDiscriminatorInIdTest.cs @@ -797,7 +797,12 @@ public override async Task Both_WithPartitionKey_and_predicate_comparisons_with_ { await base.Both_WithPartitionKey_and_predicate_comparisons_with_same_values_with_only_partition_key_leaf(); - AssertSql("""ReadItem(["PK1c"], DerivedOnlySinglePartitionKeyEntity|PK1c)"""); + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ((c["$type"] = "DerivedOnlySinglePartitionKeyEntity") AND (c["PartitionKey"] = "PK1c")) +"""); } public override async Task ReadItem_with_hierarchical_partition_key_leaf() @@ -891,7 +896,36 @@ public override async Task ReadItem_with_WithPartitionKey_with_only_partition_ke { await base.ReadItem_with_WithPartitionKey_with_only_partition_key_leaf(); - AssertSql("""ReadItem(["PK1c"], DerivedOnlySinglePartitionKeyEntity|PK1c)"""); + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ((c["$type"] = "DerivedOnlySinglePartitionKeyEntity") AND (c["PartitionKey"] = "PK1c")) +"""); + } + + public override async Task WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key() + { + await base.WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key(); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["$type"] IN ("SinglePartitionKeyEntity", "DerivedSinglePartitionKeyEntity") AND ((c["Id"] = "b29bced8-e1e5-420e-82d7-1c7a51703d34") AND (c["PartitionKey"] = "PK2"))) +"""); + } + + public override async Task WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key_leaf() + { + await base.WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key_leaf(); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ((c["$type"] = "DerivedSinglePartitionKeyEntity") AND ((c["Id"] = "188d3253-81be-4a87-b58f-a2bd07e6b98c") AND (c["PartitionKey"] = "PK2"))) +"""); } public override async Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem_leaf() diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryInheritanceTestBase.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryInheritanceTestBase.cs index 39a0c545c79..134128da1eb 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryInheritanceTestBase.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryInheritanceTestBase.cs @@ -261,6 +261,16 @@ public virtual Task ReadItem_with_WithPartitionKey_with_only_partition_key_leaf( ss => ss.Set().WithPartitionKey("PK1c").Where(e => e.PartitionKey == "PK1c"), ss => ss.Set().Where(e => e.PartitionKey == "PK1c")); + [Fact] + public virtual Task WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key_leaf() + => AssertQuery( + async: true, + ss => ss.Set().WithPartitionKey("PK1") + .Where(e => e.Id == Guid.Parse("188D3253-81BE-4A87-B58F-A2BD07E6B98C") && e.PartitionKey == "PK2"), + ss => ss.Set().Where(e => e.PartitionKey == "PK1") + .Where(e => e.Id == Guid.Parse("188D3253-81BE-4A87-B58F-A2BD07E6B98C") && e.PartitionKey == "PK2"), + assertEmpty: true); + [Fact] public virtual Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem_leaf() { diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs index d8351308d83..0b9da8e7c65 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs @@ -275,7 +275,12 @@ public override async Task Both_WithPartitionKey_and_predicate_comparisons_with_ { await base.Both_WithPartitionKey_and_predicate_comparisons_with_same_values_with_only_partition_key(); - AssertSql("""ReadItem(["PK1a"], PK1a)"""); + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["$type"] IN ("OnlySinglePartitionKeyEntity", "DerivedOnlySinglePartitionKeyEntity") AND (c["id"] = "PK1a")) +"""); } public override async Task ReadItem_with_hierarchical_partition_key() @@ -366,7 +371,12 @@ public override async Task ReadItem_with_WithPartitionKey_with_only_partition_ke { await base.ReadItem_with_WithPartitionKey_with_only_partition_key(); - AssertSql("""ReadItem(["PK1a"], PK1a)"""); + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["$type"] IN ("OnlySinglePartitionKeyEntity", "DerivedOnlySinglePartitionKeyEntity") AND (c["id"] = "PK1a")) +"""); } public override async Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem() @@ -673,7 +683,12 @@ public override async Task Both_WithPartitionKey_and_predicate_comparisons_with_ { await base.Both_WithPartitionKey_and_predicate_comparisons_with_same_values_with_only_partition_key_leaf(); - AssertSql("""ReadItem(["PK1c"], PK1c)"""); + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ((c["$type"] = "DerivedOnlySinglePartitionKeyEntity") AND (c["id"] = "PK1c")) +"""); } public override async Task ReadItem_with_hierarchical_partition_key_leaf() @@ -764,7 +779,36 @@ public override async Task ReadItem_with_WithPartitionKey_with_only_partition_ke { await base.ReadItem_with_WithPartitionKey_with_only_partition_key_leaf(); - AssertSql("""ReadItem(["PK1c"], PK1c)"""); + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ((c["$type"] = "DerivedOnlySinglePartitionKeyEntity") AND (c["id"] = "PK1c")) +"""); + } + + public override async Task WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key() + { + await base.WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key(); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["$type"] IN ("SinglePartitionKeyEntity", "DerivedSinglePartitionKeyEntity") AND ((c["id"] = "b29bced8-e1e5-420e-82d7-1c7a51703d34") AND (c["PartitionKey"] = "PK2"))) +"""); + } + + public override async Task WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key_leaf() + { + await base.WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key_leaf(); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ((c["$type"] = "DerivedSinglePartitionKeyEntity") AND ((c["id"] = "188d3253-81be-4a87-b58f-a2bd07e6b98c") AND (c["PartitionKey"] = "PK2"))) +"""); } public override async Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem_leaf() diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs index d2000eb7ce8..d5c63bae6e6 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs @@ -268,7 +268,12 @@ public override async Task Both_WithPartitionKey_and_predicate_comparisons_with_ { await base.Both_WithPartitionKey_and_predicate_comparisons_with_same_values_with_only_partition_key(); - AssertSql("""ReadItem(["PK1a"], OnlySinglePartitionKeyEntity|PK1a)"""); + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["$type"] IN ("OnlySinglePartitionKeyEntity", "DerivedOnlySinglePartitionKeyEntity") AND (c["PartitionKey"] = "PK1a")) +"""); } public override async Task ReadItem_with_hierarchical_partition_key() @@ -359,7 +364,12 @@ public override async Task ReadItem_with_WithPartitionKey_with_only_partition_ke { await base.ReadItem_with_WithPartitionKey_with_only_partition_key(); - AssertSql("""ReadItem(["PK1a"], OnlySinglePartitionKeyEntity|PK1a)"""); + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["$type"] IN ("OnlySinglePartitionKeyEntity", "DerivedOnlySinglePartitionKeyEntity") AND (c["PartitionKey"] = "PK1a")) +"""); } public override async Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem() @@ -668,7 +678,12 @@ public override async Task Both_WithPartitionKey_and_predicate_comparisons_with_ { await base.Both_WithPartitionKey_and_predicate_comparisons_with_same_values_with_only_partition_key_leaf(); - AssertSql("""ReadItem(["PK1c"], OnlySinglePartitionKeyEntity|PK1c)"""); + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ((c["$type"] = "DerivedOnlySinglePartitionKeyEntity") AND (c["PartitionKey"] = "PK1c")) +"""); } public override async Task ReadItem_with_hierarchical_partition_key_leaf() @@ -759,7 +774,36 @@ public override async Task ReadItem_with_WithPartitionKey_with_only_partition_ke { await base.ReadItem_with_WithPartitionKey_with_only_partition_key_leaf(); - AssertSql("""ReadItem(["PK1c"], OnlySinglePartitionKeyEntity|PK1c)"""); + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ((c["$type"] = "DerivedOnlySinglePartitionKeyEntity") AND (c["PartitionKey"] = "PK1c")) +"""); + } + + public override async Task WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key() + { + await base.WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key(); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["$type"] IN ("SinglePartitionKeyEntity", "DerivedSinglePartitionKeyEntity") AND ((c["Id"] = "b29bced8-e1e5-420e-82d7-1c7a51703d34") AND (c["PartitionKey"] = "PK2"))) +"""); + } + + public override async Task WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key_leaf() + { + await base.WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key_leaf(); + + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ((c["$type"] = "DerivedSinglePartitionKeyEntity") AND ((c["Id"] = "188d3253-81be-4a87-b58f-a2bd07e6b98c") AND (c["PartitionKey"] = "PK2"))) +"""); } public override async Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem_leaf() diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs index 6be7c6327a1..fe5d8cbdd4c 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs @@ -267,7 +267,13 @@ public override async Task Both_WithPartitionKey_and_predicate_comparisons_with_ { await base.Both_WithPartitionKey_and_predicate_comparisons_with_same_values_with_only_partition_key(); - AssertSql("""ReadItem(["PK1a"], PK1a)"""); + // Not ReadItem because partition key values were provided both via WithPartitionKey and in the predicate (see #38238). + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["id"] = "PK1a") +"""); } public override async Task ReadItem_with_hierarchical_partition_key() @@ -358,7 +364,26 @@ public override async Task ReadItem_with_WithPartitionKey_with_only_partition_ke { await base.ReadItem_with_WithPartitionKey_with_only_partition_key(); - AssertSql("""ReadItem(["PK1a"], PK1a)"""); + // Not ReadItem because partition key values were provided both via WithPartitionKey and in the predicate (see #38238). + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE (c["id"] = "PK1a") +"""); + } + + public override async Task WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key() + { + await base.WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key(); + + // Not ReadItem because partition key values were provided both via WithPartitionKey and in the predicate (see #38238). + AssertSql( + """ +SELECT VALUE c +FROM root c +WHERE ((c["id"] = "b29bced8-e1e5-420e-82d7-1c7a51703d34") AND (c["PartitionKey"] = "PK2")) +"""); } public override async Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem() diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTestBase.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTestBase.cs index c598dcf1a64..ceadd640109 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTestBase.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTestBase.cs @@ -292,6 +292,16 @@ public virtual Task ReadItem_with_WithPartitionKey_with_only_partition_key() ss => ss.Set().WithPartitionKey("PK1a").Where(e => e.PartitionKey == "PK1a"), ss => ss.Set().Where(e => e.PartitionKey == "PK1a")); + [Fact] + public virtual Task WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key() + => AssertQuery( + async: true, + ss => ss.Set().WithPartitionKey("PK1") + .Where(e => e.Id == Guid.Parse("B29BCED8-E1E5-420E-82D7-1C7A51703D34") && e.PartitionKey == "PK2"), + ss => ss.Set().Where(e => e.PartitionKey == "PK1") + .Where(e => e.Id == Guid.Parse("B29BCED8-E1E5-420E-82D7-1C7A51703D34") && e.PartitionKey == "PK2"), + assertEmpty: true); + [Fact] public virtual Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem() { From 090d06124e6f698abccf20aad36c9964d4eed7dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 20:37:57 +0000 Subject: [PATCH 3/8] Fix ReadItem partition-key conflict detection Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- ...CosmosReadItemAndPartitionKeysExtractor.cs | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs index 813343a51d2..c518526a05d 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs @@ -21,7 +21,7 @@ public class CosmosReadItemAndPartitionKeysExtractor : ExpressionVisitor private IEntityType _entityType = null!; private string _rootAlias = null!; private bool _isPredicateCompatibleWithReadItem; - private bool _partitionKeyComparisonInPredicate; + private bool _partitionKeyValuesInPredicateConflictWithReadItem; private bool _nonRootDiscriminatorInJsonId; private string? _discriminatorJsonPropertyName; private Dictionary _jsonIdPropertyValues = null!; @@ -61,7 +61,7 @@ public virtual Expression ExtractPartitionKeysAndId( // from the tree (either constants or parameters). // We also want to ignore the discriminator property if it's compared to our entity type's discriminator value (see below). _isPredicateCompatibleWithReadItem = true; - _partitionKeyComparisonInPredicate = false; + _partitionKeyValuesInPredicateConflictWithReadItem = false; var jsonIdDefinition = _entityType.GetJsonIdDefinition(); var jsonIdProperties = jsonIdDefinition?.Properties ?? []; @@ -100,6 +100,20 @@ public virtual Expression ExtractPartitionKeysAndId( // Whether partition key values were provided via WithPartitionKey() (as opposed to being lifted from the predicate below). var partitionKeyValuesProvidedViaApi = !liftPartitionKeys; + if (partitionKeyValuesProvidedViaApi) + { + for (var i = 0; i < queryCompilationContext.PartitionKeyPropertyValues.Count; i++) + { + var predicateValue = _partitionKeyPropertyValues[partitionKeyProperties[i]].ValueExpression; + if (predicateValue is not null + && !ExpressionEqualityComparer.Instance.Equals(queryCompilationContext.PartitionKeyPropertyValues[i], predicateValue)) + { + _partitionKeyValuesInPredicateConflictWithReadItem = true; + break; + } + } + } + foreach (var property in partitionKeyProperties) { if (liftPartitionKeys && _partitionKeyPropertyValues[property].ValueExpression is { } valueExpression) @@ -120,11 +134,10 @@ public virtual Expression ExtractPartitionKeysAndId( // predicate, and *all* partition key values are specified(in the predicate or via WithPartitionKey) if (_isPredicateCompatibleWithReadItem && allIdPropertiesSpecified - // If partition key values were provided via WithPartitionKey() and the predicate also contains partition key comparisons, - // we don't transform to ReadItem: the two may specify conflicting values, and a ReadItem would silently ignore the predicate - // and return a result for the WithPartitionKey() value. Executing as a regular query instead correctly yields zero results on - // conflict (see #38238). - && !(partitionKeyValuesProvidedViaApi && _partitionKeyComparisonInPredicate) + // If partition key values were provided via WithPartitionKey() and the predicate specifies different values, we don't + // transform to ReadItem: that would silently ignore the predicate and return a result for the WithPartitionKey() value. + // Executing as a regular query instead correctly yields zero results on conflict (see #38238). + && !_partitionKeyValuesInPredicateConflictWithReadItem && queryCompilationContext.PartitionKeyPropertyValues.Count == partitionKeyProperties.Count && select is { @@ -311,7 +324,7 @@ void ProcessPropertyComparison(string propertyName, SqlExpression propertyValue, if (propertyName == property.GetJsonPropertyName()) { if (_jsonIdPropertyValues.TryGetValue(property, out var previousValue) - && (previousValue is null || previousValue.Equals(propertyValue))) + && (previousValue is null || ExpressionEqualityComparer.Instance.Equals(previousValue, propertyValue))) { _jsonIdPropertyValues[property] = propertyValue; isCompatibleComparisonForReadItem = true; @@ -330,10 +343,10 @@ void ProcessPropertyComparison(string propertyName, SqlExpression propertyValue, // call. Note that this is always considered a compatible comparison for ReadItem. if (propertyName == property.GetJsonPropertyName() && _partitionKeyPropertyValues.TryGetValue(property, out var previousValues) - && (previousValues.ValueExpression is null || previousValues.Equals(propertyValue))) + && (previousValues.ValueExpression is null + || ExpressionEqualityComparer.Instance.Equals(previousValues.ValueExpression, propertyValue))) { _partitionKeyPropertyValues[property] = (ValueExpression: propertyValue, OriginalExpression: originalExpression); - _partitionKeyComparisonInPredicate = true; return; } } From 8e86bc778d46342e29114f2a8f4eafa3243a0aa9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 20:41:29 +0000 Subject: [PATCH 4/8] Guard partition-key compatibility loop bounds Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs index c518526a05d..9f951394b59 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs @@ -102,7 +102,7 @@ public virtual Expression ExtractPartitionKeysAndId( var partitionKeyValuesProvidedViaApi = !liftPartitionKeys; if (partitionKeyValuesProvidedViaApi) { - for (var i = 0; i < queryCompilationContext.PartitionKeyPropertyValues.Count; i++) + for (var i = 0; i < Math.Min(queryCompilationContext.PartitionKeyPropertyValues.Count, partitionKeyProperties.Count); i++) { var predicateValue = _partitionKeyPropertyValues[partitionKeyProperties[i]].ValueExpression; if (predicateValue is not null From 33055f614d6e786ab90011c39bbc64763d4cf186 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 22:09:53 +0000 Subject: [PATCH 5/8] Fix TypeMapping comparison in WithPartitionKey conflict detection using LINQ + ApplyTypeMapping Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- ...CosmosReadItemAndPartitionKeysExtractor.cs | 20 +++++++++---------- ...mPartitionKeyQueryDiscriminatorInIdTest.cs | 14 ++----------- ...artitionKeyQueryNoDiscriminatorInIdTest.cs | 14 ++----------- ...titionKeyQueryRootDiscriminatorInIdTest.cs | 14 ++----------- .../Query/ReadItemPartitionKeyQueryTest.cs | 16 ++------------- 5 files changed, 18 insertions(+), 60 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs index 9f951394b59..fd29bb56a17 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs @@ -102,16 +102,16 @@ public virtual Expression ExtractPartitionKeysAndId( var partitionKeyValuesProvidedViaApi = !liftPartitionKeys; if (partitionKeyValuesProvidedViaApi) { - for (var i = 0; i < Math.Min(queryCompilationContext.PartitionKeyPropertyValues.Count, partitionKeyProperties.Count); i++) - { - var predicateValue = _partitionKeyPropertyValues[partitionKeyProperties[i]].ValueExpression; - if (predicateValue is not null - && !ExpressionEqualityComparer.Instance.Equals(queryCompilationContext.PartitionKeyPropertyValues[i], predicateValue)) - { - _partitionKeyValuesInPredicateConflictWithReadItem = true; - break; - } - } + // Check whether any predicate partition key value conflicts with the WithPartitionKey() value at the same position. + // WithPartitionKey() values are translated with applyDefaultTypeMapping:false (TypeMapping is null), so we apply the + // predicate value's TypeMapping before comparing to ensure identical constants/parameters compare as equal. + _partitionKeyValuesInPredicateConflictWithReadItem = partitionKeyProperties + .Select((p, i) => (Property: p, Index: i)) + .Any(t => _partitionKeyPropertyValues[t.Property].ValueExpression is SqlExpression predicateValue + && (t.Index >= queryCompilationContext.PartitionKeyPropertyValues.Count + || queryCompilationContext.PartitionKeyPropertyValues[t.Index] is not SqlExpression withPkValue + || !predicateValue.Equals( + _sqlExpressionFactory.ApplyTypeMapping(withPkValue, predicateValue.TypeMapping)))); } foreach (var property in partitionKeyProperties) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryDiscriminatorInIdTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryDiscriminatorInIdTest.cs index f65552e7d37..1216cd68311 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryDiscriminatorInIdTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryDiscriminatorInIdTest.cs @@ -797,12 +797,7 @@ public override async Task Both_WithPartitionKey_and_predicate_comparisons_with_ { await base.Both_WithPartitionKey_and_predicate_comparisons_with_same_values_with_only_partition_key_leaf(); - AssertSql( - """ -SELECT VALUE c -FROM root c -WHERE ((c["$type"] = "DerivedOnlySinglePartitionKeyEntity") AND (c["PartitionKey"] = "PK1c")) -"""); + AssertSql("""ReadItem(["PK1c"], DerivedOnlySinglePartitionKeyEntity|PK1c)"""); } public override async Task ReadItem_with_hierarchical_partition_key_leaf() @@ -896,12 +891,7 @@ public override async Task ReadItem_with_WithPartitionKey_with_only_partition_ke { await base.ReadItem_with_WithPartitionKey_with_only_partition_key_leaf(); - AssertSql( - """ -SELECT VALUE c -FROM root c -WHERE ((c["$type"] = "DerivedOnlySinglePartitionKeyEntity") AND (c["PartitionKey"] = "PK1c")) -"""); + AssertSql("""ReadItem(["PK1c"], DerivedOnlySinglePartitionKeyEntity|PK1c)"""); } public override async Task WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key() diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs index 0b9da8e7c65..ae5b82ccdad 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs @@ -683,12 +683,7 @@ public override async Task Both_WithPartitionKey_and_predicate_comparisons_with_ { await base.Both_WithPartitionKey_and_predicate_comparisons_with_same_values_with_only_partition_key_leaf(); - AssertSql( - """ -SELECT VALUE c -FROM root c -WHERE ((c["$type"] = "DerivedOnlySinglePartitionKeyEntity") AND (c["id"] = "PK1c")) -"""); + AssertSql("""ReadItem(["PK1c"], PK1c)"""); } public override async Task ReadItem_with_hierarchical_partition_key_leaf() @@ -779,12 +774,7 @@ public override async Task ReadItem_with_WithPartitionKey_with_only_partition_ke { await base.ReadItem_with_WithPartitionKey_with_only_partition_key_leaf(); - AssertSql( - """ -SELECT VALUE c -FROM root c -WHERE ((c["$type"] = "DerivedOnlySinglePartitionKeyEntity") AND (c["id"] = "PK1c")) -"""); + AssertSql("""ReadItem(["PK1c"], PK1c)"""); } public override async Task WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key() diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs index d5c63bae6e6..535bd82a0cc 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs @@ -678,12 +678,7 @@ public override async Task Both_WithPartitionKey_and_predicate_comparisons_with_ { await base.Both_WithPartitionKey_and_predicate_comparisons_with_same_values_with_only_partition_key_leaf(); - AssertSql( - """ -SELECT VALUE c -FROM root c -WHERE ((c["$type"] = "DerivedOnlySinglePartitionKeyEntity") AND (c["PartitionKey"] = "PK1c")) -"""); + AssertSql("""ReadItem(["PK1c"], OnlySinglePartitionKeyEntity|PK1c)"""); } public override async Task ReadItem_with_hierarchical_partition_key_leaf() @@ -774,12 +769,7 @@ public override async Task ReadItem_with_WithPartitionKey_with_only_partition_ke { await base.ReadItem_with_WithPartitionKey_with_only_partition_key_leaf(); - AssertSql( - """ -SELECT VALUE c -FROM root c -WHERE ((c["$type"] = "DerivedOnlySinglePartitionKeyEntity") AND (c["PartitionKey"] = "PK1c")) -"""); + AssertSql("""ReadItem(["PK1c"], OnlySinglePartitionKeyEntity|PK1c)"""); } public override async Task WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key() diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs index fe5d8cbdd4c..3005aa45a06 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs @@ -267,13 +267,7 @@ public override async Task Both_WithPartitionKey_and_predicate_comparisons_with_ { await base.Both_WithPartitionKey_and_predicate_comparisons_with_same_values_with_only_partition_key(); - // Not ReadItem because partition key values were provided both via WithPartitionKey and in the predicate (see #38238). - AssertSql( - """ -SELECT VALUE c -FROM root c -WHERE (c["id"] = "PK1a") -"""); + AssertSql("""ReadItem(["PK1a"], PK1a)"""); } public override async Task ReadItem_with_hierarchical_partition_key() @@ -364,13 +358,7 @@ public override async Task ReadItem_with_WithPartitionKey_with_only_partition_ke { await base.ReadItem_with_WithPartitionKey_with_only_partition_key(); - // Not ReadItem because partition key values were provided both via WithPartitionKey and in the predicate (see #38238). - AssertSql( - """ -SELECT VALUE c -FROM root c -WHERE (c["id"] = "PK1a") -"""); + AssertSql("""ReadItem(["PK1a"], PK1a)"""); } public override async Task WithPartitionKey_and_predicate_with_id_and_conflicting_partition_key() From 696a57efd693935be7e6d70f6dbd8b910daa77db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 02:40:53 +0000 Subject: [PATCH 6/8] Fix ReadItem regression for OnlySinglePartitionKeyEntity with WithPartitionKey + same predicate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a literal is passed to WithPartitionKey() it gets funcletized to a SqlParameterExpression (outside a lambda), while the same literal in a Where() stays as a SqlConstantExpression. SqlExpression.Equals() returned false for different subtypes even with identical values, so the conflict detection incorrectly disabled ReadItem for same-value cases. Fix: when withPkValue is a parameter and predicateValue is a constant, and all partition key properties are encoded in the JSON ID, ReadItem is safe regardless of runtime value — a wrong partition key returns null rather than the wrong document. Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- .../CosmosReadItemAndPartitionKeysExtractor.cs | 15 +++++++++++++-- ...temPartitionKeyQueryNoDiscriminatorInIdTest.cs | 14 ++------------ ...mPartitionKeyQueryRootDiscriminatorInIdTest.cs | 14 ++------------ 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs index fd29bb56a17..bb3e3fb0a98 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs @@ -105,13 +105,24 @@ public virtual Expression ExtractPartitionKeysAndId( // Check whether any predicate partition key value conflicts with the WithPartitionKey() value at the same position. // WithPartitionKey() values are translated with applyDefaultTypeMapping:false (TypeMapping is null), so we apply the // predicate value's TypeMapping before comparing to ensure identical constants/parameters compare as equal. + + // When a literal is passed to WithPartitionKey(), the funcletizer converts it to a SqlParameterExpression (since it's + // outside a lambda), while the same literal inside a Where() lambda stays as a SqlConstantExpression. Their types + // differ so Equals() returns false even when values match — but when all partition key properties are part of the JSON + // ID, ReadItem is safe regardless: the document's partition key is encoded in its ID, so a wrong partition key returns + // null rather than the wrong document. + var pkPropertiesInJsonId = partitionKeyProperties.All(p => jsonIdProperties.Contains(p)); + _partitionKeyValuesInPredicateConflictWithReadItem = partitionKeyProperties .Select((p, i) => (Property: p, Index: i)) .Any(t => _partitionKeyPropertyValues[t.Property].ValueExpression is SqlExpression predicateValue && (t.Index >= queryCompilationContext.PartitionKeyPropertyValues.Count || queryCompilationContext.PartitionKeyPropertyValues[t.Index] is not SqlExpression withPkValue - || !predicateValue.Equals( - _sqlExpressionFactory.ApplyTypeMapping(withPkValue, predicateValue.TypeMapping)))); + || (!predicateValue.Equals( + _sqlExpressionFactory.ApplyTypeMapping(withPkValue, predicateValue.TypeMapping)) + && !(pkPropertiesInJsonId + && withPkValue is SqlParameterExpression + && predicateValue is SqlConstantExpression)))); } foreach (var property in partitionKeyProperties) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs index ae5b82ccdad..5eb035e93a2 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs @@ -275,12 +275,7 @@ public override async Task Both_WithPartitionKey_and_predicate_comparisons_with_ { await base.Both_WithPartitionKey_and_predicate_comparisons_with_same_values_with_only_partition_key(); - AssertSql( - """ -SELECT VALUE c -FROM root c -WHERE (c["$type"] IN ("OnlySinglePartitionKeyEntity", "DerivedOnlySinglePartitionKeyEntity") AND (c["id"] = "PK1a")) -"""); + AssertSql("""ReadItem(["PK1a"], PK1a)"""); } public override async Task ReadItem_with_hierarchical_partition_key() @@ -371,12 +366,7 @@ public override async Task ReadItem_with_WithPartitionKey_with_only_partition_ke { await base.ReadItem_with_WithPartitionKey_with_only_partition_key(); - AssertSql( - """ -SELECT VALUE c -FROM root c -WHERE (c["$type"] IN ("OnlySinglePartitionKeyEntity", "DerivedOnlySinglePartitionKeyEntity") AND (c["id"] = "PK1a")) -"""); + AssertSql("""ReadItem(["PK1a"], PK1a)"""); } public override async Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem() diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs index 535bd82a0cc..bd8489b5e15 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs @@ -268,12 +268,7 @@ public override async Task Both_WithPartitionKey_and_predicate_comparisons_with_ { await base.Both_WithPartitionKey_and_predicate_comparisons_with_same_values_with_only_partition_key(); - AssertSql( - """ -SELECT VALUE c -FROM root c -WHERE (c["$type"] IN ("OnlySinglePartitionKeyEntity", "DerivedOnlySinglePartitionKeyEntity") AND (c["PartitionKey"] = "PK1a")) -"""); + AssertSql("""ReadItem(["PK1a"], OnlySinglePartitionKeyEntity|PK1a)"""); } public override async Task ReadItem_with_hierarchical_partition_key() @@ -364,12 +359,7 @@ public override async Task ReadItem_with_WithPartitionKey_with_only_partition_ke { await base.ReadItem_with_WithPartitionKey_with_only_partition_key(); - AssertSql( - """ -SELECT VALUE c -FROM root c -WHERE (c["$type"] IN ("OnlySinglePartitionKeyEntity", "DerivedOnlySinglePartitionKeyEntity") AND (c["PartitionKey"] = "PK1a")) -"""); + AssertSql("""ReadItem(["PK1a"], OnlySinglePartitionKeyEntity|PK1a)"""); } public override async Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem() From 0e3b2dbc682d39b00ba03c69fc0d154a7595c509 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 04:34:51 +0000 Subject: [PATCH 7/8] Simplify WithPartitionKey conflict detection: replace LINQ with for loop, shorten comments Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- ...CosmosReadItemAndPartitionKeysExtractor.cs | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs index bb3e3fb0a98..06648b516e0 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs @@ -98,31 +98,30 @@ public virtual Expression ExtractPartitionKeysAndId( // given in WithPartitionKey and return zero results - that's the expected behavior). var liftPartitionKeys = queryCompilationContext.PartitionKeyPropertyValues.Count == 0; - // Whether partition key values were provided via WithPartitionKey() (as opposed to being lifted from the predicate below). - var partitionKeyValuesProvidedViaApi = !liftPartitionKeys; - if (partitionKeyValuesProvidedViaApi) + if (!liftPartitionKeys) // partition key values provided via WithPartitionKey() { - // Check whether any predicate partition key value conflicts with the WithPartitionKey() value at the same position. // WithPartitionKey() values are translated with applyDefaultTypeMapping:false (TypeMapping is null), so we apply the - // predicate value's TypeMapping before comparing to ensure identical constants/parameters compare as equal. - - // When a literal is passed to WithPartitionKey(), the funcletizer converts it to a SqlParameterExpression (since it's - // outside a lambda), while the same literal inside a Where() lambda stays as a SqlConstantExpression. Their types - // differ so Equals() returns false even when values match — but when all partition key properties are part of the JSON - // ID, ReadItem is safe regardless: the document's partition key is encoded in its ID, so a wrong partition key returns - // null rather than the wrong document. + // predicate value's TypeMapping before comparing. A literal WithPartitionKey argument is also parameterized by the + // funcletizer (outside a lambda) while the same literal in Where() stays a constant — when all PK properties are in + // the JSON ID, ReadItem is safe in that case: a wrong partition key returns null, not the wrong document. var pkPropertiesInJsonId = partitionKeyProperties.All(p => jsonIdProperties.Contains(p)); - _partitionKeyValuesInPredicateConflictWithReadItem = partitionKeyProperties - .Select((p, i) => (Property: p, Index: i)) - .Any(t => _partitionKeyPropertyValues[t.Property].ValueExpression is SqlExpression predicateValue - && (t.Index >= queryCompilationContext.PartitionKeyPropertyValues.Count - || queryCompilationContext.PartitionKeyPropertyValues[t.Index] is not SqlExpression withPkValue - || (!predicateValue.Equals( - _sqlExpressionFactory.ApplyTypeMapping(withPkValue, predicateValue.TypeMapping)) - && !(pkPropertiesInJsonId - && withPkValue is SqlParameterExpression - && predicateValue is SqlConstantExpression)))); + for (var i = 0; i < partitionKeyProperties.Count; i++) + { + if (_partitionKeyPropertyValues[partitionKeyProperties[i]].ValueExpression is not SqlExpression predicateValue) + { + continue; + } + + if (i >= queryCompilationContext.PartitionKeyPropertyValues.Count + || queryCompilationContext.PartitionKeyPropertyValues[i] is not SqlExpression withPkValue + || (!predicateValue.Equals(_sqlExpressionFactory.ApplyTypeMapping(withPkValue, predicateValue.TypeMapping)) + && !(pkPropertiesInJsonId && withPkValue is SqlParameterExpression && predicateValue is SqlConstantExpression))) + { + _partitionKeyValuesInPredicateConflictWithReadItem = true; + break; + } + } } foreach (var property in partitionKeyProperties) From c8dc731235fe5100e5ddcace6f43b0eca83cca61 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 04:48:34 +0000 Subject: [PATCH 8/8] Add WithPartitionKey_and_predicate_with_id_two_locals_same_value test Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- ...ItemPartitionKeyQueryDiscriminatorInIdTest.cs | 16 ++++++++++++++++ ...emPartitionKeyQueryNoDiscriminatorInIdTest.cs | 16 ++++++++++++++++ ...PartitionKeyQueryRootDiscriminatorInIdTest.cs | 16 ++++++++++++++++ .../Query/ReadItemPartitionKeyQueryTest.cs | 16 ++++++++++++++++ .../Query/ReadItemPartitionKeyQueryTestBase.cs | 14 ++++++++++++++ 5 files changed, 78 insertions(+) diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryDiscriminatorInIdTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryDiscriminatorInIdTest.cs index 1216cd68311..5d9e249d3fd 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryDiscriminatorInIdTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryDiscriminatorInIdTest.cs @@ -456,6 +456,22 @@ FROM root c """); } + public override async Task WithPartitionKey_and_predicate_with_id_two_locals_same_value() + { + await base.WithPartitionKey_and_predicate_with_id_two_locals_same_value(); + + // Not ReadItem because pkForWith and pkForWhere are different parameter expressions (different names), + // so they can't be compared at translate time even though they have the same runtime value. + AssertSql( + """ +@pkForWhere='PK1' + +SELECT VALUE c +FROM root c +WHERE (c["$type"] IN ("SinglePartitionKeyEntity", "DerivedSinglePartitionKeyEntity") AND ((c["Id"] = "b29bced8-e1e5-420e-82d7-1c7a51703d34") AND (c["PartitionKey"] = @pkForWhere))) +"""); + } + public override async Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem() { await base.Multiple_incompatible_predicate_comparisons_cause_no_ReadItem(); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs index 5eb035e93a2..7d4f6466f61 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryNoDiscriminatorInIdTest.cs @@ -369,6 +369,22 @@ public override async Task ReadItem_with_WithPartitionKey_with_only_partition_ke AssertSql("""ReadItem(["PK1a"], PK1a)"""); } + public override async Task WithPartitionKey_and_predicate_with_id_two_locals_same_value() + { + await base.WithPartitionKey_and_predicate_with_id_two_locals_same_value(); + + // Not ReadItem because pkForWith and pkForWhere are different parameter expressions (different names), + // so they can't be compared at translate time even though they have the same runtime value. + AssertSql( + """ +@pkForWhere='PK1' + +SELECT VALUE c +FROM root c +WHERE (c["$type"] IN ("SinglePartitionKeyEntity", "DerivedSinglePartitionKeyEntity") AND ((c["id"] = "b29bced8-e1e5-420e-82d7-1c7a51703d34") AND (c["PartitionKey"] = @pkForWhere))) +"""); + } + public override async Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem() { await base.Multiple_incompatible_predicate_comparisons_cause_no_ReadItem(); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs index bd8489b5e15..10d74c7119c 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryRootDiscriminatorInIdTest.cs @@ -362,6 +362,22 @@ public override async Task ReadItem_with_WithPartitionKey_with_only_partition_ke AssertSql("""ReadItem(["PK1a"], OnlySinglePartitionKeyEntity|PK1a)"""); } + public override async Task WithPartitionKey_and_predicate_with_id_two_locals_same_value() + { + await base.WithPartitionKey_and_predicate_with_id_two_locals_same_value(); + + // Not ReadItem because pkForWith and pkForWhere are different parameter expressions (different names), + // so they can't be compared at translate time even though they have the same runtime value. + AssertSql( + """ +@pkForWhere='PK1' + +SELECT VALUE c +FROM root c +WHERE (c["$type"] IN ("SinglePartitionKeyEntity", "DerivedSinglePartitionKeyEntity") AND ((c["Id"] = "b29bced8-e1e5-420e-82d7-1c7a51703d34") AND (c["PartitionKey"] = @pkForWhere))) +"""); + } + public override async Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem() { await base.Multiple_incompatible_predicate_comparisons_cause_no_ReadItem(); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs index 3005aa45a06..c8e6c63e809 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTest.cs @@ -374,6 +374,22 @@ FROM root c """); } + public override async Task WithPartitionKey_and_predicate_with_id_two_locals_same_value() + { + await base.WithPartitionKey_and_predicate_with_id_two_locals_same_value(); + + // Not ReadItem because pkForWith and pkForWhere are different parameter expressions (different names), + // so they can't be compared at translate time even though they have the same runtime value. + AssertSql( + """ +@pkForWhere='PK1' + +SELECT VALUE c +FROM root c +WHERE ((c["id"] = "b29bced8-e1e5-420e-82d7-1c7a51703d34") AND (c["PartitionKey"] = @pkForWhere)) +"""); + } + public override async Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem() { await base.Multiple_incompatible_predicate_comparisons_cause_no_ReadItem(); diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTestBase.cs b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTestBase.cs index ceadd640109..9cb0b7a6195 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTestBase.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/ReadItemPartitionKeyQueryTestBase.cs @@ -302,6 +302,20 @@ public virtual Task WithPartitionKey_and_predicate_with_id_and_conflicting_parti .Where(e => e.Id == Guid.Parse("B29BCED8-E1E5-420E-82D7-1C7A51703D34") && e.PartitionKey == "PK2"), assertEmpty: true); + [Fact] + public virtual Task WithPartitionKey_and_predicate_with_id_two_locals_same_value() + { + var pkForWith = "PK1"; + var pkForWhere = "PK1"; + + return AssertQuery( + async: true, + ss => ss.Set().WithPartitionKey(pkForWith) + .Where(e => e.Id == Guid.Parse("B29BCED8-E1E5-420E-82D7-1C7A51703D34") && e.PartitionKey == pkForWhere), + ss => ss.Set().Where(e => e.PartitionKey == pkForWith) + .Where(e => e.Id == Guid.Parse("B29BCED8-E1E5-420E-82D7-1C7A51703D34") && e.PartitionKey == pkForWhere)); + } + [Fact] public virtual Task Multiple_incompatible_predicate_comparisons_cause_no_ReadItem() {