diff --git a/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs index 096be03f94b..ff85628c439 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/AdHocJsonQueryTestBase.cs @@ -1467,6 +1467,296 @@ protected virtual void BuildModelNotICollection(ModelBuilder modelBuilder) #endregion + #region BadJsonProperties + + [ConditionalFact] + public virtual async Task Bad_json_properties_duplicated_navigations_tracking() + { + var contextFactory = await InitializeAsync( + onModelCreating: BuildModelBadJsonProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedBadJsonProperties); + + using (var context = contextFactory.CreateContext()) + { + var baseline = await context.Entities.SingleAsync(x => x.Scenario == "baseline"); + var dupNavs = await context.Entities.SingleAsync(x => x.Scenario == "duplicated navigations"); + + // for tracking, first one wins + Assert.Equal(baseline.RequiredReference.NestedOptional.Text, dupNavs.RequiredReference.NestedOptional.Text); + Assert.Equal(baseline.RequiredReference.NestedRequired.Text, dupNavs.RequiredReference.NestedRequired.Text); + Assert.Equal(baseline.RequiredReference.NestedCollection[0].Text, dupNavs.RequiredReference.NestedCollection[0].Text); + Assert.Equal(baseline.RequiredReference.NestedCollection[1].Text, dupNavs.RequiredReference.NestedCollection[1].Text); + + Assert.Equal(baseline.OptionalReference.NestedOptional.Text, dupNavs.OptionalReference.NestedOptional.Text); + Assert.Equal(baseline.OptionalReference.NestedRequired.Text, dupNavs.OptionalReference.NestedRequired.Text); + Assert.Equal(baseline.OptionalReference.NestedCollection[0].Text, dupNavs.OptionalReference.NestedCollection[0].Text); + Assert.Equal(baseline.OptionalReference.NestedCollection[1].Text, dupNavs.OptionalReference.NestedCollection[1].Text); + + Assert.Equal(baseline.Collection[0].NestedOptional.Text, dupNavs.Collection[0].NestedOptional.Text); + Assert.Equal(baseline.Collection[0].NestedRequired.Text, dupNavs.Collection[0].NestedRequired.Text); + Assert.Equal(baseline.Collection[0].NestedCollection[0].Text, dupNavs.Collection[0].NestedCollection[0].Text); + Assert.Equal(baseline.Collection[0].NestedCollection[1].Text, dupNavs.Collection[0].NestedCollection[1].Text); + + Assert.Equal(baseline.Collection[1].NestedOptional.Text, dupNavs.Collection[1].NestedOptional.Text); + Assert.Equal(baseline.Collection[1].NestedRequired.Text, dupNavs.Collection[1].NestedRequired.Text); + Assert.Equal(baseline.Collection[1].NestedCollection[0].Text, dupNavs.Collection[1].NestedCollection[0].Text); + Assert.Equal(baseline.Collection[1].NestedCollection[1].Text, dupNavs.Collection[1].NestedCollection[1].Text); + } + } + + [ConditionalFact] + public virtual async Task Bad_json_properties_duplicated_navigations_no_tracking() + { + var contextFactory = await InitializeAsync( + onModelCreating: BuildModelBadJsonProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedBadJsonProperties); + + using (var context = contextFactory.CreateContext()) + { + var query = context.Entities.AsNoTracking(); + + var baseline = query.Single(x => x.Scenario == "baseline"); + var dupNavs = query.Single(x => x.Scenario == "duplicated navigations"); + + // for no tracking, last one wins + Assert.Equal(baseline.RequiredReference.NestedOptional.Text + " dupnav", dupNavs.RequiredReference.NestedOptional.Text); + Assert.Equal(baseline.RequiredReference.NestedRequired.Text + " dupnav", dupNavs.RequiredReference.NestedRequired.Text); + Assert.Equal(baseline.RequiredReference.NestedCollection[0].Text + " dupnav", dupNavs.RequiredReference.NestedCollection[0].Text); + Assert.Equal(baseline.RequiredReference.NestedCollection[1].Text + " dupnav", dupNavs.RequiredReference.NestedCollection[1].Text); + + Assert.Equal(baseline.OptionalReference.NestedOptional.Text + " dupnav", dupNavs.OptionalReference.NestedOptional.Text); + Assert.Equal(baseline.OptionalReference.NestedRequired.Text + " dupnav", dupNavs.OptionalReference.NestedRequired.Text); + Assert.Equal(baseline.OptionalReference.NestedCollection[0].Text + " dupnav", dupNavs.OptionalReference.NestedCollection[0].Text); + Assert.Equal(baseline.OptionalReference.NestedCollection[1].Text + " dupnav", dupNavs.OptionalReference.NestedCollection[1].Text); + + Assert.Equal(baseline.Collection[0].NestedOptional.Text + " dupnav", dupNavs.Collection[0].NestedOptional.Text); + Assert.Equal(baseline.Collection[0].NestedRequired.Text + " dupnav", dupNavs.Collection[0].NestedRequired.Text); + Assert.Equal(baseline.Collection[0].NestedCollection[0].Text + " dupnav", dupNavs.Collection[0].NestedCollection[0].Text); + Assert.Equal(baseline.Collection[0].NestedCollection[1].Text + " dupnav", dupNavs.Collection[0].NestedCollection[1].Text); + + Assert.Equal(baseline.Collection[1].NestedOptional.Text + " dupnav", dupNavs.Collection[1].NestedOptional.Text); + Assert.Equal(baseline.Collection[1].NestedRequired.Text + " dupnav", dupNavs.Collection[1].NestedRequired.Text); + Assert.Equal(baseline.Collection[1].NestedCollection[0].Text + " dupnav", dupNavs.Collection[1].NestedCollection[0].Text); + Assert.Equal(baseline.Collection[1].NestedCollection[1].Text + " dupnav", dupNavs.Collection[1].NestedCollection[1].Text); + } + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Bad_json_properties_duplicated_scalars(bool noTracking) + { + var contextFactory = await InitializeAsync( + onModelCreating: BuildModelBadJsonProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedBadJsonProperties); + + using (var context = contextFactory.CreateContext()) + { + var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; + + var baseline = await query.SingleAsync(x => x.Scenario == "baseline"); + var dupProps = await query.SingleAsync(x => x.Scenario == "duplicated scalars"); + + Assert.Equal(baseline.RequiredReference.NestedOptional.Text + " dupprop", dupProps.RequiredReference.NestedOptional.Text); + Assert.Equal(baseline.RequiredReference.NestedRequired.Text + " dupprop", dupProps.RequiredReference.NestedRequired.Text); + Assert.Equal(baseline.RequiredReference.NestedCollection[0].Text + " dupprop", dupProps.RequiredReference.NestedCollection[0].Text); + Assert.Equal(baseline.RequiredReference.NestedCollection[1].Text + " dupprop", dupProps.RequiredReference.NestedCollection[1].Text); + + Assert.Equal(baseline.OptionalReference.NestedOptional.Text + " dupprop", dupProps.OptionalReference.NestedOptional.Text); + Assert.Equal(baseline.OptionalReference.NestedRequired.Text + " dupprop", dupProps.OptionalReference.NestedRequired.Text); + Assert.Equal(baseline.OptionalReference.NestedCollection[0].Text + " dupprop", dupProps.OptionalReference.NestedCollection[0].Text); + Assert.Equal(baseline.OptionalReference.NestedCollection[1].Text + " dupprop", dupProps.OptionalReference.NestedCollection[1].Text); + + Assert.Equal(baseline.Collection[0].NestedOptional.Text + " dupprop", dupProps.Collection[0].NestedOptional.Text); + Assert.Equal(baseline.Collection[0].NestedRequired.Text + " dupprop", dupProps.Collection[0].NestedRequired.Text); + Assert.Equal(baseline.Collection[0].NestedCollection[0].Text + " dupprop", dupProps.Collection[0].NestedCollection[0].Text); + Assert.Equal(baseline.Collection[0].NestedCollection[1].Text + " dupprop", dupProps.Collection[0].NestedCollection[1].Text); + + Assert.Equal(baseline.Collection[1].NestedOptional.Text + " dupprop", dupProps.Collection[1].NestedOptional.Text); + Assert.Equal(baseline.Collection[1].NestedRequired.Text + " dupprop", dupProps.Collection[1].NestedRequired.Text); + Assert.Equal(baseline.Collection[1].NestedCollection[0].Text + " dupprop", dupProps.Collection[1].NestedCollection[0].Text); + Assert.Equal(baseline.Collection[1].NestedCollection[1].Text + " dupprop", dupProps.Collection[1].NestedCollection[1].Text); + } + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Bad_json_properties_empty_navigations(bool noTracking) + { + var contextFactory = await InitializeAsync( + onModelCreating: BuildModelBadJsonProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedBadJsonProperties); + + using (var context = contextFactory.CreateContext()) + { + var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; + var emptyNavs = await query.SingleAsync(x => x.Scenario == "empty navigation property names"); + + Assert.Null(emptyNavs.RequiredReference.NestedOptional); + Assert.Null(emptyNavs.RequiredReference.NestedRequired); + Assert.Null(emptyNavs.RequiredReference.NestedCollection); + + Assert.Null(emptyNavs.OptionalReference.NestedOptional); + Assert.Null(emptyNavs.OptionalReference.NestedRequired); + Assert.Null(emptyNavs.OptionalReference.NestedCollection); + + Assert.Null(emptyNavs.Collection[0].NestedOptional); + Assert.Null(emptyNavs.Collection[0].NestedRequired); + Assert.Null(emptyNavs.Collection[0].NestedCollection); + + Assert.Null(emptyNavs.Collection[1].NestedOptional); + Assert.Null(emptyNavs.Collection[1].NestedRequired); + Assert.Null(emptyNavs.Collection[1].NestedCollection); + } + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Bad_json_properties_empty_scalars(bool noTracking) + { + var contextFactory = await InitializeAsync( + onModelCreating: BuildModelBadJsonProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedBadJsonProperties); + + using (var context = contextFactory.CreateContext()) + { + var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; + var emptyNavs = await query.SingleAsync(x => x.Scenario == "empty scalar property names"); + + Assert.Null(emptyNavs.RequiredReference.NestedOptional.Text); + Assert.Null(emptyNavs.RequiredReference.NestedRequired.Text); + Assert.Null(emptyNavs.RequiredReference.NestedCollection[0].Text); + Assert.Null(emptyNavs.RequiredReference.NestedCollection[1].Text); + + Assert.Null(emptyNavs.OptionalReference.NestedOptional.Text); + Assert.Null(emptyNavs.OptionalReference.NestedRequired.Text); + Assert.Null(emptyNavs.OptionalReference.NestedCollection[0].Text); + Assert.Null(emptyNavs.OptionalReference.NestedCollection[1].Text); + + Assert.Null(emptyNavs.Collection[0].NestedOptional.Text); + Assert.Null(emptyNavs.Collection[0].NestedRequired.Text); + Assert.Null(emptyNavs.Collection[0].NestedCollection[0].Text); + Assert.Null(emptyNavs.Collection[0].NestedCollection[1].Text); + + Assert.Null(emptyNavs.Collection[1].NestedOptional.Text); + Assert.Null(emptyNavs.Collection[1].NestedRequired.Text); + Assert.Null(emptyNavs.Collection[1].NestedCollection[0].Text); + Assert.Null(emptyNavs.Collection[1].NestedCollection[1].Text); + } + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Bad_json_properties_null_navigations(bool noTracking) + { + var contextFactory = await InitializeAsync( + onModelCreating: BuildModelBadJsonProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedBadJsonProperties); + + using (var context = contextFactory.CreateContext()) + { + var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; + + await Assert.ThrowsAnyAsync( + () => query.SingleAsync(x => x.Scenario == "null navigation property names")); + } + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Bad_json_properties_null_scalars(bool noTracking) + { + var contextFactory = await InitializeAsync( + onModelCreating: BuildModelBadJsonProperties, + onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings), + seed: SeedBadJsonProperties); + + using (var context = contextFactory.CreateContext()) + { + var query = noTracking ? context.Entities.AsNoTracking() : context.Entities; + + var message = (await Assert.ThrowsAnyAsync( + () => query.SingleAsync(x => x.Scenario == "null scalar property names"))).Message; + + Assert.StartsWith("'n' is an invalid start of a property name. Expected a '\"'.", message); + } + } + + protected class ContextBadJsonProperties(DbContextOptions options) : DbContext(options) + { + public DbSet Entities { get; set; } + + public class Entity + { + public int Id { get; set; } + public string Scenario { get; set; } + public JsonRoot OptionalReference { get; set; } + public JsonRoot RequiredReference { get; set; } + public List Collection { get; set; } + } + + public class JsonRoot + { + public JsonBranch NestedRequired { get; set; } + public JsonBranch NestedOptional { get; set; } + public List NestedCollection { get; set; } + } + + public class JsonBranch + { + public string Text { get; set; } + } + } + + protected abstract Task SeedBadJsonProperties(ContextBadJsonProperties ctx); + + protected virtual void BuildModelBadJsonProperties(ModelBuilder modelBuilder) + => modelBuilder.Entity( + b => + { + b.ToTable("Entities"); + b.Property(x => x.Id).ValueGeneratedNever(); + + b.OwnsOne( + x => x.RequiredReference, b => + { + b.ToJson().HasColumnType(JsonColumnType); + b.OwnsOne(x => x.NestedOptional); + b.OwnsOne(x => x.NestedRequired); + b.OwnsMany(x => x.NestedCollection); + }); + + b.OwnsOne( + x => x.OptionalReference, b => + { + b.ToJson().HasColumnType(JsonColumnType); + b.OwnsOne(x => x.NestedOptional); + b.OwnsOne(x => x.NestedRequired); + b.OwnsMany(x => x.NestedCollection); + }); + + b.OwnsMany( + x => x.Collection, b => + { + b.ToJson().HasColumnType(JsonColumnType); + b.OwnsOne(x => x.NestedOptional); + b.OwnsOne(x => x.NestedRequired); + b.OwnsMany(x => x.NestedCollection); + }); + }); + + #endregion + protected TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTestBase.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTestBase.cs index d598862d256..6cc90cd4c02 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTestBase.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocJsonQuerySqlServerTestBase.cs @@ -1,13 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -#nullable disable using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Diagnostics.Internal; namespace Microsoft.EntityFrameworkCore.Query; +#nullable disable + public abstract class AdHocJsonQuerySqlServerTestBase : AdHocJsonQueryTestBase { protected override ITestStoreFactory TestStoreFactory @@ -230,6 +231,107 @@ INSERT INTO [Entities] ([Json], [Id]) """); } + protected override async Task SeedBadJsonProperties(ContextBadJsonProperties ctx) + { + await ctx.Database.ExecuteSqlAsync( + $$""" +INSERT INTO [Entities] ([Id], [Scenario], [OptionalReference], [RequiredReference], [Collection]) +VALUES( +1, +N'baseline', +N'{"NestedOptional": { "Text":"or no" }, "NestedRequired": { "Text":"or nr" }, "NestedCollection": [ { "Text":"or nc 1" }, { "Text":"or nc 2" } ] }', +N'{"NestedOptional": { "Text":"rr no" }, "NestedRequired": { "Text":"rr nr" }, "NestedCollection": [ { "Text":"rr nc 1" }, { "Text":"rr nc 2" } ] }', +N'[ +{"NestedOptional": { "Text":"c 1 no" }, "NestedRequired": { "Text":"c 1 nr" }, "NestedCollection": [ { "Text":"c 1 nc 1" }, { "Text":"c 1 nc 2" } ] }, +{"NestedOptional": { "Text":"c 2 no" }, "NestedRequired": { "Text":"c 2 nr" }, "NestedCollection": [ { "Text":"c 2 nc 1" }, { "Text":"c 2 nc 2" } ] } +]') +"""); + + await ctx.Database.ExecuteSqlAsync( + $$""" +INSERT INTO [Entities] ([Id], [Scenario], [OptionalReference], [RequiredReference], [Collection]) +VALUES( +2, +N'duplicated navigations', +N'{"NestedOptional": { "Text":"or no" }, "NestedOptional": { "Text":"or no dupnav" }, "NestedRequired": { "Text":"or nr" }, "NestedCollection": [ { "Text":"or nc 1" }, { "Text":"or nc 2" } ], "NestedCollection": [ { "Text":"or nc 1 dupnav" }, { "Text":"or nc 2 dupnav" } ], "NestedRequired": { "Text":"or nr dupnav" } }', +N'{"NestedOptional": { "Text":"rr no" }, "NestedOptional": { "Text":"rr no dupnav" }, "NestedRequired": { "Text":"rr nr" }, "NestedCollection": [ { "Text":"rr nc 1" }, { "Text":"rr nc 2" } ], "NestedCollection": [ { "Text":"rr nc 1 dupnav" }, { "Text":"rr nc 2 dupnav" } ], "NestedRequired": { "Text":"rr nr dupnav" } }', +N'[ +{"NestedOptional": { "Text":"c 1 no" }, "NestedOptional": { "Text":"c 1 no dupnav" }, "NestedRequired": { "Text":"c 1 nr" }, "NestedCollection": [ { "Text":"c 1 nc 1" }, { "Text":"c 1 nc 2" } ], "NestedCollection": [ { "Text":"c 1 nc 1 dupnav" }, { "Text":"c 1 nc 2 dupnav" } ], "NestedRequired": { "Text":"c 1 nr dupnav" } }, +{"NestedOptional": { "Text":"c 2 no" }, "NestedOptional": { "Text":"c 2 no dupnav" }, "NestedRequired": { "Text":"c 2 nr" }, "NestedCollection": [ { "Text":"c 2 nc 1" }, { "Text":"c 2 nc 2" } ], "NestedCollection": [ { "Text":"c 2 nc 1 dupnav" }, { "Text":"c 2 nc 2 dupnav" } ], "NestedRequired": { "Text":"c 2 nr dupnav" } } +]') +"""); + + await ctx.Database.ExecuteSqlAsync( + $$""" +INSERT INTO [Entities] ([Id], [Scenario], [OptionalReference], [RequiredReference], [Collection]) +VALUES( +3, +N'duplicated scalars', +N'{"NestedOptional": { "Text":"or no", "Text":"or no dupprop" }, "NestedRequired": { "Text":"or nr", "Text":"or nr dupprop" }, "NestedCollection": [ { "Text":"or nc 1", "Text":"or nc 1 dupprop" }, { "Text":"or nc 2", "Text":"or nc 2 dupprop" } ] }', +N'{"NestedOptional": { "Text":"rr no", "Text":"rr no dupprop" }, "NestedRequired": { "Text":"rr nr", "Text":"rr nr dupprop" }, "NestedCollection": [ { "Text":"rr nc 1", "Text":"rr nc 1 dupprop" }, { "Text":"rr nc 2", "Text":"rr nc 2 dupprop" } ] }', +N'[ +{"NestedOptional": { "Text":"c 1 no", "Text":"c 1 no dupprop" }, "NestedRequired": { "Text":"c 1 nr", "Text":"c 1 nr dupprop" }, "NestedCollection": [ { "Text":"c 1 nc 1", "Text":"c 1 nc 1 dupprop" }, { "Text":"c 1 nc 2", "Text":"c 1 nc 2 dupprop" } ] }, +{"NestedOptional": { "Text":"c 2 no", "Text":"c 2 no dupprop" }, "NestedRequired": { "Text":"c 2 nr", "Text":"c 2 nr dupprop" }, "NestedCollection": [ { "Text":"c 2 nc 1", "Text":"c 2 nc 1 dupprop" }, { "Text":"c 2 nc 2", "Text":"c 2 nc 2 dupprop" } ] } +]') +"""); + + await ctx.Database.ExecuteSqlAsync( + $$""" +INSERT INTO [Entities] ([Id], [Scenario], [OptionalReference], [RequiredReference], [Collection]) +VALUES( +4, +N'empty navigation property names', +N'{"": { "Text":"or no" }, "": { "Text":"or nr" }, "": [ { "Text":"or nc 1" }, { "Text":"or nc 2" } ] }', +N'{"": { "Text":"rr no" }, "": { "Text":"rr nr" }, "": [ { "Text":"rr nc 1" }, { "Text":"rr nc 2" } ] }', +N'[ +{"": { "Text":"c 1 no" }, "": { "Text":"c 1 nr" }, "": [ { "Text":"c 1 nc 1" }, { "Text":"c 1 nc 2" } ] }, +{"": { "Text":"c 2 no" }, "": { "Text":"c 2 nr" }, "": [ { "Text":"c 2 nc 1" }, { "Text":"c 2 nc 2" } ] } +]') +"""); + + await ctx.Database.ExecuteSqlAsync( + $$""" +INSERT INTO [Entities] ([Id], [Scenario], [OptionalReference], [RequiredReference], [Collection]) +VALUES( +5, +N'empty scalar property names', +N'{"NestedOptional": { "":"or no" }, "NestedRequired": { "":"or nr" }, "NestedCollection": [ { "":"or nc 1" }, { "":"or nc 2" } ] }', +N'{"NestedOptional": { "":"rr no" }, "NestedRequired": { "":"rr nr" }, "NestedCollection": [ { "":"rr nc 1" }, { "":"rr nc 2" } ] }', +N'[ +{"NestedOptional": { "":"c 1 no" }, "NestedRequired": { "":"c 1 nr" }, "NestedCollection": [ { "":"c 1 nc 1" }, { "":"c 1 nc 2" } ] }, +{"NestedOptional": { "":"c 2 no" }, "NestedRequired": { "":"c 2 nr" }, "NestedCollection": [ { "":"c 2 nc 1" }, { "":"c 2 nc 2" } ] } +]') +"""); + + await ctx.Database.ExecuteSqlAsync( + $$""" +INSERT INTO [Entities] ([Id], [Scenario], [OptionalReference], [RequiredReference], [Collection]) +VALUES( +10, +N'null navigation property names', +N'{null: { "Text":"or no" }, null: { "Text":"or nr" }, null: [ { "Text":"or nc 1" }, { "Text":"or nc 2" } ] }', +N'{null: { "Text":"rr no" }, null: { "Text":"rr nr" }, null: [ { "Text":"rr nc 1" }, { "Text":"rr nc 2" } ] }', +N'[ +{null: { "Text":"c 1 no" }, null: { "Text":"c 1 nr" }, null: [ { "Text":"c 1 nc 1" }, { "Text":"c 1 nc 2" } ] }, +{null: { "Text":"c 2 no" }, null: { "Text":"c 2 nr" }, null: [ { "Text":"c 2 nc 1" }, { "Text":"c 2 nc 2" } ] } +]') +"""); + + await ctx.Database.ExecuteSqlAsync( + $$""" +INSERT INTO [Entities] ([Id], [Scenario], [OptionalReference], [RequiredReference], [Collection]) +VALUES( +11, +N'null scalar property names', +N'{"NestedOptional": { null:"or no", "Text":"or no nonnull" }, "NestedRequired": { null:"or nr", "Text":"or nr nonnull" }, "NestedCollection": [ { null:"or nc 1", "Text":"or nc 1 nonnull" }, { null:"or nc 2", "Text":"or nc 2 nonnull" } ] }', +N'{"NestedOptional": { null:"rr no", "Text":"rr no nonnull" }, "NestedRequired": { null:"rr nr", "Text":"rr nr nonnull" }, "NestedCollection": [ { null:"rr nc 1", "Text":"rr nc 1 nonnull" }, { null:"rr nc 2", "Text":"rr nc 2 nonnull" } ] }', +N'[ +{"NestedOptional": { null:"c 1 no", "Text":"c 1 no nonnull" }, "NestedRequired": { null:"c 1 nr", "Text":"c 1 nr nonnull" }, "NestedCollection": [ { null:"c 1 nc 1", "Text":"c 1 nc 1 nonnull" }, { null:"c 1 nc 2", "Text":"c 1 nc 2 nonnull" } ] }, +{"NestedOptional": { null:"c 2 no", "Text":"c 2 no nonnull" }, "NestedRequired": { null:"c 2 nr", "Text":"c 2 nr nonnull" }, "NestedCollection": [ { null:"c 2 nc 1", "Text":"c 2 nc 1 nonnull" }, { null:"c 2 nc 2", "Text":"c 2 nc 2 nonnull" } ] } +]') +"""); + } + #region EnumLegacyValues [ConditionalTheory] diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocJsonQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocJsonQuerySqliteTest.cs index 9934769efaf..55f245a3f94 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/AdHocJsonQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/AdHocJsonQuerySqliteTest.cs @@ -217,6 +217,107 @@ await ctx.Database.ExecuteSqlAsync( VALUES( '{"Collection":[{"Bar":21,"Foo":"c21"},{"Bar":22,"Foo":"c22"}]}', 2) +"""); + } + + protected override async Task SeedBadJsonProperties(ContextBadJsonProperties ctx) + { + await ctx.Database.ExecuteSqlAsync( + $$""" +INSERT INTO Entities (Id, Scenario, OptionalReference, RequiredReference, Collection) +VALUES( +1, +'baseline', +'{"NestedOptional": { "Text":"or no" }, "NestedRequired": { "Text":"or nr" }, "NestedCollection": [ { "Text":"or nc 1" }, { "Text":"or nc 2" } ] }', +'{"NestedOptional": { "Text":"rr no" }, "NestedRequired": { "Text":"rr nr" }, "NestedCollection": [ { "Text":"rr nc 1" }, { "Text":"rr nc 2" } ] }', +'[ +{"NestedOptional": { "Text":"c 1 no" }, "NestedRequired": { "Text":"c 1 nr" }, "NestedCollection": [ { "Text":"c 1 nc 1" }, { "Text":"c 1 nc 2" } ] }, +{"NestedOptional": { "Text":"c 2 no" }, "NestedRequired": { "Text":"c 2 nr" }, "NestedCollection": [ { "Text":"c 2 nc 1" }, { "Text":"c 2 nc 2" } ] } +]') +"""); + + await ctx.Database.ExecuteSqlAsync( + $$""" +INSERT INTO Entities (Id, Scenario, OptionalReference, RequiredReference, Collection) +VALUES( +2, +'duplicated navigations', +'{"NestedOptional": { "Text":"or no" }, "NestedOptional": { "Text":"or no dupnav" }, "NestedRequired": { "Text":"or nr" }, "NestedCollection": [ { "Text":"or nc 1" }, { "Text":"or nc 2" } ], "NestedCollection": [ { "Text":"or nc 1 dupnav" }, { "Text":"or nc 2 dupnav" } ], "NestedRequired": { "Text":"or nr dupnav" } }', +'{"NestedOptional": { "Text":"rr no" }, "NestedOptional": { "Text":"rr no dupnav" }, "NestedRequired": { "Text":"rr nr" }, "NestedCollection": [ { "Text":"rr nc 1" }, { "Text":"rr nc 2" } ], "NestedCollection": [ { "Text":"rr nc 1 dupnav" }, { "Text":"rr nc 2 dupnav" } ], "NestedRequired": { "Text":"rr nr dupnav" } }', +'[ +{"NestedOptional": { "Text":"c 1 no" }, "NestedOptional": { "Text":"c 1 no dupnav" }, "NestedRequired": { "Text":"c 1 nr" }, "NestedCollection": [ { "Text":"c 1 nc 1" }, { "Text":"c 1 nc 2" } ], "NestedCollection": [ { "Text":"c 1 nc 1 dupnav" }, { "Text":"c 1 nc 2 dupnav" } ], "NestedRequired": { "Text":"c 1 nr dupnav" } }, +{"NestedOptional": { "Text":"c 2 no" }, "NestedOptional": { "Text":"c 2 no dupnav" }, "NestedRequired": { "Text":"c 2 nr" }, "NestedCollection": [ { "Text":"c 2 nc 1" }, { "Text":"c 2 nc 2" } ], "NestedCollection": [ { "Text":"c 2 nc 1 dupnav" }, { "Text":"c 2 nc 2 dupnav" } ], "NestedRequired": { "Text":"c 2 nr dupnav" } } +]') +"""); + + await ctx.Database.ExecuteSqlAsync( + $$""" +INSERT INTO Entities (Id, Scenario, OptionalReference, RequiredReference, Collection) +VALUES( +3, +'duplicated scalars', +'{"NestedOptional": { "Text":"or no", "Text":"or no dupprop" }, "NestedRequired": { "Text":"or nr", "Text":"or nr dupprop" }, "NestedCollection": [ { "Text":"or nc 1", "Text":"or nc 1 dupprop" }, { "Text":"or nc 2", "Text":"or nc 2 dupprop" } ] }', +'{"NestedOptional": { "Text":"rr no", "Text":"rr no dupprop" }, "NestedRequired": { "Text":"rr nr", "Text":"rr nr dupprop" }, "NestedCollection": [ { "Text":"rr nc 1", "Text":"rr nc 1 dupprop" }, { "Text":"rr nc 2", "Text":"rr nc 2 dupprop" } ] }', +'[ +{"NestedOptional": { "Text":"c 1 no", "Text":"c 1 no dupprop" }, "NestedRequired": { "Text":"c 1 nr", "Text":"c 1 nr dupprop" }, "NestedCollection": [ { "Text":"c 1 nc 1", "Text":"c 1 nc 1 dupprop" }, { "Text":"c 1 nc 2", "Text":"c 1 nc 2 dupprop" } ] }, +{"NestedOptional": { "Text":"c 2 no", "Text":"c 2 no dupprop" }, "NestedRequired": { "Text":"c 2 nr", "Text":"c 2 nr dupprop" }, "NestedCollection": [ { "Text":"c 2 nc 1", "Text":"c 2 nc 1 dupprop" }, { "Text":"c 2 nc 2", "Text":"c 2 nc 2 dupprop" } ] } +]') +"""); + + await ctx.Database.ExecuteSqlAsync( + $$""" +INSERT INTO Entities (Id, Scenario, OptionalReference, RequiredReference, Collection) +VALUES( +4, +'empty navigation property names', +'{"": { "Text":"or no" }, "": { "Text":"or nr" }, "": [ { "Text":"or nc 1" }, { "Text":"or nc 2" } ] }', +'{"": { "Text":"rr no" }, "": { "Text":"rr nr" }, "": [ { "Text":"rr nc 1" }, { "Text":"rr nc 2" } ] }', +'[ +{"": { "Text":"c 1 no" }, "": { "Text":"c 1 nr" }, "": [ { "Text":"c 1 nc 1" }, { "Text":"c 1 nc 2" } ] }, +{"": { "Text":"c 2 no" }, "": { "Text":"c 2 nr" }, "": [ { "Text":"c 2 nc 1" }, { "Text":"c 2 nc 2" } ] } +]') +"""); + + await ctx.Database.ExecuteSqlAsync( + $$""" +INSERT INTO Entities (Id, Scenario, OptionalReference, RequiredReference, Collection) +VALUES( +5, +'empty scalar property names', +'{"NestedOptional": { "":"or no" }, "NestedRequired": { "":"or nr" }, "NestedCollection": [ { "":"or nc 1" }, { "":"or nc 2" } ] }', +'{"NestedOptional": { "":"rr no" }, "NestedRequired": { "":"rr nr" }, "NestedCollection": [ { "":"rr nc 1" }, { "":"rr nc 2" } ] }', +'[ +{"NestedOptional": { "":"c 1 no" }, "NestedRequired": { "":"c 1 nr" }, "NestedCollection": [ { "":"c 1 nc 1" }, { "":"c 1 nc 2" } ] }, +{"NestedOptional": { "":"c 2 no" }, "NestedRequired": { "":"c 2 nr" }, "NestedCollection": [ { "":"c 2 nc 1" }, { "":"c 2 nc 2" } ] } +]') +"""); + + await ctx.Database.ExecuteSqlAsync( + $$""" +INSERT INTO Entities (Id, Scenario, OptionalReference, RequiredReference, Collection) +VALUES( +10, +'null navigation property names', +'{null: { "Text":"or no" }, null: { "Text":"or nr" }, null: [ { "Text":"or nc 1" }, { "Text":"or nc 2" } ] }', +'{null: { "Text":"rr no" }, null: { "Text":"rr nr" }, null: [ { "Text":"rr nc 1" }, { "Text":"rr nc 2" } ] }', +'[ +{null: { "Text":"c 1 no" }, null: { "Text":"c 1 nr" }, null: [ { "Text":"c 1 nc 1" }, { "Text":"c 1 nc 2" } ] }, +{null: { "Text":"c 2 no" }, null: { "Text":"c 2 nr" }, null: [ { "Text":"c 2 nc 1" }, { "Text":"c 2 nc 2" } ] } +]') +"""); + + await ctx.Database.ExecuteSqlAsync( + $$""" +INSERT INTO Entities (Id, Scenario, OptionalReference, RequiredReference, Collection) +VALUES( +11, +'null scalar property names', +'{"NestedOptional": { null:"or no", "Text":"or no nonnull" }, "NestedRequired": { null:"or nr", "Text":"or nr nonnull" }, "NestedCollection": [ { null:"or nc 1", "Text":"or nc 1 nonnull" }, { null:"or nc 2", "Text":"or nc 2 nonnull" } ] }', +'{"NestedOptional": { null:"rr no", "Text":"rr no nonnull" }, "NestedRequired": { null:"rr nr", "Text":"rr nr nonnull" }, "NestedCollection": [ { null:"rr nc 1", "Text":"rr nc 1 nonnull" }, { null:"rr nc 2", "Text":"rr nc 2 nonnull" } ] }', +'[ +{"NestedOptional": { null:"c 1 no", "Text":"c 1 no nonnull" }, "NestedRequired": { null:"c 1 nr", "Text":"c 1 nr nonnull" }, "NestedCollection": [ { null:"c 1 nc 1", "Text":"c 1 nc 1 nonnull" }, { null:"c 1 nc 2", "Text":"c 1 nc 2 nonnull" } ] }, +{"NestedOptional": { null:"c 2 no", "Text":"c 2 no nonnull" }, "NestedRequired": { null:"c 2 nr", "Text":"c 2 nr nonnull" }, "NestedCollection": [ { null:"c 2 nc 1", "Text":"c 2 nc 1 nonnull" }, { null:"c 2 nc 2", "Text":"c 2 nc 2 nonnull" } ] } +]') """); } }