From 51e8fe7486048ed999a8d7fed25143bcb4963a08 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 23:02:34 +0000 Subject: [PATCH 1/2] Initial plan From e7268690d6bcee44dfca3601fba20aa687e5f862 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 23:18:10 +0000 Subject: [PATCH 2/2] Fix NRE in JSON collection shaper with lazy-loading proxies + nested primitive collection Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com> --- ...sitor.ShaperProcessingExpressionVisitor.cs | 8 +++- .../Query/AdHocJsonQueryTestBase.cs | 41 ++++++++++++++++--- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index d2a4610657a..93c7e7df7dd 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -2462,13 +2462,19 @@ protected override Expression VisitBinary(BinaryExpression node) var genericMethod = StructuralTypeMaterializerSource.PopulateListMethod.MakeGenericMethod( property.ClrType.TryGetElementType(typeof(IEnumerable<>))!); #pragma warning restore EF1001 // Internal EF Core API usage. + // The instance the property is being assigned to must be taken from the original assignment, not the + // hard-coded 'instance'. For lazy-loading proxies, 'instance' is the converted (non-proxy) variable that + // is only assigned at the end of the materializer, so reading the member from it here would dereference null. + var instanceExpression = node.Left is MemberExpression { Expression: { } leftInstance } + ? leftInstance + : instance; var currentVariable = Variable(parameter!.Type); var convertedVariable = genericMethod.GetParameters()[1].ParameterType.IsAssignableFrom(currentVariable.Type) ? (Expression)currentVariable : Convert(currentVariable, genericMethod.GetParameters()[1].ParameterType); return Block( [currentVariable], - MakeMemberAccess(instance, property.GetMemberInfo(forMaterialization: true, forSet: false)) + MakeMemberAccess(instanceExpression, property.GetMemberInfo(forMaterialization: true, forSet: false)) .Assign(currentVariable), IfThenElse( OrElse( diff --git a/test/EFCore.Specification.Tests/Query/AdHocJsonQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocJsonQueryTestBase.cs index 1ea059aa26e..b85370798c7 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocJsonQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocJsonQueryTestBase.cs @@ -1495,6 +1495,36 @@ public virtual async Task Project_proxies_entity_with_json() Assert.Equal(2, result.Count); } + [Fact] // Issue #38466 + public virtual async Task Project_proxies_entity_with_json_with_primitive_collection() + { + var contextFactory = await InitializeNonSharedTest( + onModelCreating: OnModelCreatingLazyLoadingProxies, + seed: SeedLazyLoadingProxies, + onConfiguring: b => + { + b = b.ConfigureWarnings(ConfigureWarnings); + OnConfiguringLazyLoadingProxies(b); + }, + addServices: AddServicesLazyLoadingProxies); + + using var context = contextFactory.CreateDbContext(); + var result = await context.Set().OrderBy(x => x.Id).ToListAsync(); + + Assert.Equal(2, result.Count); + + var e1Collection = result[0].Collection.OrderBy(x => x.Number).ToList(); + Assert.Equal(3, e1Collection.Count); + Assert.Equal(new long[] { 110, 111 }, e1Collection[0].Ints); + Assert.Equal(new long[] { 120, 121, 122 }, e1Collection[1].Ints); + Assert.Empty(e1Collection[2].Ints); + + var e2Collection = result[1].Collection.OrderBy(x => x.Number).ToList(); + Assert.Equal(2, e2Collection.Count); + Assert.Equal(new long[] { 210 }, e2Collection[0].Ints); + Assert.Equal(new long[] { 220, 221 }, e2Collection[1].Ints); + } + protected void OnConfiguringLazyLoadingProxies(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseLazyLoadingProxies(); @@ -1504,13 +1534,13 @@ protected IServiceCollection AddServicesLazyLoadingProxies(IServiceCollection ad private Task SeedLazyLoadingProxies(DbContext ctx) { var r1 = new ContextLazyLoadingProxies.MyJsonEntityWithCtor("r1", 1); - var c11 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c11", Number = 11 }; - var c12 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c12", Number = 12 }; - var c13 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c13", Number = 13 }; + var c11 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c11", Number = 11, Ints = [110, 111] }; + var c12 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c12", Number = 12, Ints = [120, 121, 122] }; + var c13 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c13", Number = 13, Ints = [] }; var r2 = new ContextLazyLoadingProxies.MyJsonEntityWithCtor("r2", 2); - var c21 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c21", Number = 21 }; - var c22 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c22", Number = 22 }; + var c21 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c21", Number = 21, Ints = [210] }; + var c22 = new ContextLazyLoadingProxies.MyJsonEntity { Name = "c22", Number = 22, Ints = [220, 221] }; var e1 = new ContextLazyLoadingProxies.MyEntity { @@ -1565,6 +1595,7 @@ public class MyJsonEntity { public string Name { get; set; } public int Number { get; set; } + public IList Ints { get; set; } } }