Skip to content

Commit e35557e

Browse files
rojiCopilot
andcommitted
Fix struct complex type boxing in collection materialization
When projecting a struct complex type alongside a collection navigation, CompensateForCollectionMaterialization added the value-type expression to _valuesArrayInitializers without boxing, causing NewArrayInit(typeof(object), ...) to throw InvalidOperationException. Box value types before adding to the object[] array, matching the existing pattern in the scalar projection path. Fixes dotnet#37926 Co-authored-by: Copilot <[email protected]>
1 parent 8bfecfd commit e35557e

4 files changed

Lines changed: 89 additions & 2 deletions

File tree

src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1479,7 +1479,13 @@ Expression CompensateForCollectionMaterialization(ParameterExpression parameter,
14791479
{
14801480
if (_containsCollectionMaterialization)
14811481
{
1482-
_valuesArrayInitializers!.Add(parameter);
1482+
Expression expressionToAdd = parameter;
1483+
if (expressionToAdd.Type.IsValueType)
1484+
{
1485+
expressionToAdd = Convert(expressionToAdd, typeof(object));
1486+
}
1487+
1488+
_valuesArrayInitializers!.Add(expressionToAdd);
14831489
return Convert(
14841490
ArrayIndex(
14851491
_valuesArrayExpression!,

test/EFCore.Cosmos.FunctionalTests/Query/Associations/ComplexProperties/ComplexPropertiesCollectionCosmosTest.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,10 @@ FROM root c
221221
}
222222

223223

224+
// Cosmos doesn't support entity collection navigations across documents.
225+
public override Task Project_struct_complex_type_with_entity_collection_navigation()
226+
=> Task.CompletedTask;
227+
224228
[ConditionalFact]
225229
public virtual void Check_all_tests_overridden()
226230
=> TestHelpers.AssertAllMethodsOverridden(GetType());

test/EFCore.Specification.Tests/Query/Associations/ComplexProperties/ComplexPropertiesCollectionTestBase.cs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,64 @@ namespace Microsoft.EntityFrameworkCore.Query.Associations.ComplexProperties;
55

66
public abstract class ComplexPropertiesCollectionTestBase<TFixture>(TFixture fixture)
77
: AssociationsCollectionTestBase<TFixture>(fixture)
8-
where TFixture : ComplexPropertiesFixtureBase, new();
8+
where TFixture : ComplexPropertiesFixtureBase, new()
9+
{
10+
#region 37926
11+
12+
[ConditionalFact]
13+
public virtual async Task Project_struct_complex_type_with_entity_collection_navigation()
14+
{
15+
var contextFactory = await InitializeNonSharedTest<Context37926>(
16+
seed: async context =>
17+
{
18+
context.Add(new Context37926.Parent
19+
{
20+
Coords = new Context37926.Coords { X = 1, Y = 2 },
21+
Children = [new() { Name = "Child1" }]
22+
});
23+
await context.SaveChangesAsync();
24+
});
25+
26+
await using var context = contextFactory.CreateDbContext();
27+
28+
var result = await context.Set<Context37926.Parent>()
29+
.OrderBy(p => p.Id)
30+
.Select(p => new { p.Coords, p.Children })
31+
.FirstAsync();
32+
33+
Assert.Equal(1, result.Coords.X);
34+
Assert.Single(result.Children);
35+
}
36+
37+
protected class Context37926(DbContextOptions options) : DbContext(options)
38+
{
39+
protected override void OnModelCreating(ModelBuilder modelBuilder)
40+
=> modelBuilder.Entity<Parent>(b =>
41+
{
42+
b.ComplexProperty(e => e.Coords);
43+
b.HasMany(e => e.Children).WithOne().HasForeignKey(c => c.ParentId);
44+
});
45+
46+
public class Parent
47+
{
48+
public int Id { get; set; }
49+
public Coords Coords { get; set; }
50+
public List<Child> Children { get; set; } = [];
51+
}
52+
53+
public struct Coords
54+
{
55+
public int X { get; set; }
56+
public int Y { get; set; }
57+
}
58+
59+
public class Child
60+
{
61+
public int Id { get; set; }
62+
public required string Name { get; set; }
63+
public int ParentId { get; set; }
64+
}
65+
}
66+
67+
#endregion 37926
68+
}

test/EFCore.SqlServer.FunctionalTests/Query/Associations/ComplexJson/ComplexJsonCollectionSqlServerTest.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,23 @@ FROM [RootEntity] AS [r]
352352
}
353353
}
354354

355+
public override async Task Project_struct_complex_type_with_entity_collection_navigation()
356+
{
357+
await base.Project_struct_complex_type_with_entity_collection_navigation();
358+
359+
AssertSql(
360+
"""
361+
SELECT [p0].[Coords_X], [p0].[Coords_Y], [p0].[Id], [c].[Id], [c].[Name], [c].[ParentId]
362+
FROM (
363+
SELECT TOP(1) [p].[Coords_X], [p].[Coords_Y], [p].[Id]
364+
FROM [Parent] AS [p]
365+
ORDER BY [p].[Id]
366+
) AS [p0]
367+
LEFT JOIN [Child] AS [c] ON [p0].[Id] = [c].[ParentId]
368+
ORDER BY [p0].[Id]
369+
""");
370+
}
371+
355372
[ConditionalFact]
356373
public virtual void Check_all_tests_overridden()
357374
=> TestHelpers.AssertAllMethodsOverridden(GetType());

0 commit comments

Comments
 (0)