From d08bc4689b8db33461e7f3c0f0a464d5ffaa029a Mon Sep 17 00:00:00 2001 From: maumar Date: Wed, 14 Feb 2024 14:43:45 -0800 Subject: [PATCH] Fix to #33073 - JSON columns throws Invalid token type: 'StartObject' exception with AsNoTrackingWithIdentityResolution() Problem was that we were treating NoTrackingWithIdentityResolution queries as regular NoTracking in the context of JSON queries. However due to how JSON is materialized (streaming + nested includes are part of the parent materialization, rather than each entity materialized separately) we have special provisions when ChangeTracker is being used. In NoTrackingWithIdentityResolution, ChangeTracker is being used but the materializer didn't adjust to that, which lead to errors. Fix is to generate JSON materializer code based on whether query uses Change Tracker rather than if it's a Tracking/NoTracking query. Fixes #33073 --- ...sitor.ShaperProcessingExpressionVisitor.cs | 22 ++- .../Query/JsonQueryTestBase.cs | 123 +++++++++++++- .../Query/AdHocMiscellaneousQueryTestBase.cs | 24 +-- .../AdHocMiscellaneousQuerySqlServerTest.cs | 156 ++++++++++++++++++ .../Query/JsonQuerySqlServerTest.cs | 107 +++++++++++- 5 files changed, 404 insertions(+), 28 deletions(-) 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 static readonly MethodInfo EnumParseMethodInfo 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 @@ public ShaperProcessingExpressionVisitor( _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 ShaperProcessingExpressionVisitor( _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 ShaperProcessingExpressionVisitor( _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 @@ private Expression CreateJsonShapers( var rewrittenEntityShaperMaterializer = new JsonEntityMaterializerRewriter( entityType, - _isTracking, + _queryStateManager, jsonReaderDataShaperLambdaParameter, innerShapersMap, innerFixupMap, @@ -1512,7 +1519,7 @@ private Expression CreateJsonShapers( 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 static readonly PropertyInfo JsonEncodedTextEncodedUtf8BytesProperty public JsonEntityMaterializerRewriter( IEntityType entityType, - bool isTracking, + bool queryStateManager, ParameterExpression jsonReaderDataParameter, IDictionary innerShapersMap, IDictionary innerFixupMap, @@ -1536,7 +1543,7 @@ public JsonEntityMaterializerRewriter( 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 @@ FROM [JsonEntitiesBasic] AS [j] """); } + 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 @@ FROM [JsonEntitiesSingleOwned] AS [j] """); } + 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 @@ FROM [JsonEntitiesBasic] AS [j] """); } + 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 @@ FROM [JsonEntitiesBasic] AS [j] """); } + 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 @@ ORDER BY [j].[Id] """); } - 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 @@ FROM [JsonEntitiesBasic] AS [j] """); } + 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 @@ FROM [JsonEntitiesBasic] AS [j] """); } + 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 @@ FROM [JsonEntitiesBasic] AS [j] """); } + 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);