Skip to content

Commit 3834fa9

Browse files
authored
Merge branch 'main' into copilot/fix-invalid-property-name-compilation
2 parents 86de7a0 + 35e052e commit 3834fa9

288 files changed

Lines changed: 10980 additions & 40997 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/Build.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444

4545
steps:
4646
- name: Checkout
47-
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
47+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
4848

4949
- name: Restore
5050
if: runner.os != 'Windows'
@@ -91,7 +91,7 @@ jobs:
9191
Start-CosmosDbEmulator -Timeout 540 -NoUI -NoTelemetry -NoFirewall -EnablePreview
9292
9393
- name: Checkout
94-
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
94+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
9595

9696
- name: Restore
9797
if: runner.os != 'Windows'
@@ -143,7 +143,7 @@ jobs:
143143
144144
steps:
145145
- name: Checkout
146-
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
146+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
147147

148148
- name: Restore
149149
run: ./restore.sh
@@ -177,7 +177,7 @@ jobs:
177177

178178
steps:
179179
- name: Checkout
180-
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
180+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
181181

182182
- name: Restore
183183
if: runner.os != 'Windows'
@@ -216,7 +216,7 @@ jobs:
216216

217217
steps:
218218
- name: Checkout
219-
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
219+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
220220

221221
- name: Restore
222222
if: runner.os != 'Windows'

.github/workflows/api-review-baselines.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ jobs:
113113
114114
- name: Check out selected commit
115115
if: steps.detect.outputs.has_baselines == 'true'
116-
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
116+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
117117
with:
118118
ref: ${{ steps.detect.outputs.target_sha }}
119119
fetch-depth: 1

.github/workflows/copilot-setup-steps.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
--health-timeout=5s
5555
5656
steps:
57-
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
57+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
5858

5959
- name: Restore .NET SDK
6060
continue-on-error: true

.github/workflows/inter-branch-merge-flow.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ permissions:
1010

1111
jobs:
1212
Merge:
13-
uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@a6bacc00545c78b35c459f81331508e43d835d23
13+
uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@69bac0b76e58dc33ee3ee9740b6ae5b3e4c3b65a

eng/Versions.props

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@
4949
<MicrosoftCodeAnalysisCSharpTestingVersion>1.1.4</MicrosoftCodeAnalysisCSharpTestingVersion>
5050
<AzureIdentityVersion>1.21.0</AzureIdentityVersion>
5151
<AzureResourceManagerCosmosDBVersion>1.4.1</AzureResourceManagerCosmosDBVersion>
52-
<OpenTelemetryExporterInMemoryVersion>1.15.1</OpenTelemetryExporterInMemoryVersion>
52+
<OpenTelemetryExporterInMemoryVersion>1.16.0</OpenTelemetryExporterInMemoryVersion>
5353
<SQLitePCLRawVersion>3.0.3</SQLitePCLRawVersion>
5454
<SQLite3MCPCLRawBundleVersion>2.3.5</SQLite3MCPCLRawBundleVersion>
5555
<XUnitV3Version>4.0.0-pre.108</XUnitV3Version>
56-
<MicrosoftDotNetXUnitV3ExtensionsVersion>11.0.0-beta.26312.108</MicrosoftDotNetXUnitV3ExtensionsVersion>
56+
<MicrosoftDotNetXUnitV3ExtensionsVersion>11.0.0-beta.26319.1</MicrosoftDotNetXUnitV3ExtensionsVersion>
5757
<MicrosoftTestingPlatformVersion>2.2.2</MicrosoftTestingPlatformVersion>
5858
</PropertyGroup>
5959

@@ -64,7 +64,7 @@
6464

6565
<PropertyGroup Label="Tooling dependencies">
6666
<ICSharpCodeDecompilerVersion>10.1.0.8386</ICSharpCodeDecompilerVersion>
67-
<SystemCommandLineVersion>2.0.5</SystemCommandLineVersion>
67+
<SystemCommandLineVersion>2.0.9</SystemCommandLineVersion>
6868
</PropertyGroup>
6969

7070
</Project>

src/EFCore.Cosmos/Query/Internal/CosmosReadItemAndPartitionKeysExtractor.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class CosmosReadItemAndPartitionKeysExtractor : ExpressionVisitor
2121
private IEntityType _entityType = null!;
2222
private string _rootAlias = null!;
2323
private bool _isPredicateCompatibleWithReadItem;
24+
private bool _partitionKeyValuesInPredicateConflictWithReadItem;
2425
private bool _nonRootDiscriminatorInJsonId;
2526
private string? _discriminatorJsonPropertyName;
2627
private Dictionary<IProperty, Expression?> _jsonIdPropertyValues = null!;
@@ -60,6 +61,7 @@ public virtual Expression ExtractPartitionKeysAndId(
6061
// from the tree (either constants or parameters).
6162
// We also want to ignore the discriminator property if it's compared to our entity type's discriminator value (see below).
6263
_isPredicateCompatibleWithReadItem = true;
64+
_partitionKeyValuesInPredicateConflictWithReadItem = false;
6365
var jsonIdDefinition = _entityType.GetJsonIdDefinition();
6466

6567
var jsonIdProperties = jsonIdDefinition?.Properties ?? [];
@@ -95,6 +97,33 @@ public virtual Expression ExtractPartitionKeysAndId(
9597
// this case, we skip lifting the predicate comparisons and leave the predicate exactly as it is (it may conflict with the values
9698
// given in WithPartitionKey and return zero results - that's the expected behavior).
9799
var liftPartitionKeys = queryCompilationContext.PartitionKeyPropertyValues.Count == 0;
100+
101+
if (!liftPartitionKeys) // partition key values provided via WithPartitionKey()
102+
{
103+
// WithPartitionKey() values are translated with applyDefaultTypeMapping:false (TypeMapping is null), so we apply the
104+
// predicate value's TypeMapping before comparing. A literal WithPartitionKey argument is also parameterized by the
105+
// funcletizer (outside a lambda) while the same literal in Where() stays a constant — when all PK properties are in
106+
// the JSON ID, ReadItem is safe in that case: a wrong partition key returns null, not the wrong document.
107+
var pkPropertiesInJsonId = partitionKeyProperties.All(p => jsonIdProperties.Contains(p));
108+
109+
for (var i = 0; i < partitionKeyProperties.Count; i++)
110+
{
111+
if (_partitionKeyPropertyValues[partitionKeyProperties[i]].ValueExpression is not SqlExpression predicateValue)
112+
{
113+
continue;
114+
}
115+
116+
if (i >= queryCompilationContext.PartitionKeyPropertyValues.Count
117+
|| queryCompilationContext.PartitionKeyPropertyValues[i] is not SqlExpression withPkValue
118+
|| (!predicateValue.Equals(_sqlExpressionFactory.ApplyTypeMapping(withPkValue, predicateValue.TypeMapping))
119+
&& !(pkPropertiesInJsonId && withPkValue is SqlParameterExpression && predicateValue is SqlConstantExpression)))
120+
{
121+
_partitionKeyValuesInPredicateConflictWithReadItem = true;
122+
break;
123+
}
124+
}
125+
}
126+
98127
foreach (var property in partitionKeyProperties)
99128
{
100129
if (liftPartitionKeys && _partitionKeyPropertyValues[property].ValueExpression is { } valueExpression)
@@ -115,6 +144,10 @@ public virtual Expression ExtractPartitionKeysAndId(
115144
// predicate, and *all* partition key values are specified(in the predicate or via WithPartitionKey)
116145
if (_isPredicateCompatibleWithReadItem
117146
&& allIdPropertiesSpecified
147+
// If partition key values were provided via WithPartitionKey() and the predicate specifies different values, we don't
148+
// transform to ReadItem: that would silently ignore the predicate and return a result for the WithPartitionKey() value.
149+
// Executing as a regular query instead correctly yields zero results on conflict (see #38238).
150+
&& !_partitionKeyValuesInPredicateConflictWithReadItem
118151
&& queryCompilationContext.PartitionKeyPropertyValues.Count == partitionKeyProperties.Count
119152
&& select is
120153
{
@@ -301,7 +334,7 @@ void ProcessPropertyComparison(string propertyName, SqlExpression propertyValue,
301334
if (propertyName == property.GetJsonPropertyName())
302335
{
303336
if (_jsonIdPropertyValues.TryGetValue(property, out var previousValue)
304-
&& (previousValue is null || previousValue.Equals(propertyValue)))
337+
&& (previousValue is null || ExpressionEqualityComparer.Instance.Equals(previousValue, propertyValue)))
305338
{
306339
_jsonIdPropertyValues[property] = propertyValue;
307340
isCompatibleComparisonForReadItem = true;
@@ -320,7 +353,8 @@ void ProcessPropertyComparison(string propertyName, SqlExpression propertyValue,
320353
// call. Note that this is always considered a compatible comparison for ReadItem.
321354
if (propertyName == property.GetJsonPropertyName()
322355
&& _partitionKeyPropertyValues.TryGetValue(property, out var previousValues)
323-
&& (previousValues.ValueExpression is null || previousValues.Equals(propertyValue)))
356+
&& (previousValues.ValueExpression is null
357+
|| ExpressionEqualityComparer.Instance.Equals(previousValues.ValueExpression, propertyValue)))
324358
{
325359
_partitionKeyPropertyValues[property] = (ValueExpression: propertyValue, OriginalExpression: originalExpression);
326360
return;

src/EFCore.Cosmos/Storage/Internal/CosmosTimeOnlyTypeMapping.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
99
/// any release. You should only use it directly in your code with extreme caution and knowing that
1010
/// doing so can result in application failures when updating to a new Entity Framework Core release.
1111
/// </summary>
12-
public class CosmosTimeOnlyTypeMapping : CosmosTypeMapping
12+
public class CosmosTimeOnlyTypeMapping : CosmosTypeMapping<TimeOnly>
1313
{
1414
/// <summary>
1515
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -25,7 +25,7 @@ public class CosmosTimeOnlyTypeMapping : CosmosTypeMapping
2525
/// any release. You should only use it directly in your code with extreme caution and knowing that
2626
/// doing so can result in application failures when updating to a new Entity Framework Core release.
2727
/// </summary>
28-
public CosmosTimeOnlyTypeMapping() : base(typeof(TimeOnly), null, null, null, CosmosJsonTimeOnlyReaderWriter.Instance)
28+
public CosmosTimeOnlyTypeMapping() : base(jsonValueReaderWriter: CosmosJsonTimeOnlyReaderWriter.Instance)
2929
{
3030
}
3131

src/EFCore.Cosmos/Storage/Internal/CosmosTimeSpanTypeMapping.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal;
99
/// any release. You should only use it directly in your code with extreme caution and knowing that
1010
/// doing so can result in application failures when updating to a new Entity Framework Core release.
1111
/// </summary>
12-
public class CosmosTimeSpanTypeMapping : CosmosTypeMapping
12+
public class CosmosTimeSpanTypeMapping : CosmosTypeMapping<TimeSpan>
1313
{
1414
/// <summary>
1515
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -25,7 +25,7 @@ public class CosmosTimeSpanTypeMapping : CosmosTypeMapping
2525
/// any release. You should only use it directly in your code with extreme caution and knowing that
2626
/// doing so can result in application failures when updating to a new Entity Framework Core release.
2727
/// </summary>
28-
public CosmosTimeSpanTypeMapping() : base(typeof(TimeSpan), null, null, null, CosmosJsonTimeSpanReaderWriter.Instance)
28+
public CosmosTimeSpanTypeMapping() : base(jsonValueReaderWriter: CosmosJsonTimeSpanReaderWriter.Instance)
2929
{
3030
}
3131

src/EFCore.Cosmos/Storage/Internal/CosmosTypeMappingSource.cs

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Frozen;
5+
using System.Diagnostics.CodeAnalysis;
56
using System.Text.Json;
67
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
78
using Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal;
@@ -36,7 +37,7 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies)
3637
{ typeof(TimeOnly), CosmosTimeOnlyTypeMapping.Default },
3738
{ typeof(TimeSpan), CosmosTimeSpanTypeMapping.Default },
3839
{
39-
typeof(JObject), new CosmosTypeMapping(
40+
typeof(JObject), CreateMapping(
4041
typeof(JObject), jsonValueReaderWriter: dependencies.JsonValueReaderWriterSource.FindReaderWriter(typeof(JObject)))
4142
}
4243
}.ToFrozenDictionary();
@@ -115,10 +116,10 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies)
115116
CoreTypeMapping? typeMapping = null;
116117
return !TryFindJsonCollectionMapping(elementMappingInfo, memoryType.MakeArrayType(), null, ref typeMapping, out var _, out var readerWriter)
117118
? null
118-
: new CosmosTypeMapping(clrType, jsonValueReaderWriter: readerWriter)
119-
.WithComposedConverter(
120-
(ValueConverter)Activator.CreateInstance(typeof(ReadOnlyMemoryConverter<>).MakeGenericType(memoryType))!,
121-
(ValueComparer)Activator.CreateInstance(typeof(ReadOnlyMemoryComparer<>).MakeGenericType(memoryType))!);
119+
: CreateMapping(clrType, jsonValueReaderWriter: readerWriter)
120+
.WithComposedConverter(
121+
(ValueConverter)Activator.CreateInstance(typeof(ReadOnlyMemoryConverter<>).MakeGenericType(memoryType))!,
122+
(ValueComparer)Activator.CreateInstance(typeof(ReadOnlyMemoryComparer<>).MakeGenericType(memoryType))!);
122123
}
123124

124125
return clrType.IsNumeric()
@@ -129,7 +130,7 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies)
129130
|| clrType == typeof(DateTimeOffset)
130131
|| clrType == typeof(TimeSpan)
131132
|| clrType == typeof(string)
132-
? new CosmosTypeMapping(
133+
? CreateMapping(
133134
clrType, jsonValueReaderWriter: Dependencies.JsonValueReaderWriterSource.FindReaderWriter(clrType))
134135
: null;
135136
}
@@ -155,7 +156,7 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies)
155156
out var collectionReaderWriter)
156157
&& elementMapping is not null)
157158
{
158-
return new CosmosTypeMapping(
159+
return CreateMapping(
159160
clrType,
160161
elementComparer,
161162
elementMapping: elementMapping,
@@ -234,7 +235,7 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies)
234235
}
235236
}
236237

237-
return new CosmosTypeMapping(
238+
return CreateMapping(
238239
clrType,
239240
CreateStringDictionaryComparer(elementMapping, elementType, clrType),
240241
jsonValueReaderWriter: jsonValueReaderWriter);
@@ -245,14 +246,67 @@ public CosmosTypeMappingSource(TypeMappingSourceDependencies dependencies)
245246
return null;
246247
}
247248

249+
private static CosmosTypeMapping CreateMapping(
250+
Type clrType,
251+
ValueComparer? comparer = null,
252+
ValueComparer? keyComparer = null,
253+
CoreTypeMapping? elementMapping = null,
254+
JsonValueReaderWriter? jsonValueReaderWriter = null)
255+
=> clrType switch
256+
{
257+
_ when clrType == typeof(bool) => Create<bool>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
258+
_ when clrType == typeof(byte) => Create<byte>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
259+
_ when clrType == typeof(sbyte) => Create<sbyte>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
260+
_ when clrType == typeof(char) => Create<char>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
261+
_ when clrType == typeof(short) => Create<short>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
262+
_ when clrType == typeof(ushort) => Create<ushort>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
263+
_ when clrType == typeof(int) => Create<int>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
264+
_ when clrType == typeof(uint) => Create<uint>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
265+
_ when clrType == typeof(long) => Create<long>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
266+
_ when clrType == typeof(ulong) => Create<ulong>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
267+
_ when clrType == typeof(float) => Create<float>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
268+
_ when clrType == typeof(double) => Create<double>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
269+
_ when clrType == typeof(decimal) => Create<decimal>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
270+
_ when clrType == typeof(string) => Create<string>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
271+
_ when clrType == typeof(Guid) => Create<Guid>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
272+
_ when clrType == typeof(DateTime) => Create<DateTime>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
273+
_ when clrType == typeof(DateTimeOffset) => Create<DateTimeOffset>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
274+
_ when clrType == typeof(DateOnly) => Create<DateOnly>(comparer, keyComparer, elementMapping, jsonValueReaderWriter),
275+
_ => CreateMappingWithReflection(clrType, comparer, keyComparer, elementMapping, jsonValueReaderWriter)
276+
};
277+
278+
private static CosmosTypeMapping Create<
279+
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicProperties)] T>(
280+
ValueComparer? comparer,
281+
ValueComparer? keyComparer,
282+
CoreTypeMapping? elementMapping,
283+
JsonValueReaderWriter? jsonValueReaderWriter)
284+
=> comparer is null && keyComparer is null && elementMapping is null && jsonValueReaderWriter is null
285+
? CosmosTypeMapping<T>.Default
286+
: new CosmosTypeMapping<T>(comparer, keyComparer, elementMapping, jsonValueReaderWriter);
287+
288+
[UnconditionalSuppressMessage(
289+
"AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.",
290+
Justification = "The type mapping source is not used at runtime by NativeAOT applications, which use a compiled model instead.")]
291+
private static CosmosTypeMapping CreateMappingWithReflection(
292+
Type clrType,
293+
ValueComparer? comparer,
294+
ValueComparer? keyComparer,
295+
CoreTypeMapping? elementMapping,
296+
JsonValueReaderWriter? jsonValueReaderWriter)
297+
{
298+
var genericType = typeof(CosmosTypeMapping<>).MakeGenericType(clrType);
299+
return comparer is null && keyComparer is null && elementMapping is null && jsonValueReaderWriter is null
300+
? (CosmosTypeMapping)genericType.GetAnyProperty(nameof(CosmosTypeMapping<object>.Default))!.GetValue(null)!
301+
: (CosmosTypeMapping)Activator.CreateInstance(genericType, comparer, keyComparer, elementMapping, jsonValueReaderWriter)!;
302+
}
303+
248304
private static ValueComparer CreateStringDictionaryComparer(
249305
CoreTypeMapping elementMapping,
250306
Type elementType,
251307
Type dictType,
252308
bool readOnly = false)
253309
{
254-
var unwrappedType = elementType.UnwrapNullableType();
255-
256310
return (ValueComparer)Activator.CreateInstance(
257311
typeof(StringDictionaryComparer<,>).MakeGenericType(dictType, elementType),
258312
#pragma warning disable EF1001 // Internal EF Core API usage.

0 commit comments

Comments
 (0)