Skip to content

Commit e5080b7

Browse files
authored
Merge release/9.0-staging to release/9.0 (#37294)
2 parents 4726c29 + 3532857 commit e5080b7

File tree

14 files changed

+480
-19
lines changed

14 files changed

+480
-19
lines changed

azure-pipelines-public.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ stages:
9393
- job: macOS
9494
enablePublishTestResults: true
9595
pool:
96-
vmImage: macOS-13
96+
vmImage: macOS-15
9797
variables:
9898
# Rely on task Arcade injects, not auto-injected build step.
9999
- skipComponentGovernanceDetection: true
@@ -151,7 +151,7 @@ stages:
151151
- name: _HelixBuildConfig
152152
value: $(_BuildConfig)
153153
- name: HelixTargetQueues
154-
value: Windows.10.Amd64.Open;OSX.13.ARM64.Open;[email protected]/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
154+
value: Windows.10.Amd64.Open;OSX.15.ARM64.Open;[email protected]/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
155155
- name: _HelixAccessToken
156156
value: '' # Needed for public queues
157157
steps:

azure-pipelines.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ extends:
140140
- job: macOS
141141
pool:
142142
name: Azure Pipelines
143-
image: macOS-13
143+
image: macOS-15
144144
os: macOS
145145
variables:
146146
# Rely on task Arcade injects, not auto-injected build step.
@@ -195,7 +195,7 @@ extends:
195195
- name: _HelixBuildConfig
196196
value: $(_BuildConfig)
197197
- name: HelixTargetQueues
198-
value: Windows.10.Amd64;OSX.13.ARM64;[email protected]/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
198+
value: Windows.10.Amd64;OSX.15.ARM64;[email protected]/dotnet-buildtools/prereqs:ubuntu-20.04-helix-sqlserver-amd64
199199
- name: _HelixAccessToken
200200
# Needed for internal queues
201201
value: $(HelixApiAccessToken)

src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs

Lines changed: 15 additions & 0 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.Text;
5+
using System.Text.Json;
56
using Microsoft.EntityFrameworkCore.Internal;
67
using Microsoft.EntityFrameworkCore.Metadata.Internal;
78

@@ -837,6 +838,20 @@ protected virtual void GenerateEntityTypeAnnotations(
837838
.FilterIgnoredAnnotations(entityType.GetAnnotations())
838839
.ToDictionary(a => a.Name, a => a);
839840

841+
// Add ContainerColumnType annotation if entity is mapped to JSON but the type annotation is missing
842+
if (annotations.ContainsKey(RelationalAnnotationNames.ContainerColumnName)
843+
&& !annotations.ContainsKey(RelationalAnnotationNames.ContainerColumnType))
844+
{
845+
var containerColumnType = entityType.GetContainerColumnType()
846+
?? Dependencies.RelationalTypeMappingSource.FindMapping(typeof(JsonElement))?.StoreType;
847+
if (containerColumnType != null)
848+
{
849+
annotations[RelationalAnnotationNames.ContainerColumnType] = new Annotation(
850+
RelationalAnnotationNames.ContainerColumnType,
851+
containerColumnType);
852+
}
853+
}
854+
840855
GenerateTableMapping(entityTypeBuilderName, entityType, stringBuilder, annotations);
841856
GenerateSplitTableMapping(entityTypeBuilderName, entityType, stringBuilder);
842857

src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ public class ExpressionTreeFuncletizer : ExpressionVisitor
115115
private static readonly bool UseOldBehavior35100 =
116116
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35100", out var enabled35100) && enabled35100;
117117

118+
private static readonly bool UseOldBehavior37176 =
119+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue37176", out var enabled37176) && enabled37176;
120+
118121
private static readonly MethodInfo ReadOnlyCollectionIndexerGetter = typeof(ReadOnlyCollection<Expression>).GetProperties()
119122
.Single(p => p.GetIndexParameters() is { Length: 1 } indexParameters && indexParameters[0].ParameterType == typeof(int)).GetMethod!;
120123

@@ -985,14 +988,32 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall)
985988
switch (method.Name)
986989
{
987990
case nameof(MemoryExtensions.Contains)
988-
when methodCall.Arguments is [var arg0, var arg1] && TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0):
991+
when UseOldBehavior37176
992+
&& methodCall.Arguments is [var arg0, var arg1]
993+
&& TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0):
989994
{
990995
return Visit(
991996
Call(
992997
EnumerableMethods.Contains.MakeGenericMethod(methodCall.Method.GetGenericArguments()[0]),
993998
unwrappedArg0, arg1));
994999
}
9951000

1001+
// In .NET 10, MemoryExtensions.Contains has an overload that accepts a third, optional comparer, in addition to the older
1002+
// overload that accepts two parameters only.
1003+
case nameof(MemoryExtensions.Contains)
1004+
when !UseOldBehavior37176
1005+
&& methodCall.Arguments is [var spanArg, var valueArg, ..]
1006+
&& (methodCall.Arguments.Count is 2
1007+
|| methodCall.Arguments.Count is 3
1008+
&& methodCall.Arguments[2] is ConstantExpression { Value: null })
1009+
&& TryUnwrapSpanImplicitCast(spanArg, out var unwrappedSpanArg):
1010+
{
1011+
return Visit(
1012+
Call(
1013+
EnumerableMethods.Contains.MakeGenericMethod(method.GetGenericArguments()[0]),
1014+
unwrappedSpanArg, valueArg));
1015+
}
1016+
9961017
case nameof(MemoryExtensions.SequenceEqual)
9971018
when methodCall.Arguments is [var arg0, var arg1]
9981019
&& TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0)
@@ -1005,20 +1026,37 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall)
10051026

10061027
static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
10071028
{
1008-
if (expression is MethodCallExpression
1029+
switch (expression)
1030+
{
1031+
// With newer versions of the SDK, the implicit cast is represented as a MethodCallExpression;
1032+
// with older versions, it's a Convert node.
1033+
case MethodCallExpression
10091034
{
10101035
Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
10111036
Arguments: [var unwrapped]
1037+
} when implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
1038+
&& (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)):
1039+
{
1040+
result = unwrapped;
1041+
return true;
10121042
}
1013-
&& implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
1014-
&& (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
1015-
{
1016-
result = unwrapped;
1017-
return true;
1018-
}
10191043

1020-
result = null;
1021-
return false;
1044+
case UnaryExpression
1045+
{
1046+
NodeType: ExpressionType.Convert,
1047+
Operand: var unwrapped,
1048+
Type: { IsGenericType: true } convertType
1049+
} when !UseOldBehavior37176 && convertType.GetGenericTypeDefinition() is var genericTypeDefinition
1050+
&& (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)):
1051+
{
1052+
result = unwrapped;
1053+
return true;
1054+
}
1055+
1056+
default:
1057+
result = null;
1058+
return false;
1059+
}
10221060
}
10231061
}
10241062

src/Microsoft.Data.Sqlite.Core/SqliteBlob.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,12 @@ public virtual int Read(Span<byte> buffer)
206206
count = (int)(Length - position);
207207
}
208208

209-
var rc = sqlite3_blob_read(_blob, buffer.Slice(0, count), (int)position);
210-
SqliteException.ThrowExceptionForRC(rc, _connection.Handle);
209+
// Newer sqlite3_blob_read returns error for 0-byte reads.
210+
if (count > 0)
211+
{
212+
var rc = sqlite3_blob_read(_blob, buffer.Slice(0, count), (int)position);
213+
SqliteException.ThrowExceptionForRC(rc, _connection.Handle);
214+
}
211215
_position += count;
212216
return count;
213217
}
@@ -280,8 +284,12 @@ public virtual void Write(ReadOnlySpan<byte> buffer)
280284
throw new NotSupportedException(Resources.ResizeNotSupported);
281285
}
282286

283-
var rc = sqlite3_blob_write(_blob, buffer.Slice(0, count), (int)position);
284-
SqliteException.ThrowExceptionForRC(rc, _connection.Handle);
287+
// Newer sqlite3_blob_write returns error for 0-byte writes.
288+
if (count > 0)
289+
{
290+
var rc = sqlite3_blob_write(_blob, buffer.Slice(0, count), (int)position);
291+
SqliteException.ThrowExceptionForRC(rc, _connection.Handle);
292+
}
285293
_position += count;
286294
}
287295

test/EFCore.Cosmos.FunctionalTests/Query/PrimitiveCollectionsQueryCosmosTest.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,50 @@ FROM root c
997997
""");
998998
});
999999

1000+
public override Task Contains_on_Enumerable(bool async)
1001+
=> CosmosTestHelpers.Instance.NoSyncTest(
1002+
async, async a =>
1003+
{
1004+
await base.Contains_on_Enumerable(a);
1005+
1006+
AssertSql(
1007+
"""
1008+
SELECT VALUE c
1009+
FROM root c
1010+
WHERE c["Int"] IN (10, 999)
1011+
""");
1012+
});
1013+
1014+
public override Task Contains_on_MemoryExtensions(bool async)
1015+
=> CosmosTestHelpers.Instance.NoSyncTest(
1016+
async, async a =>
1017+
{
1018+
await base.Contains_on_MemoryExtensions(a);
1019+
1020+
AssertSql(
1021+
"""
1022+
SELECT VALUE c
1023+
FROM root c
1024+
WHERE c["Int"] IN (10, 999)
1025+
""");
1026+
});
1027+
1028+
#if NET10_0_OR_GREATER
1029+
public override Task Contains_with_MemoryExtensions_with_null_comparer(bool async)
1030+
=> CosmosTestHelpers.Instance.NoSyncTest(
1031+
async, async a =>
1032+
{
1033+
await base.Contains_with_MemoryExtensions_with_null_comparer(a);
1034+
1035+
AssertSql(
1036+
"""
1037+
SELECT VALUE c
1038+
FROM root c
1039+
WHERE c["Int"] IN (10, 999)
1040+
""");
1041+
});
1042+
#endif
1043+
10001044
public override Task Column_collection_Length(bool async)
10011045
=> CosmosTestHelpers.Instance.NoSyncTest(
10021046
async, async a =>

test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.ModelSnapshot.cs

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4362,7 +4362,9 @@ public virtual void Owned_types_mapped_to_json_are_stored_in_snapshot()
43624362
43634363
b1.ToTable("EntityWithOneProperty", "DefaultSchema");
43644364
4365-
b1.ToJson("EntityWithTwoProperties");
4365+
b1
4366+
.ToJson("EntityWithTwoProperties")
4367+
.HasColumnType("nvarchar(max)");
43664368
43674369
b1.WithOwner("EntityWithOneProperty")
43684370
.HasForeignKey("EntityWithOnePropertyId");
@@ -4437,6 +4439,7 @@ public virtual void Owned_types_mapped_to_json_are_stored_in_snapshot()
44374439

44384440
Assert.Equal(nameof(EntityWithOneProperty), ownedType1.GetTableName());
44394441
Assert.Equal("EntityWithTwoProperties", ownedType1.GetContainerColumnName());
4442+
Assert.Equal("nvarchar(max)", ownedType1.GetContainerColumnType());
44404443

44414444
var ownership2 = ownedType1.FindNavigation(nameof(EntityWithStringKey)).ForeignKey;
44424445
Assert.Equal("EntityWithTwoPropertiesEntityWithOnePropertyId", ownership2.Properties[0].Name);
@@ -4473,6 +4476,83 @@ public virtual void Owned_types_mapped_to_json_are_stored_in_snapshot()
44734476
Assert.Equal("Name", ownedProperties3[3].Name);
44744477
});
44754478

4479+
[ConditionalFact]
4480+
public virtual void Owned_types_mapped_to_json_with_explicit_column_type_are_stored_in_snapshot()
4481+
=> Test(
4482+
builder =>
4483+
{
4484+
builder.Entity<EntityWithOneProperty>(b =>
4485+
{
4486+
b.HasKey(x => x.Id).HasName("PK_Custom");
4487+
4488+
b.OwnsOne(
4489+
x => x.EntityWithTwoProperties, bb =>
4490+
{
4491+
bb.ToJson().HasColumnType("json");
4492+
bb.Ignore(x => x.Id);
4493+
bb.Property(x => x.AlternateId).HasJsonPropertyName("NotKey");
4494+
bb.WithOwner(e => e.EntityWithOneProperty);
4495+
});
4496+
});
4497+
},
4498+
AddBoilerPlate(
4499+
GetHeading()
4500+
+ """
4501+
modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty", b =>
4502+
{
4503+
b.Property<int>("Id")
4504+
.ValueGeneratedOnAdd()
4505+
.HasColumnType("int");
4506+
4507+
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
4508+
4509+
b.HasKey("Id")
4510+
.HasName("PK_Custom");
4511+
4512+
b.ToTable("EntityWithOneProperty", "DefaultSchema");
4513+
});
4514+
4515+
modelBuilder.Entity("Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithOneProperty", b =>
4516+
{
4517+
b.OwnsOne("Microsoft.EntityFrameworkCore.Migrations.Design.CSharpMigrationsGeneratorTest+EntityWithTwoProperties", "EntityWithTwoProperties", b1 =>
4518+
{
4519+
b1.Property<int>("EntityWithOnePropertyId")
4520+
.HasColumnType("int");
4521+
4522+
b1.Property<int>("AlternateId")
4523+
.HasColumnType("int")
4524+
.HasAnnotation("Relational:JsonPropertyName", "NotKey");
4525+
4526+
b1.HasKey("EntityWithOnePropertyId");
4527+
4528+
b1.ToTable("EntityWithOneProperty", "DefaultSchema");
4529+
4530+
b1
4531+
.ToJson("EntityWithTwoProperties")
4532+
.HasColumnType("json");
4533+
4534+
b1.WithOwner("EntityWithOneProperty")
4535+
.HasForeignKey("EntityWithOnePropertyId");
4536+
4537+
b1.Navigation("EntityWithOneProperty");
4538+
});
4539+
4540+
b.Navigation("EntityWithTwoProperties");
4541+
});
4542+
""", usingSystem: false),
4543+
o =>
4544+
{
4545+
var entityWithOneProperty = o.FindEntityType(typeof(EntityWithOneProperty));
4546+
Assert.Equal("PK_Custom", entityWithOneProperty.GetKeys().Single().GetName());
4547+
4548+
var ownership1 = entityWithOneProperty.FindNavigation(nameof(EntityWithOneProperty.EntityWithTwoProperties))
4549+
.ForeignKey;
4550+
var ownedType1 = ownership1.DeclaringEntityType;
4551+
Assert.Equal(nameof(EntityWithOneProperty), ownedType1.GetTableName());
4552+
Assert.Equal("EntityWithTwoProperties", ownedType1.GetContainerColumnName());
4553+
Assert.Equal("json", ownedType1.GetContainerColumnType());
4554+
});
4555+
44764556
private class Order
44774557
{
44784558
public int Id { get; set; }

test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,34 @@ public virtual Task Column_collection_of_bools_Contains(bool async)
607607
async,
608608
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => c.Bools.Contains(true)));
609609

610+
// C# 14 first-class spans caused MemoryExtensions.Contains to get resolved instead of Enumerable.Contains.
611+
// The following tests that the various overloads are all supported.
612+
[ConditionalTheory]
613+
[MemberData(nameof(IsAsyncData))]
614+
public virtual Task Contains_on_Enumerable(bool async)
615+
=> AssertQuery(
616+
async,
617+
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => Enumerable.Contains(new[] { 10, 999 }, c.Int)));
618+
619+
// C# 14 first-class spans caused MemoryExtensions.Contains to get resolved instead of Enumerable.Contains.
620+
// The following tests that the various overloads are all supported.
621+
[ConditionalTheory]
622+
[MemberData(nameof(IsAsyncData))]
623+
public virtual Task Contains_on_MemoryExtensions(bool async)
624+
=> AssertQuery(
625+
async,
626+
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => MemoryExtensions.Contains(new[] { 10, 999 }, c.Int)));
627+
628+
// Note that we don't test EF 8/9 with .NET 10; this test is here for completeness/documentation purposes.
629+
#if NET10_0_OR_GREATER
630+
[ConditionalTheory]
631+
[MemberData(nameof(IsAsyncData))]
632+
public virtual Task Contains_with_MemoryExtensions_with_null_comparer(bool async)
633+
=> AssertQuery(
634+
async,
635+
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => MemoryExtensions.Contains(new[] { 10, 999 }, c.Int, comparer: null)));
636+
#endif
637+
610638
[ConditionalTheory]
611639
[MemberData(nameof(IsAsyncData))]
612640
public virtual Task Column_collection_Count_method(bool async)

0 commit comments

Comments
 (0)