diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index f9a975f47a0..f7b9fd26adf 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -80,6 +80,7 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit private readonly RelationalShapedQueryCompilingExpressionVisitor _parentVisitor; private readonly ISet? _tags; private readonly bool _isTracking; + private readonly bool _queryStateManager; private readonly bool _isAsync; private readonly bool _splitQuery; private readonly bool _detailedErrorsEnabled; @@ -186,6 +187,8 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit _generateCommandCache = true; _detailedErrorsEnabled = parentVisitor._detailedErrorsEnabled; _isTracking = parentVisitor.QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.TrackAll; + _queryStateManager = parentVisitor.QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.TrackAll + || parentVisitor.QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution; _isAsync = parentVisitor.QueryCompilationContext.IsAsync; _splitQuery = splitQuery; @@ -212,6 +215,8 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit _generateCommandCache = false; _detailedErrorsEnabled = parentVisitor._detailedErrorsEnabled; _isTracking = parentVisitor.QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.TrackAll; + _queryStateManager = parentVisitor.QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.TrackAll + || parentVisitor.QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution; _isAsync = parentVisitor.QueryCompilationContext.IsAsync; _splitQuery = false; } @@ -241,6 +246,8 @@ private sealed partial class ShaperProcessingExpressionVisitor : ExpressionVisit _generateCommandCache = true; _detailedErrorsEnabled = parentVisitor._detailedErrorsEnabled; _isTracking = parentVisitor.QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.TrackAll; + _queryStateManager = parentVisitor.QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.TrackAll + || parentVisitor.QueryCompilationContext.QueryTrackingBehavior == QueryTrackingBehavior.NoTrackingWithIdentityResolution; _isAsync = parentVisitor.QueryCompilationContext.IsAsync; _splitQuery = true; @@ -1390,7 +1397,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp var rewrittenEntityShaperMaterializer = new JsonEntityMaterializerRewriter( entityType, - _isTracking, + _queryStateManager, jsonReaderDataShaperLambdaParameter, innerShapersMap, innerFixupMap, @@ -1512,7 +1519,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp private sealed class JsonEntityMaterializerRewriter : ExpressionVisitor { private readonly IEntityType _entityType; - private readonly bool _isTracking; + private readonly bool _queryStateManager; private readonly ParameterExpression _jsonReaderDataParameter; private readonly IDictionary _innerShapersMap; private readonly IDictionary _innerFixupMap; @@ -1528,7 +1535,7 @@ private sealed class JsonEntityMaterializerRewriter : ExpressionVisitor public JsonEntityMaterializerRewriter( IEntityType entityType, - bool isTracking, + bool queryStateManager, ParameterExpression jsonReaderDataParameter, IDictionary innerShapersMap, IDictionary innerFixupMap, @@ -1536,7 +1543,7 @@ private sealed class JsonEntityMaterializerRewriter : ExpressionVisitor IDiagnosticsLogger queryLogger) { _entityType = entityType; - _isTracking = isTracking; + _queryStateManager = queryStateManager; _jsonReaderDataParameter = jsonReaderDataParameter; _innerShapersMap = innerShapersMap; _innerFixupMap = innerFixupMap; @@ -1700,9 +1707,9 @@ protected override Expression VisitSwitch(SwitchExpression switchExpression) finalBlockExpressions.Add(propertyAssignmentReplacer.Visit(jsonEntityTypeInitializerBlockExpression)); } - // Fixup is only needed for non-tracking queries, in case of tracking - ChangeTracker does the job + // Fixup is only needed for non-tracking queries, in case of tracking (or NoTrackingWithIdentityResolution) - ChangeTracker does the job // or for empty/null collections of a tracking queries. - if (_isTracking) + if (_queryStateManager) { ProcessFixup(_trackingInnerFixupMap); } @@ -1879,7 +1886,8 @@ protected override Expression VisitConditional(ConditionalExpression conditional // the code here re-arranges the existing materializer so that even if we find parent in the change tracker // we still process all the child navigations, it's just that we use the parent instance from change tracker, rather than create new one #pragma warning disable EF1001 // Internal EF Core API usage. - if (_isTracking + //if (_isTracking + if (_queryStateManager && visited is ConditionalExpression { Test: BinaryExpression diff --git a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs index 78254b96b8b..931d2b3a319 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs @@ -27,6 +27,13 @@ public virtual Task Basic_json_projection_owner_entity_NoTracking(bool async) async, ss => ss.Set().AsNoTracking()); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Basic_json_projection_owner_entity_NoTrackingWithIdentityResolution(bool async) + => AssertQuery( + async, + ss => ss.Set().AsNoTrackingWithIdentityResolution()); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Basic_json_projection_owner_entity_duplicated(bool async) @@ -53,6 +60,19 @@ public virtual Task Basic_json_projection_owner_entity_duplicated_NoTracking(boo AssertEqual(e.Second, a.Second); }); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Basic_json_projection_owner_entity_duplicated_NoTrackingWithIdentityResolution(bool async) + => AssertQuery( + async, + ss => ss.Set().Select(x => new { First = x, Second = x }).AsNoTrackingWithIdentityResolution(), + elementSorter: e => e.First.Id, + elementAsserter: (e, a) => + { + AssertEqual(e.First, a.First); + AssertEqual(e.Second, a.Second); + }); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Basic_json_projection_owner_entity_twice(bool async) @@ -79,6 +99,19 @@ public virtual Task Basic_json_projection_owner_entity_twice_NoTracking(bool asy AssertEqual(e.Second, a.Second); }); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Basic_json_projection_owner_entity_twice_NoTrackingWithIdentityResolution(bool async) + => AssertQuery( + async, + ss => ss.Set().Select(x => new { First = x, Second = x }).AsNoTrackingWithIdentityResolution(), + elementSorter: e => e.First.Id, + elementAsserter: (e, a) => + { + AssertEqual(e.First, a.First); + AssertEqual(e.Second, a.Second); + }); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task Project_json_reference_in_tracking_query_fails(bool async) @@ -137,6 +170,61 @@ public virtual Task Basic_json_projection_owned_reference_root(bool async) async, ss => ss.Set().Select(x => x.OwnedReferenceRoot).AsNoTracking()); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Basic_json_projection_owned_reference_root_NoTrackingWithIdentityResolution(bool async) + => AssertQuery( + async, + ss => ss.Set().Select(x => x.OwnedReferenceRoot).AsNoTrackingWithIdentityResolution()); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Basic_json_projection_owned_reference_duplicated(bool async) + => AssertQuery( + async, + ss => ss.Set() + .OrderBy(x => x.Id) + .Select( + x => new + { + Root1 = x.OwnedReferenceRoot, + Branch1 = x.OwnedReferenceRoot.OwnedReferenceBranch, + Root2 = x.OwnedReferenceRoot, + Branch2 = x.OwnedReferenceRoot.OwnedReferenceBranch, + }).AsNoTracking(), + assertOrder: true, + elementAsserter: (e, a) => + { + AssertEqual(e.Root1, a.Root1); + AssertEqual(e.Root2, a.Root2); + AssertEqual(e.Branch1, a.Branch1); + AssertEqual(e.Branch2, a.Branch2); + }); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Basic_json_projection_owned_reference_duplicated_NoTrackingWithIdentityResolution(bool async) + => AssertQuery( + async, + ss => ss.Set() + .OrderBy(x => x.Id) + .Select( + x => new + { + Root1 = x.OwnedReferenceRoot, + Branch1 = x.OwnedReferenceRoot.OwnedReferenceBranch, + Root2 = x.OwnedReferenceRoot, + Branch2 = x.OwnedReferenceRoot.OwnedReferenceBranch, + }).AsNoTrackingWithIdentityResolution(), + assertOrder: true, + elementAsserter: (e, a) => + { + AssertEqual(e.Root1, a.Root1); + AssertEqual(e.Root2, a.Root2); + AssertEqual(e.Branch1, a.Branch1); + AssertEqual(e.Branch2, a.Branch2); + }); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Basic_json_projection_owned_reference_duplicated2(bool async) @@ -163,7 +251,7 @@ public virtual Task Basic_json_projection_owned_reference_duplicated2(bool async [ConditionalTheory] [MemberData(nameof(IsAsyncData))] - public virtual Task Basic_json_projection_owned_reference_duplicated(bool async) + public virtual Task Basic_json_projection_owned_reference_duplicated2_NoTrackingWithIdentityResolution(bool async) => AssertQuery( async, ss => ss.Set() @@ -172,17 +260,17 @@ public virtual Task Basic_json_projection_owned_reference_duplicated(bool async) x => new { Root1 = x.OwnedReferenceRoot, - Branch1 = x.OwnedReferenceRoot.OwnedReferenceBranch, + Leaf1 = x.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf, Root2 = x.OwnedReferenceRoot, - Branch2 = x.OwnedReferenceRoot.OwnedReferenceBranch, - }).AsNoTracking(), + Leaf2 = x.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf, + }).AsNoTrackingWithIdentityResolution(), assertOrder: true, elementAsserter: (e, a) => { AssertEqual(e.Root1, a.Root1); AssertEqual(e.Root2, a.Root2); - AssertEqual(e.Branch1, a.Branch1); - AssertEqual(e.Branch2, a.Branch2); + AssertEqual(e.Leaf1, a.Leaf1); + AssertEqual(e.Leaf2, a.Leaf2); }); [ConditionalTheory] @@ -193,6 +281,14 @@ public virtual Task Basic_json_projection_owned_collection_root(bool async) ss => ss.Set().Select(x => x.OwnedCollectionRoot).AsNoTracking(), elementAsserter: (e, a) => AssertCollection(e, a, ordered: true)); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Basic_json_projection_owned_collection_root_NoTrackingWithIdentityResolution(bool async) + => AssertQuery( + async, + ss => ss.Set().Select(x => x.OwnedCollectionRoot).AsNoTrackingWithIdentityResolution(), + elementAsserter: (e, a) => AssertCollection(e, a, ordered: true)); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Basic_json_projection_owned_reference_branch(bool async) @@ -200,6 +296,13 @@ public virtual Task Basic_json_projection_owned_reference_branch(bool async) async, ss => ss.Set().Select(x => x.OwnedReferenceRoot.OwnedReferenceBranch).AsNoTracking()); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Basic_json_projection_owned_reference_branch_NoTrackingWithIdentityResolution(bool async) + => AssertQuery( + async, + ss => ss.Set().Select(x => x.OwnedReferenceRoot.OwnedReferenceBranch).AsNoTrackingWithIdentityResolution()); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Basic_json_projection_owned_collection_branch(bool async) @@ -208,6 +311,14 @@ public virtual Task Basic_json_projection_owned_collection_branch(bool async) ss => ss.Set().Select(x => x.OwnedReferenceRoot.OwnedCollectionBranch).AsNoTracking(), elementAsserter: (e, a) => AssertCollection(e, a, ordered: true)); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Basic_json_projection_owned_collection_branch_NoTrackingWithIdentityResolution(bool async) + => AssertQuery( + async, + ss => ss.Set().Select(x => x.OwnedReferenceRoot.OwnedCollectionBranch).AsNoTrackingWithIdentityResolution(), + elementAsserter: (e, a) => AssertCollection(e, a, ordered: true)); + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task Basic_json_projection_owned_reference_leaf(bool async) diff --git a/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs index 505cb12e975..fe633bfc9dd 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocMiscellaneousQueryTestBase.cs @@ -160,20 +160,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) public DbSet Customers { get; set; } public DbSet Postcodes { get; set; } - } - public class Customer - { - public int CustomerID { get; set; } - public string CustomerName { get; set; } - public int? PostcodeID { get; set; } - } + public class Customer + { + public int CustomerID { get; set; } + public string CustomerName { get; set; } + public int? PostcodeID { get; set; } + } - public class Postcode - { - public int PostcodeID { get; set; } - public string PostcodeValue { get; set; } - public string TownName { get; set; } + public class Postcode + { + public int PostcodeID { get; set; } + public string PostcodeValue { get; set; } + public string TownName { get; set; } + } } #endregion diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs index 143ca36cc88..bad919862c9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs @@ -2414,4 +2414,160 @@ ELSE NULL END = N'COUNTRY' """); } + + +#nullable enable + + [ConditionalFact] + public async Task MyTest() + { + //using (var context = new MyContext()) + //{ + // context.Database.EnsureDeleted(); + // context.Database.EnsureCreated(); + + // var customerAlan = new Customer + // { + // Name = "Alan", + // LastName = "XXX", + // ContactInfo = new List + // { + // new ContactInfo { ContactInfoType = "Home", Email = "alan@someone.eu", Phone = "45325252532"}, + // new ContactInfo { ContactInfoType = "Work", Email = "alan@company.eu", Phone = "+44220000077"} + // } + // }; + + // var customerDylan = new Customer + // { + // Name = "Dylan", + // LastName = "YYY", + // ContactInfo = new List + // { + // new ContactInfo { ContactInfoType = "Home", Email = "dylan@someone.eu", Phone = "7254367347"}, + // new ContactInfo { ContactInfoType = "Work", Email = "dylan@company.eu", Phone = "547546-2323542"}, + // new ContactInfo { ContactInfoType = "Work2", Email = "dylan@company2.eu", Phone = "55522255"} + // } + // }; + + // var customerRita = new Customer + // { + // Name = "Rita", + // LastName = "ZZZ", + // ContactInfo = new List + // { + // new ContactInfo { ContactInfoType = "Work", Email = "rita@business.eu", Phone = "+46253253"}, + // new ContactInfo { ContactInfoType = "Home", Email = "rita@home.eu", Phone = "68263000"}, + // new ContactInfo { ContactInfoType = "SummerHome", Email = "rita@summer.eu", Phone = "5555555"} + // } + // }; + + // context.Customers.Add(customerAlan); + // context.Customers.Add(customerDylan); + // context.Customers.Add(customerRita); + // await context.SaveChangesAsync(); + + // context.Cases.Add(new Case + // { + // Type = "a", + // CaseCustomers = new List + //{ + // new CaseCustomer{ CustomerId = customerAlan.Id}, + // new CaseCustomer{ CustomerId = customerDylan.Id}, + // new CaseCustomer{ CustomerId = customerAlan.Id}, + //} + // }); + + // context.Cases.Add(new Case + // { + // Type = "B", + // CaseCustomers = new List + //{ + // new CaseCustomer{ CustomerId = customerRita.Id}, + // new CaseCustomer{ CustomerId = customerDylan.Id} + //} + // }); + + // await context.SaveChangesAsync(); + //} + + using (var context = new MyContext()) + { + + //var resultsi = await context.Set().AsNoTracking().Include(cc => cc.Customer).Where(e => e.CaseId == 1).ToListAsync(); + + var results1i = await context.Set().AsNoTrackingWithIdentityResolution().Include(cc => cc.Customer).Where(e => e.CaseId == 1).ToListAsync(); + + + //var results1tracking = await context.Set().AsTracking().Include(cc => cc.Customer).Where(e => e.CaseId == 1).ToListAsync(); + + + + //var foobar = await context.Set().Where(x => x.Id == 1).ToListAsync(); + } + } + + public class Case + { + public int Id { get; set; } +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public string Type { get; set; } + public List CaseCustomers { get; set; } +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + } + + public class CaseCustomer + { + public int Id { get; set; } + + public int CaseId { get; set; } + public Case Case { get; set; } = null!; + public int? CustomerId { get; set; } + public Customer? Customer { get; set; } + } + + public class Customer + { + public int Id { get; set; } +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public string Name { get; set; } + public string LastName { get; set; } + public List ContactInfo { get; set; } +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + } + + public class ContactInfo + { +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public string ContactInfoType { get; set; } +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public string? Email { get; set; } + public string? Phone { get; set; } + } + + public class MyContext : DbContext + { +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public DbSet Cases { get; set; } + public DbSet Customers { get; set; } + + public DbSet CaseCustomers { get; set; } +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Repro;Trusted_Connection=True;MultipleActiveResultSets=true"); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().OwnsMany(c => c.ContactInfo, ownedNavigationBuilder => + { + ownedNavigationBuilder.ToJson(); + }); + } + } + + + +#nullable disable } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs index 266ea31db78..f45a3ec8689 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs @@ -37,6 +37,17 @@ public override async Task Basic_json_projection_owner_entity_NoTracking(bool as """); } + public override async Task Basic_json_projection_owner_entity_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owner_entity_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT [j].[Id], [j].[EntityBasicId], [j].[Name], [j].[OwnedCollectionRoot], [j].[OwnedReferenceRoot] +FROM [JsonEntitiesBasic] AS [j] +"""); + } + public override async Task Basic_json_projection_owner_entity_duplicated(bool async) { await base.Basic_json_projection_owner_entity_duplicated(async); @@ -59,6 +70,17 @@ public override async Task Basic_json_projection_owner_entity_duplicated_NoTrack """); } + public override async Task Basic_json_projection_owner_entity_duplicated_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owner_entity_duplicated_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT [j].[Id], [j].[Name], [j].[OwnedCollection], [j].[OwnedCollection] +FROM [JsonEntitiesSingleOwned] AS [j] +"""); + } + public override async Task Basic_json_projection_owner_entity_twice(bool async) { await base.Basic_json_projection_owner_entity_twice(async); @@ -81,6 +103,17 @@ public override async Task Basic_json_projection_owner_entity_twice_NoTracking(b """); } + public override async Task Basic_json_projection_owner_entity_twice_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owner_entity_twice_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT [j].[Id], [j].[EntityBasicId], [j].[Name], [j].[OwnedCollectionRoot], [j].[OwnedReferenceRoot], [j].[OwnedCollectionRoot], [j].[OwnedReferenceRoot] +FROM [JsonEntitiesBasic] AS [j] +"""); + } + public override async Task Basic_json_projection_owned_reference_root(bool async) { await base.Basic_json_projection_owned_reference_root(async); @@ -92,6 +125,41 @@ public override async Task Basic_json_projection_owned_reference_root(bool async """); } + public override async Task Basic_json_projection_owned_reference_root_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_reference_root_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT [j].[OwnedReferenceRoot], [j].[Id] +FROM [JsonEntitiesBasic] AS [j] +"""); + } + + public override async Task Basic_json_projection_owned_reference_duplicated(bool async) + { + await base.Basic_json_projection_owned_reference_duplicated(async); + + AssertSql( + """ +SELECT [j].[OwnedReferenceRoot], [j].[Id], JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedReferenceBranch'), [j].[OwnedReferenceRoot], JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedReferenceBranch') +FROM [JsonEntitiesBasic] AS [j] +ORDER BY [j].[Id] +"""); + } + + public override async Task Basic_json_projection_owned_reference_duplicated_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_reference_duplicated_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT [j].[OwnedReferenceRoot], [j].[Id], JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedReferenceBranch'), [j].[OwnedReferenceRoot], JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedReferenceBranch') +FROM [JsonEntitiesBasic] AS [j] +ORDER BY [j].[Id] +"""); + } + public override async Task Basic_json_projection_owned_reference_duplicated2(bool async) { await base.Basic_json_projection_owned_reference_duplicated2(async); @@ -104,13 +172,13 @@ public override async Task Basic_json_projection_owned_reference_duplicated2(boo """); } - public override async Task Basic_json_projection_owned_reference_duplicated(bool async) + public override async Task Basic_json_projection_owned_reference_duplicated2_NoTrackingWithIdentityResolution(bool async) { - await base.Basic_json_projection_owned_reference_duplicated(async); + await base.Basic_json_projection_owned_reference_duplicated2_NoTrackingWithIdentityResolution(async); AssertSql( """ -SELECT [j].[OwnedReferenceRoot], [j].[Id], JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedReferenceBranch'), [j].[OwnedReferenceRoot], JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedReferenceBranch') +SELECT [j].[OwnedReferenceRoot], [j].[Id], JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedReferenceBranch.OwnedReferenceLeaf'), [j].[OwnedReferenceRoot], JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedReferenceBranch.OwnedReferenceLeaf') FROM [JsonEntitiesBasic] AS [j] ORDER BY [j].[Id] """); @@ -127,6 +195,17 @@ public override async Task Basic_json_projection_owned_collection_root(bool asyn """); } + public override async Task Basic_json_projection_owned_collection_root_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_collection_root_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT [j].[OwnedCollectionRoot], [j].[Id] +FROM [JsonEntitiesBasic] AS [j] +"""); + } + public override async Task Basic_json_projection_owned_reference_branch(bool async) { await base.Basic_json_projection_owned_reference_branch(async); @@ -138,6 +217,17 @@ public override async Task Basic_json_projection_owned_reference_branch(bool asy """); } + public override async Task Basic_json_projection_owned_reference_branch_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_reference_branch_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedReferenceBranch'), [j].[Id] +FROM [JsonEntitiesBasic] AS [j] +"""); + } + public override async Task Basic_json_projection_owned_collection_branch(bool async) { await base.Basic_json_projection_owned_collection_branch(async); @@ -149,6 +239,17 @@ public override async Task Basic_json_projection_owned_collection_branch(bool as """); } + public override async Task Basic_json_projection_owned_collection_branch_NoTrackingWithIdentityResolution(bool async) + { + await base.Basic_json_projection_owned_collection_branch_NoTrackingWithIdentityResolution(async); + + AssertSql( + """ +SELECT JSON_QUERY([j].[OwnedReferenceRoot], '$.OwnedCollectionBranch'), [j].[Id] +FROM [JsonEntitiesBasic] AS [j] +"""); + } + public override async Task Basic_json_projection_owned_reference_leaf(bool async) { await base.Basic_json_projection_owned_reference_leaf(async);