Skip to content

Commit f8a5d54

Browse files
authored
Fix to #30073 - JSON query regression in EF8 (#30086)
Problem was that when we were translating join, combining two SelectExpressions together, we were not remapping JSON column indexes stored in the inner select. This later result in shaper trying to read JSON column data from the wrong index and throwing the exception. We already had a test that would have caught that, but it didn't return any data so the mismatch was never detected - fixed the data issue and added few more tests for good measure. Fixes #30073
1 parent d483adc commit f8a5d54

File tree

6 files changed

+116
-16
lines changed

6 files changed

+116
-16
lines changed

src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1539,7 +1539,7 @@ Expression CopyProjectionToOuter(SelectExpression innerSelectExpression, Express
15391539

15401540
remappedConstant = Constant(
15411541
new JsonProjectionInfo(
1542-
jsonProjectionInfo.JsonColumnIndex,
1542+
projectionIndexMap[jsonProjectionInfo.JsonColumnIndex],
15431543
newKeyAccessInfo,
15441544
jsonProjectionInfo.AdditionalPath));
15451545
}

test/EFCore.Relational.Specification.Tests/Query/JsonQueryFixtureBase.cs

+1
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ protected override void Seed(JsonQueryContext context)
377377
protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
378378
{
379379
modelBuilder.Entity<JsonEntityBasic>().Property(x => x.Id).ValueGeneratedNever();
380+
modelBuilder.Entity<EntityBasic>().Property(x => x.Id).ValueGeneratedNever();
380381
modelBuilder.Entity<JsonEntityBasicForReference>().Property(x => x.Id).ValueGeneratedNever();
381382
modelBuilder.Entity<JsonEntityBasicForCollection>().Property(x => x.Id).ValueGeneratedNever();
382383
modelBuilder.Entity<JsonEntityBasic>().OwnsOne(

test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs

+54-1
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,59 @@ from e2 in g.DefaultIfEmpty()
435435
},
436436
entryCount: 40);
437437

438+
[ConditionalTheory]
439+
[MemberData(nameof(IsAsyncData))]
440+
public virtual Task Left_join_json_entities_json_being_inner(bool async)
441+
=> AssertQuery(
442+
async,
443+
ss => from e1 in ss.Set<JsonEntityBasic>()
444+
join e2 in ss.Set<JsonEntitySingleOwned>() on e1.Id equals e2.Id into g
445+
from e2 in g.DefaultIfEmpty()
446+
select new { e1, e2 },
447+
elementSorter: e => (e.e1.Id, e.e2?.Id),
448+
elementAsserter: (e, a) =>
449+
{
450+
AssertEqual(e.e1, a.e1);
451+
AssertEqual(e.e2, a.e2);
452+
},
453+
entryCount: 44);
454+
455+
[ConditionalTheory]
456+
[MemberData(nameof(IsAsyncData))]
457+
public virtual Task Left_join_json_entities_complex_projection_json_being_inner(bool async)
458+
=> AssertQuery(
459+
async,
460+
461+
ss => (from e1 in ss.Set<JsonEntityBasic>()
462+
join e2 in ss.Set<JsonEntitySingleOwned>() on e1.Id equals e2.Id into g
463+
from e2 in g.DefaultIfEmpty()
464+
select new
465+
{
466+
Id1 = e1.Id,
467+
Id2 = (int?)e2.Id,
468+
e1,
469+
e1.OwnedReferenceRoot,
470+
e1.OwnedReferenceRoot.OwnedReferenceBranch,
471+
e1.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf,
472+
e1.OwnedReferenceRoot.OwnedReferenceBranch.OwnedCollectionLeaf,
473+
e2,
474+
e2.Name
475+
}),
476+
elementSorter: e => (e.Id1, e?.Id2),
477+
elementAsserter: (e, a) =>
478+
{
479+
Assert.Equal(e.Id1, a.Id1);
480+
Assert.Equal(e.Id2, a.Id2);
481+
AssertEqual(e.e1, a.e1);
482+
AssertEqual(e.OwnedReferenceRoot, a.OwnedReferenceRoot);
483+
AssertEqual(e.OwnedReferenceBranch, a.OwnedReferenceBranch);
484+
AssertEqual(e.OwnedReferenceLeaf, a.OwnedReferenceLeaf);
485+
AssertCollection(e.OwnedCollectionLeaf, a.OwnedCollectionLeaf, ordered: true);
486+
AssertEqual(e.e2, a.e2);
487+
AssertEqual(e.Name, a.Name);
488+
},
489+
entryCount: 44);
490+
438491
[ConditionalTheory]
439492
[MemberData(nameof(IsAsyncData))]
440493
public virtual Task Project_json_entity_FirstOrDefault_subquery(bool async)
@@ -1346,7 +1399,7 @@ public virtual Task Entity_including_collection_with_json(bool async)
13461399
elementAsserter: (e, a) => AssertInclude(
13471400
e, a,
13481401
new ExpectedInclude<EntityBasic>(x => x.JsonEntityBasics)),
1349-
entryCount: 0);
1402+
entryCount: 41);
13501403

13511404
[ConditionalTheory]
13521405
[MemberData(nameof(IsAsyncData))]

test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryContext.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,18 @@ public JsonQueryContext(DbContextOptions options)
2222
public static void Seed(JsonQueryContext context)
2323
{
2424
var jsonEntitiesBasic = JsonQueryData.CreateJsonEntitiesBasic();
25+
var entitiesBasic = JsonQueryData.CreateEntitiesBasic();
2526
var jsonEntitiesBasicForReference = JsonQueryData.CreateJsonEntitiesBasicForReference();
2627
var jsonEntitiesBasicForCollection = JsonQueryData.CreateJsonEntitiesBasicForCollection();
27-
JsonQueryData.WireUp(jsonEntitiesBasic, jsonEntitiesBasicForReference, jsonEntitiesBasicForCollection);
28+
JsonQueryData.WireUp(jsonEntitiesBasic, entitiesBasic, jsonEntitiesBasicForReference, jsonEntitiesBasicForCollection);
2829

2930
var jsonEntitiesCustomNaming = JsonQueryData.CreateJsonEntitiesCustomNaming();
3031
var jsonEntitiesSingleOwned = JsonQueryData.CreateJsonEntitiesSingleOwned();
3132
var jsonEntitiesInheritance = JsonQueryData.CreateJsonEntitiesInheritance();
3233
var jsonEntitiesAllTypes = JsonQueryData.CreateJsonEntitiesAllTypes();
3334

3435
context.JsonEntitiesBasic.AddRange(jsonEntitiesBasic);
36+
context.EntitiesBasic.AddRange(entitiesBasic);
3537
context.JsonEntitiesBasicForReference.AddRange(jsonEntitiesBasicForReference);
3638
context.JsonEntitiesBasicForCollection.AddRange(jsonEntitiesBasicForCollection);
3739
context.JsonEntitiesCustomNaming.AddRange(jsonEntitiesCustomNaming);

test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryData.cs

+33-13
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ public class JsonQueryData : ISetSource
77
{
88
public JsonQueryData()
99
{
10-
EntitiesBasic = new List<EntityBasic>();
1110
JsonEntitiesBasic = CreateJsonEntitiesBasic();
11+
EntitiesBasic = CreateEntitiesBasic();
1212
JsonEntitiesBasicForReference = CreateJsonEntitiesBasicForReference();
1313
JsonEntitiesBasicForCollection = CreateJsonEntitiesBasicForCollection();
14-
WireUp(JsonEntitiesBasic, JsonEntitiesBasicForReference, JsonEntitiesBasicForCollection);
14+
WireUp(JsonEntitiesBasic, EntitiesBasic, JsonEntitiesBasicForReference, JsonEntitiesBasicForCollection);
1515

1616
JsonEntitiesCustomNaming = CreateJsonEntitiesCustomNaming();
1717
JsonEntitiesSingleOwned = CreateJsonEntitiesSingleOwned();
@@ -292,6 +292,13 @@ public static IReadOnlyList<JsonEntityBasic> CreateJsonEntitiesBasic()
292292
return new List<JsonEntityBasic> { entity1 };
293293
}
294294

295+
public static IReadOnlyList<EntityBasic> CreateEntitiesBasic()
296+
{
297+
var entity1 = new EntityBasic { Id = 1, Name = "eb 1" };
298+
299+
return new List<EntityBasic> { entity1 };
300+
}
301+
295302
public static IReadOnlyList<JsonEntityBasicForReference> CreateJsonEntitiesBasicForReference()
296303
{
297304
var entity1 = new JsonEntityBasicForReference { Id = 1, Name = "EntityReference1" };
@@ -314,27 +321,30 @@ public static IReadOnlyList<JsonEntityBasicForCollection> CreateJsonEntitiesBasi
314321
}
315322

316323
public static void WireUp(
317-
IReadOnlyList<JsonEntityBasic> entitiesBasic,
324+
IReadOnlyList<JsonEntityBasic> jsonEntitiesBasic,
325+
IReadOnlyList<EntityBasic> entitiesBasic,
318326
IReadOnlyList<JsonEntityBasicForReference> entitiesBasicForReference,
319327
IReadOnlyList<JsonEntityBasicForCollection> entitiesBasicForCollection)
320328
{
321-
entitiesBasic[0].EntityReference = entitiesBasicForReference[0];
322-
entitiesBasicForReference[0].Parent = entitiesBasic[0];
323-
entitiesBasicForReference[0].ParentId = entitiesBasic[0].Id;
329+
entitiesBasic[0].JsonEntityBasics = new List<JsonEntityBasic> { jsonEntitiesBasic[0] };
330+
331+
jsonEntitiesBasic[0].EntityReference = entitiesBasicForReference[0];
332+
entitiesBasicForReference[0].Parent = jsonEntitiesBasic[0];
333+
entitiesBasicForReference[0].ParentId = jsonEntitiesBasic[0].Id;
324334

325-
entitiesBasic[0].EntityCollection = new List<JsonEntityBasicForCollection>
335+
jsonEntitiesBasic[0].EntityCollection = new List<JsonEntityBasicForCollection>
326336
{
327337
entitiesBasicForCollection[0],
328338
entitiesBasicForCollection[1],
329339
entitiesBasicForCollection[2]
330340
};
331341

332-
entitiesBasicForCollection[0].Parent = entitiesBasic[0];
333-
entitiesBasicForCollection[0].ParentId = entitiesBasic[0].Id;
334-
entitiesBasicForCollection[1].Parent = entitiesBasic[0];
335-
entitiesBasicForCollection[1].ParentId = entitiesBasic[0].Id;
336-
entitiesBasicForCollection[2].Parent = entitiesBasic[0];
337-
entitiesBasicForCollection[2].ParentId = entitiesBasic[0].Id;
342+
entitiesBasicForCollection[0].Parent = jsonEntitiesBasic[0];
343+
entitiesBasicForCollection[0].ParentId = jsonEntitiesBasic[0].Id;
344+
entitiesBasicForCollection[1].Parent = jsonEntitiesBasic[0];
345+
entitiesBasicForCollection[1].ParentId = jsonEntitiesBasic[0].Id;
346+
entitiesBasicForCollection[2].Parent = jsonEntitiesBasic[0];
347+
entitiesBasicForCollection[2].ParentId = jsonEntitiesBasic[0].Id;
338348
}
339349

340350
public static IReadOnlyList<JsonEntityCustomNaming> CreateJsonEntitiesCustomNaming()
@@ -801,6 +811,16 @@ public IQueryable<TEntity> Set<TEntity>()
801811
return (IQueryable<TEntity>)JsonEntitiesAllTypes.OfType<JsonEntityAllTypes>().AsQueryable();
802812
}
803813

814+
if (typeof(TEntity) == typeof(JsonEntityBasicForReference))
815+
{
816+
return (IQueryable<TEntity>)JsonEntitiesBasicForReference.AsQueryable();
817+
}
818+
819+
if (typeof(TEntity) == typeof(JsonEntityBasicForCollection))
820+
{
821+
return (IQueryable<TEntity>)JsonEntitiesBasicForCollection.AsQueryable();
822+
}
823+
804824
throw new InvalidOperationException("Invalid entity type: " + typeof(TEntity));
805825
}
806826
}

test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs

+24
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,30 @@ FROM [JsonEntitiesSingleOwned] AS [j]
435435
""");
436436
}
437437

438+
public override async Task Left_join_json_entities_json_being_inner(bool async)
439+
{
440+
await base.Left_join_json_entities_json_being_inner(async);
441+
442+
AssertSql(
443+
"""
444+
SELECT [j].[Id], [j].[EntityBasicId], [j].[Name], [j].[OwnedCollectionRoot], [j].[OwnedReferenceRoot], [j0].[Id], [j0].[Name], [j0].[OwnedCollection]
445+
FROM [JsonEntitiesBasic] AS [j]
446+
LEFT JOIN [JsonEntitiesSingleOwned] AS [j0] ON [j].[Id] = [j0].[Id]
447+
""");
448+
}
449+
450+
public override async Task Left_join_json_entities_complex_projection_json_being_inner(bool async)
451+
{
452+
await base.Left_join_json_entities_complex_projection_json_being_inner(async);
453+
454+
AssertSql(
455+
"""
456+
SELECT [j].[Id], [j0].[Id], [j].[EntityBasicId], [j].[Name], [j].[OwnedCollectionRoot], [j].[OwnedReferenceRoot], [j0].[Name], [j0].[OwnedCollection]
457+
FROM [JsonEntitiesBasic] AS [j]
458+
LEFT JOIN [JsonEntitiesSingleOwned] AS [j0] ON [j].[Id] = [j0].[Id]
459+
""");
460+
}
461+
438462
public override async Task Project_json_entity_FirstOrDefault_subquery(bool async)
439463
{
440464
await base.Project_json_entity_FirstOrDefault_subquery(async);

0 commit comments

Comments
 (0)