Skip to content

Commit 4af7bb6

Browse files
committed
Merge in 'release/7.0' changes
2 parents bb2cde3 + be9e79d commit 4af7bb6

File tree

2 files changed

+234
-46
lines changed

2 files changed

+234
-46
lines changed

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

Lines changed: 54 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -440,52 +440,7 @@ void GenerateNonHierarchyNonSplittingEntityType(ITableBase table, TableExpressio
440440
}
441441

442442
var entityProjection = new EntityProjectionExpression(entityType, propertyExpressions);
443-
444-
foreach (var ownedJsonNavigation in GetAllNavigationsInHierarchy(entityType)
445-
.Where(
446-
n => n.ForeignKey.IsOwnership
447-
&& n.TargetEntityType.IsMappedToJson()
448-
&& n.ForeignKey.PrincipalToDependent == n))
449-
{
450-
var targetEntityType = ownedJsonNavigation.TargetEntityType;
451-
var jsonColumnName = targetEntityType.GetContainerColumnName()!;
452-
var jsonColumnTypeMapping = targetEntityType.GetContainerColumnTypeMapping()!;
453-
454-
var jsonColumn = new ConcreteColumnExpression(
455-
jsonColumnName,
456-
tableReferenceExpression,
457-
jsonColumnTypeMapping.ClrType,
458-
jsonColumnTypeMapping,
459-
nullable: !ownedJsonNavigation.ForeignKey.IsRequiredDependent || ownedJsonNavigation.IsCollection);
460-
461-
// for json collections we need to skip ordinal key (which is always the last one)
462-
// simple copy from parent is safe here, because we only do it at top level
463-
// so there is no danger of multiple keys being synthesized (like we have in multi-level nav chains)
464-
var keyPropertiesMap = new Dictionary<IProperty, ColumnExpression>();
465-
var keyProperties = targetEntityType.FindPrimaryKey()!.Properties;
466-
var keyPropertiesCount = ownedJsonNavigation.IsCollection
467-
? keyProperties.Count - 1
468-
: keyProperties.Count;
469-
470-
for (var i = 0; i < keyPropertiesCount; i++)
471-
{
472-
var correspondingParentKeyProperty = ownedJsonNavigation.ForeignKey.PrincipalKey.Properties[i];
473-
keyPropertiesMap[keyProperties[i]] = propertyExpressions[correspondingParentKeyProperty];
474-
}
475-
476-
var entityShaperExpression = new RelationalEntityShaperExpression(
477-
targetEntityType,
478-
new JsonQueryExpression(
479-
targetEntityType,
480-
jsonColumn,
481-
keyPropertiesMap,
482-
ownedJsonNavigation.ClrType,
483-
ownedJsonNavigation.IsCollection),
484-
!ownedJsonNavigation.ForeignKey.IsRequiredDependent);
485-
486-
entityProjection.AddNavigationBinding(ownedJsonNavigation, entityShaperExpression);
487-
}
488-
443+
AddJsonNavigationBindings(entityType, entityProjection, propertyExpressions, tableReferenceExpression);
489444
_projectionMapping[new ProjectionMember()] = entityProjection;
490445

491446
var primaryKey = entityType.FindPrimaryKey();
@@ -522,6 +477,7 @@ internal SelectExpression(IEntityType entityType, TableExpressionBase tableExpre
522477
}
523478

524479
var entityProjection = new EntityProjectionExpression(entityType, propertyExpressions);
480+
AddJsonNavigationBindings(entityType, entityProjection, propertyExpressions, tableReferenceExpression);
525481
_projectionMapping[new ProjectionMember()] = entityProjection;
526482

527483
var primaryKey = entityType.FindPrimaryKey();
@@ -534,6 +490,58 @@ internal SelectExpression(IEntityType entityType, TableExpressionBase tableExpre
534490
}
535491
}
536492

493+
private void AddJsonNavigationBindings(
494+
IEntityType entityType,
495+
EntityProjectionExpression entityProjection,
496+
Dictionary<IProperty, ColumnExpression> propertyExpressions,
497+
TableReferenceExpression tableReferenceExpression)
498+
{
499+
foreach (var ownedJsonNavigation in GetAllNavigationsInHierarchy(entityType)
500+
.Where(
501+
n => n.ForeignKey.IsOwnership
502+
&& n.TargetEntityType.IsMappedToJson()
503+
&& n.ForeignKey.PrincipalToDependent == n))
504+
{
505+
var targetEntityType = ownedJsonNavigation.TargetEntityType;
506+
var jsonColumnName = targetEntityType.GetContainerColumnName()!;
507+
var jsonColumnTypeMapping = targetEntityType.GetContainerColumnTypeMapping()!;
508+
509+
var jsonColumn = new ConcreteColumnExpression(
510+
jsonColumnName,
511+
tableReferenceExpression,
512+
jsonColumnTypeMapping.ClrType,
513+
jsonColumnTypeMapping,
514+
nullable: !ownedJsonNavigation.ForeignKey.IsRequiredDependent || ownedJsonNavigation.IsCollection);
515+
516+
// for json collections we need to skip ordinal key (which is always the last one)
517+
// simple copy from parent is safe here, because we only do it at top level
518+
// so there is no danger of multiple keys being synthesized (like we have in multi-level nav chains)
519+
var keyPropertiesMap = new Dictionary<IProperty, ColumnExpression>();
520+
var keyProperties = targetEntityType.FindPrimaryKey()!.Properties;
521+
var keyPropertiesCount = ownedJsonNavigation.IsCollection
522+
? keyProperties.Count - 1
523+
: keyProperties.Count;
524+
525+
for (var i = 0; i < keyPropertiesCount; i++)
526+
{
527+
var correspondingParentKeyProperty = ownedJsonNavigation.ForeignKey.PrincipalKey.Properties[i];
528+
keyPropertiesMap[keyProperties[i]] = propertyExpressions[correspondingParentKeyProperty];
529+
}
530+
531+
var entityShaperExpression = new RelationalEntityShaperExpression(
532+
targetEntityType,
533+
new JsonQueryExpression(
534+
targetEntityType,
535+
jsonColumn,
536+
keyPropertiesMap,
537+
ownedJsonNavigation.ClrType,
538+
ownedJsonNavigation.IsCollection),
539+
!ownedJsonNavigation.ForeignKey.IsRequiredDependent);
540+
541+
entityProjection.AddNavigationBinding(ownedJsonNavigation, entityShaperExpression);
542+
}
543+
}
544+
537545
/// <summary>
538546
/// The list of tags applied to this <see cref="SelectExpression" />.
539547
/// </summary>

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

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.Data.SqlClient;
5+
using Microsoft.EntityFrameworkCore.TestModels.JsonQuery;
6+
47
namespace Microsoft.EntityFrameworkCore.Query;
58

69
public class JsonQuerySqlServerTest : JsonQueryTestBase<JsonQuerySqlServerFixture>
@@ -838,6 +841,183 @@ FROM [JsonEntitiesAllTypes] AS [j]
838841
""");
839842
}
840843

844+
[ConditionalTheory]
845+
[MemberData(nameof(IsAsyncData))]
846+
public virtual async Task FromSql_on_entity_with_json_basic(bool async)
847+
{
848+
await AssertQuery(
849+
async,
850+
ss => ((DbSet<JsonEntityBasic>)ss.Set<JsonEntityBasic>()).FromSqlRaw(
851+
Fixture.TestStore.NormalizeDelimitersInRawString("SELECT * FROM [JsonEntitiesBasic] AS j")),
852+
ss => ss.Set<JsonEntityBasic>(),
853+
entryCount: 40);
854+
855+
AssertSql(
856+
"""
857+
SELECT [m].[Id], [m].[EntityBasicId], [m].[Name], JSON_QUERY([m].[OwnedCollectionRoot],'$'), JSON_QUERY([m].[OwnedReferenceRoot],'$')
858+
FROM (
859+
SELECT * FROM "JsonEntitiesBasic" AS j
860+
) AS [m]
861+
""");
862+
}
863+
864+
[ConditionalTheory]
865+
[MemberData(nameof(IsAsyncData))]
866+
public virtual async Task FromSqlInterpolated_on_entity_with_json_with_predicate(bool async)
867+
{
868+
var parameter = new SqlParameter { ParameterName = "prm", Value = 1 };
869+
await AssertQuery(
870+
async,
871+
ss => ((DbSet<JsonEntityBasic>)ss.Set<JsonEntityBasic>()).FromSql(
872+
Fixture.TestStore.NormalizeDelimitersInInterpolatedString($"SELECT * FROM [JsonEntitiesBasic] AS j WHERE [j].[Id] = {parameter}")),
873+
ss => ss.Set<JsonEntityBasic>(),
874+
entryCount: 40);
875+
876+
AssertSql(
877+
"""
878+
prm='1'
879+
880+
SELECT [m].[Id], [m].[EntityBasicId], [m].[Name], JSON_QUERY([m].[OwnedCollectionRoot],'$'), JSON_QUERY([m].[OwnedReferenceRoot],'$')
881+
FROM (
882+
SELECT * FROM "JsonEntitiesBasic" AS j WHERE "j"."Id" = @prm
883+
) AS [m]
884+
""");
885+
}
886+
887+
[ConditionalTheory]
888+
[MemberData(nameof(IsAsyncData))]
889+
public virtual async Task FromSql_on_entity_with_json_project_json_reference(bool async)
890+
{
891+
await AssertQuery(
892+
async,
893+
ss => ((DbSet<JsonEntityBasic>)ss.Set<JsonEntityBasic>()).FromSqlRaw(
894+
Fixture.TestStore.NormalizeDelimitersInRawString("SELECT * FROM [JsonEntitiesBasic] AS j"))
895+
.AsNoTracking()
896+
.Select(x => x.OwnedReferenceRoot.OwnedReferenceBranch),
897+
ss => ss.Set<JsonEntityBasic>().Select(x => x.OwnedReferenceRoot.OwnedReferenceBranch));
898+
899+
AssertSql(
900+
"""
901+
SELECT JSON_QUERY([m].[OwnedReferenceRoot],'$.OwnedReferenceBranch'), [m].[Id]
902+
FROM (
903+
SELECT * FROM "JsonEntitiesBasic" AS j
904+
) AS [m]
905+
""");
906+
}
907+
908+
[ConditionalTheory]
909+
[MemberData(nameof(IsAsyncData))]
910+
public virtual async Task FromSql_on_entity_with_json_project_json_collection(bool async)
911+
{
912+
await AssertQuery(
913+
async,
914+
ss => ((DbSet<JsonEntityBasic>)ss.Set<JsonEntityBasic>()).FromSqlRaw(
915+
Fixture.TestStore.NormalizeDelimitersInRawString("SELECT * FROM [JsonEntitiesBasic] AS j"))
916+
.AsNoTracking()
917+
.Select(x => x.OwnedReferenceRoot.OwnedCollectionBranch),
918+
ss => ss.Set<JsonEntityBasic>().Select(x => x.OwnedReferenceRoot.OwnedCollectionBranch),
919+
elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: ee => (ee.Date, ee.Enum, ee.Fraction)));
920+
921+
AssertSql(
922+
"""
923+
SELECT JSON_QUERY([m].[OwnedReferenceRoot],'$.OwnedCollectionBranch'), [m].[Id]
924+
FROM (
925+
SELECT * FROM "JsonEntitiesBasic" AS j
926+
) AS [m]
927+
""");
928+
}
929+
930+
[ConditionalTheory]
931+
[MemberData(nameof(IsAsyncData))]
932+
public virtual async Task FromSql_on_entity_with_json_inheritance_on_base(bool async)
933+
{
934+
await AssertQuery(
935+
async,
936+
ss => ((DbSet<JsonEntityInheritanceBase>)ss.Set<JsonEntityInheritanceBase>()).FromSqlRaw(
937+
Fixture.TestStore.NormalizeDelimitersInRawString("SELECT * FROM [JsonEntitiesInheritance] AS j")),
938+
ss => ss.Set<JsonEntityInheritanceBase>(),
939+
entryCount: 38);
940+
941+
AssertSql(
942+
"""
943+
SELECT [m].[Id], [m].[Discriminator], [m].[Name], [m].[Fraction], JSON_QUERY([m].[CollectionOnBase],'$'), JSON_QUERY([m].[ReferenceOnBase],'$'), JSON_QUERY([m].[CollectionOnDerived],'$'), JSON_QUERY([m].[ReferenceOnDerived],'$')
944+
FROM (
945+
SELECT * FROM "JsonEntitiesInheritance" AS j
946+
) AS [m]
947+
""");
948+
}
949+
950+
[ConditionalTheory]
951+
[MemberData(nameof(IsAsyncData))]
952+
public virtual async Task FromSql_on_entity_with_json_inheritance_on_derived(bool async)
953+
{
954+
await AssertQuery(
955+
async,
956+
ss => ((DbSet<JsonEntityInheritanceDerived>)ss.Set<JsonEntityInheritanceDerived>()).FromSqlRaw(
957+
Fixture.TestStore.NormalizeDelimitersInRawString("SELECT * FROM [JsonEntitiesInheritance] AS j")),
958+
ss => ss.Set<JsonEntityInheritanceDerived>(),
959+
entryCount: 25);
960+
961+
AssertSql(
962+
"""
963+
SELECT [m].[Id], [m].[Discriminator], [m].[Name], [m].[Fraction], JSON_QUERY([m].[CollectionOnBase],'$'), JSON_QUERY([m].[ReferenceOnBase],'$'), JSON_QUERY([m].[CollectionOnDerived],'$'), JSON_QUERY([m].[ReferenceOnDerived],'$')
964+
FROM (
965+
SELECT * FROM "JsonEntitiesInheritance" AS j
966+
) AS [m]
967+
WHERE [m].[Discriminator] = N'JsonEntityInheritanceDerived'
968+
""");
969+
}
970+
971+
[ConditionalTheory]
972+
[MemberData(nameof(IsAsyncData))]
973+
public virtual async Task FromSql_on_entity_with_json_inheritance_project_reference_on_base(bool async)
974+
{
975+
await AssertQuery(
976+
async,
977+
ss => ((DbSet<JsonEntityInheritanceBase>)ss.Set<JsonEntityInheritanceBase>()).FromSqlRaw(
978+
Fixture.TestStore.NormalizeDelimitersInRawString("SELECT * FROM [JsonEntitiesInheritance] AS j"))
979+
.AsNoTracking()
980+
.OrderBy(x => x.Id)
981+
.Select(x => x.ReferenceOnBase),
982+
ss => ss.Set<JsonEntityInheritanceBase>().OrderBy(x => x.Id).Select(x => x.ReferenceOnBase),
983+
assertOrder: true);
984+
985+
AssertSql(
986+
"""
987+
SELECT JSON_QUERY([m].[ReferenceOnBase],'$'), [m].[Id]
988+
FROM (
989+
SELECT * FROM "JsonEntitiesInheritance" AS j
990+
) AS [m]
991+
ORDER BY [m].[Id]
992+
""");
993+
}
994+
995+
[ConditionalTheory]
996+
[MemberData(nameof(IsAsyncData))]
997+
public virtual async Task FromSql_on_entity_with_json_inheritance_project_reference_on_derived(bool async)
998+
{
999+
await AssertQuery(
1000+
async,
1001+
ss => ((DbSet<JsonEntityInheritanceDerived>)ss.Set<JsonEntityInheritanceDerived>()).FromSqlRaw(
1002+
Fixture.TestStore.NormalizeDelimitersInRawString("SELECT * FROM [JsonEntitiesInheritance] AS j"))
1003+
.AsNoTracking()
1004+
.OrderBy(x => x.Id)
1005+
.Select(x => x.CollectionOnDerived),
1006+
ss => ss.Set<JsonEntityInheritanceDerived>().OrderBy(x => x.Id).Select(x => x.CollectionOnDerived),
1007+
elementAsserter: (e, a) => AssertCollection(e, a, elementSorter: ee => (ee.Date, ee.Enum, ee.Fraction)),
1008+
assertOrder: true);
1009+
1010+
AssertSql(
1011+
"""
1012+
SELECT JSON_QUERY([m].[CollectionOnDerived],'$'), [m].[Id]
1013+
FROM (
1014+
SELECT * FROM "JsonEntitiesInheritance" AS j
1015+
) AS [m]
1016+
WHERE [m].[Discriminator] = N'JsonEntityInheritanceDerived'
1017+
ORDER BY [m].[Id]
1018+
""");
1019+
}
1020+
8411021
private void AssertSql(params string[] expected)
8421022
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
8431023
}

0 commit comments

Comments
 (0)