From 7923d9adacb128a5b485809a300de75e98832842 Mon Sep 17 00:00:00 2001 From: maumar Date: Mon, 22 Aug 2022 00:10:14 -0700 Subject: [PATCH] Fix to #28845 - JSON: add support for partial update to update just a single scalar property, rather than entire entities Optimization for partial updates - when only one property is modified on the entity we can change it directly, rather than rewriting the JSON element for the entire entity. To avoid string formatting issues we construct faux JSON array and insert the value that is to be the replacement, and then in the update statement we extract the value using JSON_VALUE. In case of numeric or bool we need an additional convert statement around the value. Fixes #28845 --- .../Update/ColumnModificationParameters.cs | 20 +- .../Update/IColumnModification.cs | 3 + .../Update/ModificationCommand.cs | 126 +++-- .../Internal/SqlServerUpdateSqlGenerator.cs | 35 +- .../Query/JsonQueryFixtureBase.cs | 53 ++ .../Query/JsonQueryTestBase.cs | 34 ++ .../JsonQuery/JsonEntityAllTypes.cs | 12 + .../TestModels/JsonQuery/JsonOwnedAllTypes.cs | 26 + .../TestModels/JsonQuery/JsonQueryContext.cs | 3 + .../TestModels/JsonQuery/JsonQueryData.cs | 57 ++ .../Update/JsonUpdateFixtureBase.cs | 14 + .../Update/JsonUpdateTestBase.cs | 535 ++++++++++++++++++ .../Query/JsonQuerySqlServerTest.cs | 18 + .../Update/JsonUpdateSqlServerTest.cs | 445 ++++++++++++++- 14 files changed, 1312 insertions(+), 69 deletions(-) create mode 100644 test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityAllTypes.cs create mode 100644 test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonOwnedAllTypes.cs diff --git a/src/EFCore.Relational/Update/ColumnModificationParameters.cs b/src/EFCore.Relational/Update/ColumnModificationParameters.cs index 7ddd7ce9150..1a613ba3bac 100644 --- a/src/EFCore.Relational/Update/ColumnModificationParameters.cs +++ b/src/EFCore.Relational/Update/ColumnModificationParameters.cs @@ -236,14 +236,14 @@ public ColumnModificationParameters( } /// - /// Creates a new instance. + /// Creates a new instance specific for updating objects mapped to JSON column. /// - /// The name of the column. - /// The original value of the property mapped to this column. - /// The current value of the property mapped to this column. - /// The JSON path leading to the JSON element that needs to be updated. - /// The database type of the column. + /// The name of the JSON column. + /// The current value of the JSON element located at the given JSON path. + /// In case of JSON column single scalar property modification, the scalar property that is being modified, null otherwise. + /// The database type of the JSON column. /// The relational type mapping to be used for the command parameter. + /// The JSON path leading to the JSON element that needs to be updated. /// Indicates whether a value must be read from the database for the column. /// Indicates whether a value must be written to the database for the column. /// Indicates whether the column part of a primary or alternate key. @@ -252,8 +252,8 @@ public ColumnModificationParameters( /// A value indicating whether the value could be null. public ColumnModificationParameters( string columnName, - object? originalValue, object? value, + IProperty? property, string? columnType, RelationalTypeMapping? typeMapping, string jsonPath, @@ -266,12 +266,11 @@ public ColumnModificationParameters( { Column = null; ColumnName = columnName; - OriginalValue = originalValue; + OriginalValue = null; Value = value; - Property = null; + Property = property; ColumnType = columnType; TypeMapping = typeMapping; - JsonPath = jsonPath; IsRead = read; IsWrite = write; IsKey = key; @@ -281,5 +280,6 @@ public ColumnModificationParameters( GenerateParameterName = null; Entry = null; + JsonPath = jsonPath; } } diff --git a/src/EFCore.Relational/Update/IColumnModification.cs b/src/EFCore.Relational/Update/IColumnModification.cs index 5e8c3510bd9..c24d009a9db 100644 --- a/src/EFCore.Relational/Update/IColumnModification.cs +++ b/src/EFCore.Relational/Update/IColumnModification.cs @@ -28,6 +28,9 @@ public interface IColumnModification /// /// The property that maps to the column. /// + /// + /// In case of JSON column single scalar property modification, the scalar property that is being modified. + /// public IProperty? Property { get; } /// diff --git a/src/EFCore.Relational/Update/ModificationCommand.cs b/src/EFCore.Relational/Update/ModificationCommand.cs index 4dc4d9f55a5..90c2824af6a 100644 --- a/src/EFCore.Relational/Update/ModificationCommand.cs +++ b/src/EFCore.Relational/Update/ModificationCommand.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Data; +using System.Text.Json; using System.Text.Json.Nodes; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Internal; @@ -251,6 +252,13 @@ public virtual IColumnModification AddColumnModification(in ColumnModificationPa protected virtual IColumnModification CreateColumnModification(in ColumnModificationParameters columnModificationParameters) => new ColumnModification(columnModificationParameters); + private sealed class JsonPartialUpdateInfo + { + public List Path { get; } = new(); + public IProperty? Property { get; set; } + public object? PropertyValue { get; set; } + } + private record struct JsonPartialUpdatePathEntry { public JsonPartialUpdatePathEntry( @@ -322,19 +330,16 @@ private List GenerateColumnModifications() if (jsonEntry) { - var jsonColumnsUpdateMap = new Dictionary>(); + var jsonColumnsUpdateMap = new Dictionary(); var processedEntries = new List(); foreach (var entry in _entries.Where(e => e.EntityType.IsMappedToJson())) { - var modifiedMembers = entry.ToEntityEntry().Members.Where(m => m is not NavigationEntry && m.IsModified).ToList(); + var modifiedMembers = entry.ToEntityEntry().Properties.Where(m => m.IsModified).ToList(); var jsonColumn = entry.EntityType.GetContainerColumnName()!; var jsonPartialUpdateInfo = FindJsonPartialUpdateInfo(entry, processedEntries); - processedEntries.Add(entry); if (jsonPartialUpdateInfo == null) { - // this entry is a subtree of an entry that we already processed - // so we already need to update the parent - no need to have extra entry for the subtree continue; } @@ -348,52 +353,60 @@ private List GenerateColumnModifications() jsonColumnsUpdateMap[jsonColumn] = jsonPartialUpdateInfo; } - foreach (var (jsonColumnName, updatePath) in jsonColumnsUpdateMap) + foreach (var (jsonColumnName, updateInfo) in jsonColumnsUpdateMap) { - var finalUpdatePathElement = updatePath.Last(); + var finalUpdatePathElement = updateInfo.Path.Last(); var navigation = finalUpdatePathElement.Navigation; var jsonColumnTypeMapping = navigation.TargetEntityType.GetContainerColumnTypeMapping()!; var navigationValue = finalUpdatePathElement.ParentEntry.GetCurrentValue(navigation); var json = default(JsonNode?); - if (finalUpdatePathElement.Ordinal != null && navigationValue != null) + var jsonPathString = string.Join( + ".", updateInfo.Path.Select(x => x.PropertyName + (x.Ordinal != null ? "[" + x.Ordinal + "]" : ""))); + + if (updateInfo.Property != null) { - int i = 0; - foreach (var navigationValueElement in (IEnumerable)navigationValue) + json = new JsonArray(JsonValue.Create(updateInfo.PropertyValue)); + jsonPathString = jsonPathString + "." + updateInfo.Property.GetJsonPropertyName(); + } + else + { + if (finalUpdatePathElement.Ordinal != null && navigationValue != null) { - if (i == finalUpdatePathElement.Ordinal) + var i = 0; + foreach (var navigationValueElement in (IEnumerable)navigationValue) { - json = CreateJson( - navigationValueElement, - finalUpdatePathElement.ParentEntry, - navigation.TargetEntityType, - ordinal: null, - isCollection: false); - - break; + if (i == finalUpdatePathElement.Ordinal) + { + json = CreateJson( + navigationValueElement, + finalUpdatePathElement.ParentEntry, + navigation.TargetEntityType, + ordinal: null, + isCollection: false); + + break; + } + + i++; } - - i++; } - } - else - { - json = CreateJson( - navigationValue, - finalUpdatePathElement.ParentEntry, - navigation.TargetEntityType, - ordinal: null, - isCollection: navigation.IsCollection); + else + { + json = CreateJson( + navigationValue, + finalUpdatePathElement.ParentEntry, + navigation.TargetEntityType, + ordinal: null, + isCollection: navigation.IsCollection); + } } - var jsonPathString = string.Join( - ".", updatePath.Select(x => x.PropertyName + (x.Ordinal != null ? "[" + x.Ordinal + "]" : ""))); - var columnModificationParameters = new ColumnModificationParameters( jsonColumnName, - originalValue: null, value: json?.ToJsonString(), + property: updateInfo.Property, columnType: jsonColumnTypeMapping.StoreType, jsonColumnTypeMapping, jsonPath: jsonPathString, @@ -582,9 +595,9 @@ entry.EntityState is EntityState.Modified or EntityState.Added return columnModifications; - static List? FindJsonPartialUpdateInfo(IUpdateEntry entry, List processedEntries) + static JsonPartialUpdateInfo? FindJsonPartialUpdateInfo(IUpdateEntry entry, List processedEntries) { - var result = new List(); + var result = new JsonPartialUpdateInfo(); var currentEntry = entry; var currentOwnership = currentEntry.EntityType.FindOwnership()!; @@ -617,7 +630,20 @@ entry.EntityState is EntityState.Modified or EntityState.Added currentEntry, currentOwnership.GetNavigation(pointsToPrincipal: false)!); - result.Insert(0, pathEntry); + result.Path.Insert(0, pathEntry); + } + + var modifiedMembers = entry.ToEntityEntry().Properties.Where(m => m.IsModified).ToList(); + if (modifiedMembers.Count == 1) + { + result.Property = modifiedMembers.Single().Metadata; + result.PropertyValue = entry.GetCurrentProviderValue(result.Property); + } + else + { + // only add to processed entries list if we are planning to update the entire entity + // (rather than just a single property on that entity) + processedEntries.Add(entry); } // parent entity got deleted, no need to do any json-specific processing @@ -629,36 +655,36 @@ entry.EntityState is EntityState.Modified or EntityState.Added return result; } - static List FindCommonJsonPartialUpdateInfo( - List first, - List second) + static JsonPartialUpdateInfo FindCommonJsonPartialUpdateInfo( + JsonPartialUpdateInfo first, + JsonPartialUpdateInfo second) { - var result = new List(); - for (var i = 0; i < Math.Min(first.Count, second.Count); i++) + var result = new JsonPartialUpdateInfo(); + for (var i = 0; i < Math.Min(first.Path.Count, second.Path.Count); i++) { - if (first[i].PropertyName == second[i].PropertyName) + if (first.Path[i].PropertyName == second.Path[i].PropertyName) { - if (first[i].Ordinal == second[i].Ordinal) + if (first.Path[i].Ordinal == second.Path[i].Ordinal) { - result.Add(first[i]); + result.Path.Add(first.Path[i]); continue; } else { var common = new JsonPartialUpdatePathEntry( - first[i].PropertyName, + first.Path[i].PropertyName, null, - first[i].ParentEntry, - first[i].Navigation); + first.Path[i].ParentEntry, + first.Path[i].Navigation); - result.Add(common); + result.Path.Add(common); } break; } } - Debug.Assert(result.Count > 0, "Common denominator should always have at least one node - the root."); + Debug.Assert(result.Path.Count > 0, "Common denominator should always have at least one node - the root."); return result; } diff --git a/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs b/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs index 960161c7e6a..63feb980ccd 100644 --- a/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs +++ b/src/EFCore.SqlServer/Update/Internal/SqlServerUpdateSqlGenerator.cs @@ -4,6 +4,7 @@ using System.Data; using System.Globalization; using System.Text; +using Microsoft.Extensions.Primitives; namespace Microsoft.EntityFrameworkCore.SqlServer.Update.Internal; @@ -152,9 +153,37 @@ protected override void AppendUpdateColumnValue( // using strict so that we don't remove json elements when they are assigned NULL value stringBuilder.Append(", 'strict "); stringBuilder.Append(columnModification.JsonPath); - stringBuilder.Append("', JSON_QUERY("); - base.AppendUpdateColumnValue(updateSqlGeneratorHelper, columnModification, stringBuilder, name, schema); - stringBuilder.Append("))"); + stringBuilder.Append("', "); + + if (columnModification.Property != null) + { + var needsTypeConversion = columnModification.Property.ClrType.IsNumeric() + || columnModification.Property.ClrType == typeof(bool); + + if (needsTypeConversion) + { + stringBuilder.Append("CAST("); + } + + stringBuilder.Append("JSON_VALUE("); + base.AppendUpdateColumnValue(updateSqlGeneratorHelper, columnModification, stringBuilder, name, schema); + stringBuilder.Append(", '$[0]')"); + + if (needsTypeConversion) + { + stringBuilder.Append(" AS "); + stringBuilder.Append(columnModification.Property.GetRelationalTypeMapping().StoreType); + stringBuilder.Append(")"); + } + } + else + { + stringBuilder.Append("JSON_QUERY("); + base.AppendUpdateColumnValue(updateSqlGeneratorHelper, columnModification, stringBuilder, name, schema); + stringBuilder.Append(")"); + } + + stringBuilder.Append(")"); } else { diff --git a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryFixtureBase.cs index a6e3719e4bb..d97451abc4f 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryFixtureBase.cs @@ -31,6 +31,7 @@ public virtual ISetSource GetExpectedData() { typeof(JsonEntitySingleOwned), e => ((JsonEntitySingleOwned)e)?.Id }, { typeof(JsonEntityInheritanceBase), e => ((JsonEntityInheritanceBase)e)?.Id }, { typeof(JsonEntityInheritanceDerived), e => ((JsonEntityInheritanceDerived)e)?.Id }, + { typeof(JsonEntityAllTypes), e => ((JsonEntityAllTypes)e)?.Id }, }.ToDictionary(e => e.Key, e => (object)e.Value); public IReadOnlyDictionary EntityAsserters { get; } = new Dictionary> @@ -240,6 +241,27 @@ public virtual ISetSource GetExpectedData() } } }, + { + typeof(JsonEntityAllTypes), (e, a) => + { + Assert.Equal(e == null, a == null); + if (a != null) + { + var ee = (JsonEntityAllTypes)e; + var aa = (JsonEntityAllTypes)a; + + Assert.Equal(ee.Id, aa.Id); + + AssertAllTypes(ee.Reference, aa.Reference); + + Assert.Equal(ee.Collection?.Count ?? 0, aa.Collection?.Count ?? 0); + for (var i = 0; i < ee.Collection.Count; i++) + { + AssertAllTypes(ee.Collection[i], aa.Collection[i]); + } + } + } + }, }.ToDictionary(e => e.Key, e => (object)e.Value); private static void AssertOwnedRoot(JsonOwnedRoot expected, JsonOwnedRoot actual) @@ -293,6 +315,25 @@ public static void AssertCustomNameBranch(JsonOwnedCustomNameBranch expected, Js Assert.Equal(expected.Fraction, actual.Fraction); } + public static void AssertAllTypes(JsonOwnedAllTypes expected, JsonOwnedAllTypes actual) + { + Assert.Equal(expected.TestBoolean, actual.TestBoolean); + Assert.Equal(expected.TestCharacter, actual.TestCharacter); + Assert.Equal(expected.TestDateTime, actual.TestDateTime); + Assert.Equal(expected.TestDateTimeOffset, actual.TestDateTimeOffset); + Assert.Equal(expected.TestDouble, actual.TestDouble); + Assert.Equal(expected.TestGuid, actual.TestGuid); + Assert.Equal(expected.TestInt16, actual.TestInt16); + Assert.Equal(expected.TestInt32, actual.TestInt32); + Assert.Equal(expected.TestInt64, actual.TestInt64); + Assert.Equal(expected.TestSignedByte, actual.TestSignedByte); + Assert.Equal(expected.TestSingle, actual.TestSingle); + Assert.Equal(expected.TestTimeSpan, actual.TestTimeSpan); + Assert.Equal(expected.TestUnsignedInt16, actual.TestUnsignedInt16); + Assert.Equal(expected.TestUnsignedInt32, actual.TestUnsignedInt32); + Assert.Equal(expected.TestUnsignedInt64, actual.TestUnsignedInt64); + } + protected override string StoreName { get; } = "JsonQueryTest"; public new RelationalTestStore TestStore @@ -420,5 +461,17 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con bb.Property(x => x.Fraction).HasPrecision(18, 2); }); }); + + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne(x => x.Reference, b => + { + b.ToJson(); + b.Property(x => x.TestDecimal).HasPrecision(18, 3); + }); + modelBuilder.Entity().OwnsMany(x => x.Collection, b => + { + b.ToJson(); + b.Property(x => x.TestDecimal).HasPrecision(18, 3); + }); } } diff --git a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs index 2794e360121..9a89f73d09b 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/JsonQueryTestBase.cs @@ -675,4 +675,38 @@ public virtual Task Json_with_include_on_entity_collection_and_reference(bool as new ExpectedInclude(x => x.EntityReference), new ExpectedInclude(x => x.EntityCollection)), entryCount: 44); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_all_types_entity_projection(bool async) + => AssertQuery( + async, + ss => ss.Set(), + entryCount: 3); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Json_all_types_projection_individual_properties(bool async) + => AssertQuery( + async, + ss => ss.Set().Select(x => new + { + x.Reference.TestBoolean, + x.Reference.TestByte, + x.Reference.TestCharacter, + x.Reference.TestDateTime, + x.Reference.TestDateTimeOffset, + x.Reference.TestDecimal, + x.Reference.TestDouble, + x.Reference.TestGuid, + x.Reference.TestInt16, + x.Reference.TestInt32, + x.Reference.TestInt64, + x.Reference.TestSignedByte, + x.Reference.TestSingle, + x.Reference.TestTimeSpan, + x.Reference.TestUnsignedInt16, + x.Reference.TestUnsignedInt32, + x.Reference.TestUnsignedInt64 + })); } diff --git a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityAllTypes.cs b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityAllTypes.cs new file mode 100644 index 00000000000..5aa5eefac16 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonEntityAllTypes.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.JsonQuery +{ + public class JsonEntityAllTypes + { + public int Id { get; set; } + public JsonOwnedAllTypes Reference { get; set; } + public List Collection { get; set; } + } +} diff --git a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonOwnedAllTypes.cs b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonOwnedAllTypes.cs new file mode 100644 index 00000000000..d54d567e6e5 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonOwnedAllTypes.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.JsonQuery +{ + public class JsonOwnedAllTypes + { + public short TestInt16 { get; set; } + public int TestInt32 { get; set; } + public long TestInt64 { get; set; } + public double TestDouble { get; set; } + public decimal TestDecimal { get; set; } + public DateTime TestDateTime { get; set; } + public DateTimeOffset TestDateTimeOffset { get; set; } + public TimeSpan TestTimeSpan { get; set; } + public float TestSingle { get; set; } + public bool TestBoolean { get; set; } + public byte TestByte { get; set; } + public Guid TestGuid { get; set; } + public ushort TestUnsignedInt16 { get; set; } + public uint TestUnsignedInt32 { get; set; } + public ulong TestUnsignedInt64 { get; set; } + public char TestCharacter { get; set; } + public sbyte TestSignedByte { get; set; } + } +} diff --git a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryContext.cs b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryContext.cs index 86fc815042c..b23e0c0e310 100644 --- a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryContext.cs +++ b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryContext.cs @@ -16,6 +16,7 @@ public JsonQueryContext(DbContextOptions options) public DbSet JsonEntitiesCustomNaming { get; set; } public DbSet JsonEntitiesSingleOwned { get; set; } public DbSet JsonEntitiesInheritance { get; set; } + public DbSet JsonEntitiesAllTypes { get; set; } public static void Seed(JsonQueryContext context) { @@ -27,6 +28,7 @@ public static void Seed(JsonQueryContext context) var jsonEntitiesCustomNaming = JsonQueryData.CreateJsonEntitiesCustomNaming(); var jsonEntitiesSingleOwned = JsonQueryData.CreateJsonEntitiesSingleOwned(); var jsonEntitiesInheritance = JsonQueryData.CreateJsonEntitiesInheritance(); + var jsonEntitiesAllTypes = JsonQueryData.CreateJsonEntitiesAllTypes(); context.JsonEntitiesBasic.AddRange(jsonEntitiesBasic); context.JsonEntitiesBasicForReference.AddRange(jsonEntitiesBasicForReference); @@ -34,6 +36,7 @@ public static void Seed(JsonQueryContext context) context.JsonEntitiesCustomNaming.AddRange(jsonEntitiesCustomNaming); context.JsonEntitiesSingleOwned.AddRange(jsonEntitiesSingleOwned); context.JsonEntitiesInheritance.AddRange(jsonEntitiesInheritance); + context.JsonEntitiesAllTypes.AddRange(jsonEntitiesAllTypes); context.SaveChanges(); } } diff --git a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryData.cs b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryData.cs index db835a3f29f..3e4215c4fee 100644 --- a/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryData.cs +++ b/test/EFCore.Relational.Specification.Tests/TestModels/JsonQuery/JsonQueryData.cs @@ -15,6 +15,7 @@ public JsonQueryData() JsonEntitiesCustomNaming = CreateJsonEntitiesCustomNaming(); JsonEntitiesSingleOwned = CreateJsonEntitiesSingleOwned(); JsonEntitiesInheritance = CreateJsonEntitiesInheritance(); + JsonEntitiesAllTypes = CreateJsonEntitiesAllTypes(); } public IReadOnlyList JsonEntitiesBasic { get; } @@ -23,6 +24,7 @@ public JsonQueryData() public IReadOnlyList JsonEntitiesCustomNaming { get; set; } public IReadOnlyList JsonEntitiesSingleOwned { get; set; } public IReadOnlyList JsonEntitiesInheritance { get; set; } + public IReadOnlyList JsonEntitiesAllTypes { get; set; } public static IReadOnlyList CreateJsonEntitiesBasic() { @@ -704,6 +706,56 @@ public static IReadOnlyList CreateJsonEntitiesInherit return new List { baseEntity, derivedEntity }; } + public static IReadOnlyList CreateJsonEntitiesAllTypes() + { + var r = new JsonOwnedAllTypes + { + TestInt16 = -1234, + TestInt32 = -123456789, + TestInt64 = -1234567890123456789L, + TestDouble = -1.23456789, + TestDecimal = -1234567890.01M, + TestDateTime = DateTime.Parse("01/01/2000 12:34:56"), + TestDateTimeOffset = new DateTimeOffset(DateTime.Parse("01/01/2000 12:34:56"), TimeSpan.FromHours(-8.0)), + TestTimeSpan = new TimeSpan(0, 10, 9, 8, 7), + TestSingle = -1.234F, + TestBoolean = true, + TestByte = 255, + TestGuid = new Guid("12345678-1234-4321-7777-987654321000"), + TestUnsignedInt16 = 1234, + TestUnsignedInt32 = 1234565789U, + TestUnsignedInt64 = 1234567890123456789UL, + TestCharacter = 'a', + TestSignedByte = -128, + }; + + var c = new JsonOwnedAllTypes + { + TestInt16 = -12, + TestInt32 = -12345, + TestInt64 = -1234567890L, + TestDouble = -1.2345, + TestDecimal = -123450.01M, + TestDateTime = DateTime.Parse("11/11/2100 12:34:56"), + TestDateTimeOffset = new DateTimeOffset(DateTime.Parse("11/11/2200 12:34:56"), TimeSpan.FromHours(-5.0)), + TestTimeSpan = new TimeSpan(0, 6, 5, 4, 3), + TestSingle = -1.4F, + TestBoolean = false, + TestByte = 25, + TestGuid = new Guid("00000000-0000-0000-0000-000000000000"), + TestUnsignedInt16 = 12, + TestUnsignedInt32 = 12345U, + TestUnsignedInt64 = 1234567867UL, + TestCharacter = 'h', + TestSignedByte = -18, + }; + + return new List + { + new JsonEntityAllTypes { Id = 1, Reference = r, Collection = new List { c } } + }; + } + public IQueryable Set() where TEntity : class { @@ -732,6 +784,11 @@ public IQueryable Set() return (IQueryable)JsonEntitiesInheritance.OfType().AsQueryable(); } + if (typeof(TEntity) == typeof(JsonEntityAllTypes)) + { + return (IQueryable)JsonEntitiesAllTypes.OfType().AsQueryable(); + } + throw new InvalidOperationException("Invalid entity type: " + typeof(TEntity)); } } diff --git a/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateFixtureBase.cs index 8d2a773a636..323eb2e57be 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateFixtureBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateFixtureBase.cs @@ -97,6 +97,18 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Ignore(); modelBuilder.Ignore(); + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsOne(x => x.Reference, b => + { + b.ToJson(); + b.Property(x => x.TestDecimal).HasPrecision(18, 3); + }); + modelBuilder.Entity().OwnsMany(x => x.Collection, b => + { + b.ToJson(); + b.Property(x => x.TestDecimal).HasPrecision(18, 3); + }); + base.OnModelCreating(modelBuilder, context); } @@ -104,9 +116,11 @@ protected override void Seed(JsonQueryContext context) { var jsonEntitiesBasic = JsonQueryData.CreateJsonEntitiesBasic(); var jsonEntitiesInheritance = JsonQueryData.CreateJsonEntitiesInheritance(); + var jsonEntitiesAllTypes = JsonQueryData.CreateJsonEntitiesAllTypes(); context.JsonEntitiesBasic.AddRange(jsonEntitiesBasic); context.JsonEntitiesInheritance.AddRange(jsonEntitiesInheritance); + context.JsonEntitiesAllTypes.AddRange(jsonEntitiesAllTypes); context.SaveChanges(); } } diff --git a/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateTestBase.cs index 50ca05af8aa..1af7ea7db8c 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateTestBase.cs @@ -605,6 +605,541 @@ public virtual Task Edit_collection_element_and_reference_at_once() Assert.Equal("edit2", result.OwnedReferenceRoot.OwnedCollectionBranch[1].OwnedReferenceLeaf.SomethingSomething); }); + [ConditionalFact] + public virtual Task Edit_single_enum_property() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesBasic.ToListAsync(); + var entity = query.Single(); + entity.OwnedReferenceRoot.OwnedReferenceBranch.Enum = JsonEnum.Two; + entity.OwnedCollectionRoot[1].OwnedCollectionBranch[1].Enum = JsonEnum.Two; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(JsonEnum.Two, result.OwnedReferenceRoot.OwnedReferenceBranch.Enum); + Assert.Equal(JsonEnum.Two, result.OwnedCollectionRoot[1].OwnedCollectionBranch[1].Enum); + }); + + [ConditionalFact] + public virtual Task Edit_single_numeric_property() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesBasic.ToListAsync(); + var entity = query.Single(); + entity.OwnedReferenceRoot.Number = 999; + entity.OwnedCollectionRoot[1].Number = 1024; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(999, result.OwnedReferenceRoot.Number); + Assert.Equal(1024, result.OwnedCollectionRoot[1].Number); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_bool() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestBoolean = false; + entity.Collection[0].TestBoolean = true; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(false, result.Reference.TestBoolean); + Assert.Equal(true, result.Collection[0].TestBoolean); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_byte() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestByte = 25; + entity.Collection[0].TestByte = 14; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(25, result.Reference.TestByte); + Assert.Equal(14, result.Collection[0].TestByte); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_char() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestCharacter = 't'; + entity.Collection[0].TestCharacter = 'h'; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal('t', result.Reference.TestCharacter); + Assert.Equal('h', result.Collection[0].TestCharacter); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_datetime() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestDateTime = DateTime.Parse("01/01/3000 12:34:56"); + entity.Collection[0].TestDateTime = DateTime.Parse("01/01/3000 12:34:56"); + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(DateTime.Parse("01/01/3000 12:34:56"), result.Reference.TestDateTime); + Assert.Equal(DateTime.Parse("01/01/3000 12:34:56"), result.Collection[0].TestDateTime); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_datetimeoffset() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestDateTimeOffset = new DateTimeOffset(DateTime.Parse("01/01/3000 12:34:56"), TimeSpan.FromHours(-4.0)); + entity.Collection[0].TestDateTimeOffset = new DateTimeOffset(DateTime.Parse("01/01/3000 12:34:56"), TimeSpan.FromHours(-4.0)); + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(new DateTimeOffset(DateTime.Parse("01/01/3000 12:34:56"), TimeSpan.FromHours(-4.0)), result.Reference.TestDateTimeOffset); + Assert.Equal(new DateTimeOffset(DateTime.Parse("01/01/3000 12:34:56"), TimeSpan.FromHours(-4.0)), result.Collection[0].TestDateTimeOffset); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_decimal() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestDecimal = -13579.01M; + entity.Collection[0].TestDecimal = -13579.01M; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(-13579.01M, result.Reference.TestDecimal); + Assert.Equal(-13579.01M, result.Collection[0].TestDecimal); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_double() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestDouble = -1.23579; + entity.Collection[0].TestDouble = -1.23579; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(-1.23579, result.Reference.TestDouble); + Assert.Equal(-1.23579, result.Collection[0].TestDouble); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_guid() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestGuid = new Guid("12345678-1234-4321-5555-987654321000"); + entity.Collection[0].TestGuid = new Guid("12345678-1234-4321-5555-987654321000"); + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(new Guid("12345678-1234-4321-5555-987654321000"), result.Reference.TestGuid); + Assert.Equal(new Guid("12345678-1234-4321-5555-987654321000"), result.Collection[0].TestGuid); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_int16() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestInt16 = -3234; + entity.Collection[0].TestInt16 = -3234; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(-3234, result.Reference.TestInt16); + Assert.Equal(-3234, result.Collection[0].TestInt16); + }); + + + [ConditionalFact] + public virtual Task Edit_single_property_int32() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestInt32 = -3234; + entity.Collection[0].TestInt32 = -3234; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(-3234, result.Reference.TestInt32); + Assert.Equal(-3234, result.Collection[0].TestInt32); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_int64() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestInt64 = -3234; + entity.Collection[0].TestInt64 = -3234; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(-3234, result.Reference.TestInt64); + Assert.Equal(-3234, result.Collection[0].TestInt64); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_signed_byte() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestSignedByte = -108; + entity.Collection[0].TestSignedByte = -108; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(-108, result.Reference.TestSignedByte); + Assert.Equal(-108, result.Collection[0].TestSignedByte); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_single() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestSingle = -7.234F; + entity.Collection[0].TestSingle = -7.234F; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(-7.234F, result.Reference.TestSingle); + Assert.Equal(-7.234F, result.Collection[0].TestSingle); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_timespan() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestTimeSpan = new TimeSpan(0, 10, 1, 1, 7); + entity.Collection[0].TestTimeSpan = new TimeSpan(0, 10, 1, 1, 7); + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(new TimeSpan(0, 10, 1, 1, 7), result.Reference.TestTimeSpan); + Assert.Equal(new TimeSpan(0, 10, 1, 1, 7), result.Collection[0].TestTimeSpan); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_uint16() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestUnsignedInt16 = 1534; + entity.Collection[0].TestUnsignedInt16 = 1534; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(1534, result.Reference.TestUnsignedInt16); + Assert.Equal(1534, result.Collection[0].TestUnsignedInt16); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_uint32() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestUnsignedInt32 = 1237775789U; + entity.Collection[0].TestUnsignedInt32 = 1237775789U; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(1237775789U, result.Reference.TestUnsignedInt32); + Assert.Equal(1237775789U, result.Collection[0].TestUnsignedInt32); + }); + + [ConditionalFact] + public virtual Task Edit_single_property_uint64() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestUnsignedInt64 = 1234555555123456789UL; + entity.Collection[0].TestUnsignedInt64 = 1234555555123456789UL; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(1234555555123456789UL, result.Reference.TestUnsignedInt64); + Assert.Equal(1234555555123456789UL, result.Collection[0].TestUnsignedInt64); + }); + + [ConditionalFact] + public virtual Task Edit_two_properties_on_same_entity_updates_the_entire_entity() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesAllTypes.ToListAsync(); + var entity = query.Single(); + entity.Reference.TestInt32 = 32; + entity.Reference.TestInt64 = 64; + entity.Collection[0].TestInt32 = 32; + entity.Collection[0].TestInt64 = 64; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(32, result.Reference.TestInt32); + Assert.Equal(64, result.Reference.TestInt64); + Assert.Equal(32, result.Collection[0].TestInt32); + Assert.Equal(64, result.Collection[0].TestInt64); + }); + + [ConditionalFact] + public virtual Task Edit_a_scalar_property_and_reference_navigation_on_the_same_entity() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesBasic.ToListAsync(); + var entity = query.Single(); + entity.OwnedReferenceRoot.OwnedReferenceBranch.Fraction = 123.532M; + entity.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf = null; + await context.SaveChangesAsync(); + }, + + async context => + { + var query = await context.JsonEntitiesBasic.ToListAsync(); + var entity = query.Single(); + entity.OwnedReferenceRoot.OwnedReferenceBranch.Fraction = 523.532M; + entity.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf = new JsonOwnedLeaf { SomethingSomething = "edit" }; + + ClearLog(); + await context.SaveChangesAsync(); + }, + + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(523.532M, result.OwnedReferenceRoot.OwnedReferenceBranch.Fraction); + Assert.Equal("edit", result.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething); + }); + + [ConditionalFact] + public virtual Task Edit_a_scalar_property_and_collection_navigation_on_the_same_entity() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesBasic.ToListAsync(); + var entity = query.Single(); + entity.OwnedReferenceRoot.OwnedReferenceBranch.Fraction = 123.532M; + entity.OwnedReferenceRoot.OwnedReferenceBranch.OwnedCollectionLeaf = null; + await context.SaveChangesAsync(); + }, + + async context => + { + var query = await context.JsonEntitiesBasic.ToListAsync(); + var entity = query.Single(); + entity.OwnedReferenceRoot.OwnedReferenceBranch.Fraction = 523.532M; + entity.OwnedReferenceRoot.OwnedReferenceBranch.OwnedCollectionLeaf = new List + { + new JsonOwnedLeaf { SomethingSomething = "edit" } + }; + + ClearLog(); + await context.SaveChangesAsync(); + }, + + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(523.532M, result.OwnedReferenceRoot.OwnedReferenceBranch.Fraction); + Assert.Equal("edit", result.OwnedReferenceRoot.OwnedReferenceBranch.OwnedCollectionLeaf[0].SomethingSomething); + }); + + [ConditionalFact] + public virtual Task Edit_a_scalar_property_and_another_property_behind_reference_navigation_on_the_same_entity() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesBasic.ToListAsync(); + var entity = query.Single(); + entity.OwnedReferenceRoot.OwnedReferenceBranch.Fraction = 523.532M; + entity.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething = "edit"; + + ClearLog(); + await context.SaveChangesAsync(); + }, + + async context => + { + var result = await context.Set().SingleAsync(); + Assert.Equal(523.532M, result.OwnedReferenceRoot.OwnedReferenceBranch.Fraction); + Assert.Equal("edit", result.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething); + }); + public void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs index c1653415489..54a09c29c37 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/JsonQuerySqlServerTest.cs @@ -595,6 +595,24 @@ FROM [JsonEntitiesBasic] AS [j] ORDER BY [j].[Id], [j0].[Id]"); } + public override async Task Json_all_types_entity_projection(bool async) + { + await base.Json_all_types_entity_projection(async); + + AssertSql( + @"SELECT [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Json_all_types_projection_individual_properties(bool async) + { + await base.Json_all_types_projection_individual_properties(async); + + AssertSql( + @"SELECT CAST(JSON_VALUE([j].[Reference],'$.TestBoolean') AS bit) AS [TestBoolean], CAST(JSON_VALUE([j].[Reference],'$.TestByte') AS tinyint) AS [TestByte], CAST(JSON_VALUE([j].[Reference],'$.TestCharacter') AS nvarchar(1)) AS [TestCharacter], CAST(JSON_VALUE([j].[Reference],'$.TestDateTime') AS datetime2) AS [TestDateTime], CAST(JSON_VALUE([j].[Reference],'$.TestDateTimeOffset') AS datetimeoffset) AS [TestDateTimeOffset], CAST(JSON_VALUE([j].[Reference],'$.TestDecimal') AS decimal(18,3)) AS [TestDecimal], CAST(JSON_VALUE([j].[Reference],'$.TestDouble') AS float) AS [TestDouble], CAST(JSON_VALUE([j].[Reference],'$.TestGuid') AS uniqueidentifier) AS [TestGuid], CAST(JSON_VALUE([j].[Reference],'$.TestInt16') AS smallint) AS [TestInt16], CAST(JSON_VALUE([j].[Reference],'$.TestInt32') AS int) AS [TestInt32], CAST(JSON_VALUE([j].[Reference],'$.TestInt64') AS bigint) AS [TestInt64], CAST(JSON_VALUE([j].[Reference],'$.TestSignedByte') AS smallint) AS [TestSignedByte], CAST(JSON_VALUE([j].[Reference],'$.TestSingle') AS real) AS [TestSingle], CAST(JSON_VALUE([j].[Reference],'$.TestTimeSpan') AS time) AS [TestTimeSpan], CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt16') AS int) AS [TestUnsignedInt16], CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt32') AS bigint) AS [TestUnsignedInt32], CAST(JSON_VALUE([j].[Reference],'$.TestUnsignedInt64') AS decimal(20,0)) AS [TestUnsignedInt64] +FROM [JsonEntitiesAllTypes] AS [j]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateSqlServerTest.cs index 4cb48b4afda..0d931d7c2c7 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateSqlServerTest.cs @@ -232,12 +232,12 @@ public override async Task Edit_element_in_json_collection_branch() await base.Edit_element_in_json_collection_branch(); AssertSql( - @"@p0='{""Date"":""2111-11-11T00:00:00"",""Enum"":""Two"",""Fraction"":11.1,""OwnedCollectionLeaf"":[{""SomethingSomething"":""e1_c1_c1_c1""},{""SomethingSomething"":""e1_c1_c1_c2""}],""OwnedReferenceLeaf"":{""SomethingSomething"":""e1_c1_c1_r""}}' (Nullable = false) (Size = 214) + @"@p0='[""2111-11-11T00:00:00""]' (Nullable = false) (Size = 23) @p1='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [JsonEntitiesBasic] SET [OwnedCollectionRoot] = JSON_MODIFY([OwnedCollectionRoot], 'strict $[0].OwnedCollectionBranch[0]', JSON_QUERY(@p0)) +UPDATE [JsonEntitiesBasic] SET [OwnedCollectionRoot] = JSON_MODIFY([OwnedCollectionRoot], 'strict $[0].OwnedCollectionBranch[0].Date', JSON_VALUE(@p0, '$[0]')) OUTPUT 1 WHERE [Id] = @p1;", // @@ -250,12 +250,12 @@ public override async Task Edit_element_in_json_collection_root1() await base.Edit_element_in_json_collection_root1(); AssertSql( - @"@p0='{""Name"":""Modified"",""Number"":11,""OwnedCollectionBranch"":[{""Date"":""2111-01-01T00:00:00"",""Enum"":""Two"",""Fraction"":11.1,""OwnedCollectionLeaf"":[{""SomethingSomething"":""e1_c1_c1_c1""},{""SomethingSomething"":""e1_c1_c1_c2""}],""OwnedReferenceLeaf"":{""SomethingSomething"":""e1_c1_c1_r""}},{""Date"":""2112-01-01T00:00:00"",""Enum"":""Three"",""Fraction"":11.2,""OwnedCollectionLeaf"":[{""SomethingSomething"":""e1_c1_c2_c1""},{""SomethingSomething"":""e1_c1_c2_c2""}],""OwnedReferenceLeaf"":{""SomethingSomething"":""e1_c1_c2_r""}}],""OwnedReferenceBranch"":{""Date"":""2110-01-01T00:00:00"",""Enum"":""One"",""Fraction"":11.0,""OwnedCollectionLeaf"":[{""SomethingSomething"":""e1_c1_r_c1""},{""SomethingSomething"":""e1_c1_r_c2""}],""OwnedReferenceLeaf"":{""SomethingSomething"":""e1_c1_r_r""}}}' (Nullable = false) (Size = 724) + @"@p0='[""Modified""]' (Nullable = false) (Size = 12) @p1='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [JsonEntitiesBasic] SET [OwnedCollectionRoot] = JSON_MODIFY([OwnedCollectionRoot], 'strict $[0]', JSON_QUERY(@p0)) +UPDATE [JsonEntitiesBasic] SET [OwnedCollectionRoot] = JSON_MODIFY([OwnedCollectionRoot], 'strict $[0].Name', JSON_VALUE(@p0, '$[0]')) OUTPUT 1 WHERE [Id] = @p1;", // @@ -268,12 +268,12 @@ public override async Task Edit_element_in_json_collection_root2() await base.Edit_element_in_json_collection_root2(); AssertSql( - @"@p0='{""Name"":""Modified"",""Number"":12,""OwnedCollectionBranch"":[{""Date"":""2121-01-01T00:00:00"",""Enum"":""Two"",""Fraction"":12.1,""OwnedCollectionLeaf"":[{""SomethingSomething"":""e1_c2_c1_c1""},{""SomethingSomething"":""e1_c2_c1_c2""}],""OwnedReferenceLeaf"":{""SomethingSomething"":""e1_c2_c1_r""}},{""Date"":""2122-01-01T00:00:00"",""Enum"":""One"",""Fraction"":12.2,""OwnedCollectionLeaf"":[{""SomethingSomething"":""e1_c2_c2_c1""},{""SomethingSomething"":""e1_c2_c2_c2""}],""OwnedReferenceLeaf"":{""SomethingSomething"":""e1_c2_c2_r""}}],""OwnedReferenceBranch"":{""Date"":""2120-01-01T00:00:00"",""Enum"":""Three"",""Fraction"":12.0,""OwnedCollectionLeaf"":[{""SomethingSomething"":""e1_c2_r_c1""},{""SomethingSomething"":""e1_c2_r_c2""}],""OwnedReferenceLeaf"":{""SomethingSomething"":""e1_c2_r_r""}}}' (Nullable = false) (Size = 724) + @"@p0='[""Modified""]' (Nullable = false) (Size = 12) @p1='1' SET IMPLICIT_TRANSACTIONS OFF; SET NOCOUNT ON; -UPDATE [JsonEntitiesBasic] SET [OwnedCollectionRoot] = JSON_MODIFY([OwnedCollectionRoot], 'strict $[1]', JSON_QUERY(@p0)) +UPDATE [JsonEntitiesBasic] SET [OwnedCollectionRoot] = JSON_MODIFY([OwnedCollectionRoot], 'strict $[1].Name', JSON_VALUE(@p0, '$[0]')) OUTPUT 1 WHERE [Id] = @p1;", // @@ -372,6 +372,439 @@ OUTPUT 1 FROM [JsonEntitiesBasic] AS [j]"); } + public override async Task Edit_single_enum_property() + { + await base.Edit_single_enum_property(); + + AssertSql( + @"@p0='[""Two""]' (Nullable = false) (Size = 7) +@p1='[""Two""]' (Nullable = false) (Size = 7) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesBasic] SET [OwnedCollectionRoot] = JSON_MODIFY([OwnedCollectionRoot], 'strict $[1].OwnedCollectionBranch[1].Enum', JSON_VALUE(@p0, '$[0]')), [OwnedReferenceRoot] = JSON_MODIFY([OwnedReferenceRoot], 'strict $.OwnedReferenceBranch.Enum', JSON_VALUE(@p1, '$[0]')) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], [j].[Name], JSON_QUERY([j].[OwnedCollectionRoot],'$'), JSON_QUERY([j].[OwnedReferenceRoot],'$') +FROM [JsonEntitiesBasic] AS [j]"); + } + + public override async Task Edit_single_numeric_property() + { + await base.Edit_single_numeric_property(); + + AssertSql( + @"@p0='[1024]' (Nullable = false) (Size = 6) +@p1='[999]' (Nullable = false) (Size = 5) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesBasic] SET [OwnedCollectionRoot] = JSON_MODIFY([OwnedCollectionRoot], 'strict $[1].Number', CAST(JSON_VALUE(@p0, '$[0]') AS int)), [OwnedReferenceRoot] = JSON_MODIFY([OwnedReferenceRoot], 'strict $.Number', CAST(JSON_VALUE(@p1, '$[0]') AS int)) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], [j].[Name], JSON_QUERY([j].[OwnedCollectionRoot],'$'), JSON_QUERY([j].[OwnedReferenceRoot],'$') +FROM [JsonEntitiesBasic] AS [j]"); + } + + public override async Task Edit_single_property_bool() + { + await base.Edit_single_property_bool(); + + AssertSql( + @"@p0='[true]' (Nullable = false) (Size = 6) +@p1='[false]' (Nullable = false) (Size = 7) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestBoolean', CAST(JSON_VALUE(@p0, '$[0]') AS bit)), [Reference] = JSON_MODIFY([Reference], 'strict $.TestBoolean', CAST(JSON_VALUE(@p1, '$[0]') AS bit)) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_byte() + { + await base.Edit_single_property_byte(); + + AssertSql( + @"@p0='[14]' (Nullable = false) (Size = 4) +@p1='[25]' (Nullable = false) (Size = 4) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestByte', CAST(JSON_VALUE(@p0, '$[0]') AS tinyint)), [Reference] = JSON_MODIFY([Reference], 'strict $.TestByte', CAST(JSON_VALUE(@p1, '$[0]') AS tinyint)) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_char() + { + await base.Edit_single_property_char(); + + AssertSql( + @"@p0='[""t""]' (Nullable = false) (Size = 5) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Reference] = JSON_MODIFY([Reference], 'strict $.TestCharacter', CAST(JSON_VALUE(@p0, '$[0]') AS nvarchar(1))) +OUTPUT 1 +WHERE [Id] = @p1;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_datetime() + { + await base.Edit_single_property_datetime(); + + AssertSql( + @"@p0='[""3000-01-01T12:34:56""]' (Nullable = false) (Size = 23) +@p1='[""3000-01-01T12:34:56""]' (Nullable = false) (Size = 23) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestDateTime', JSON_VALUE(@p0, '$[0]')), [Reference] = JSON_MODIFY([Reference], 'strict $.TestDateTime', JSON_VALUE(@p1, '$[0]')) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_datetimeoffset() + { + await base.Edit_single_property_datetimeoffset(); + + AssertSql( + @"@p0='[""3000-01-01T12:34:56-04:00""]' (Nullable = false) (Size = 29) +@p1='[""3000-01-01T12:34:56-04:00""]' (Nullable = false) (Size = 29) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestDateTimeOffset', JSON_VALUE(@p0, '$[0]')), [Reference] = JSON_MODIFY([Reference], 'strict $.TestDateTimeOffset', JSON_VALUE(@p1, '$[0]')) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_decimal() + { + await base.Edit_single_property_decimal(); + + AssertSql( + @"@p0='[-13579.01]' (Nullable = false) (Size = 11) +@p1='[-13579.01]' (Nullable = false) (Size = 11) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestDecimal', CAST(JSON_VALUE(@p0, '$[0]') AS decimal(18,3))), [Reference] = JSON_MODIFY([Reference], 'strict $.TestDecimal', CAST(JSON_VALUE(@p1, '$[0]') AS decimal(18,3))) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_double() + { + await base.Edit_single_property_double(); + + AssertSql( + @"@p0='[-1.23579]' (Nullable = false) (Size = 10) +@p1='[-1.23579]' (Nullable = false) (Size = 10) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestDouble', CAST(JSON_VALUE(@p0, '$[0]') AS float)), [Reference] = JSON_MODIFY([Reference], 'strict $.TestDouble', CAST(JSON_VALUE(@p1, '$[0]') AS float)) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_guid() + { + await base.Edit_single_property_guid(); + + AssertSql( + @"@p0='[""12345678-1234-4321-5555-987654321000""]' (Nullable = false) (Size = 40) +@p1='[""12345678-1234-4321-5555-987654321000""]' (Nullable = false) (Size = 40) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestGuid', JSON_VALUE(@p0, '$[0]')), [Reference] = JSON_MODIFY([Reference], 'strict $.TestGuid', JSON_VALUE(@p1, '$[0]')) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_int16() + { + await base.Edit_single_property_int16(); + + AssertSql( + @"@p0='[-3234]' (Nullable = false) (Size = 7) +@p1='[-3234]' (Nullable = false) (Size = 7) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestInt16', CAST(JSON_VALUE(@p0, '$[0]') AS smallint)), [Reference] = JSON_MODIFY([Reference], 'strict $.TestInt16', CAST(JSON_VALUE(@p1, '$[0]') AS smallint)) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_int32() + { + await base.Edit_single_property_int32(); + + AssertSql( + @"@p0='[-3234]' (Nullable = false) (Size = 7) +@p1='[-3234]' (Nullable = false) (Size = 7) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestInt32', CAST(JSON_VALUE(@p0, '$[0]') AS int)), [Reference] = JSON_MODIFY([Reference], 'strict $.TestInt32', CAST(JSON_VALUE(@p1, '$[0]') AS int)) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_int64() + { + await base.Edit_single_property_int64(); + + AssertSql( + @"@p0='[-3234]' (Nullable = false) (Size = 7) +@p1='[-3234]' (Nullable = false) (Size = 7) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestInt64', CAST(JSON_VALUE(@p0, '$[0]') AS bigint)), [Reference] = JSON_MODIFY([Reference], 'strict $.TestInt64', CAST(JSON_VALUE(@p1, '$[0]') AS bigint)) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_signed_byte() + { + await base.Edit_single_property_signed_byte(); + + AssertSql( + @"@p0='[-108]' (Nullable = false) (Size = 6) +@p1='[-108]' (Nullable = false) (Size = 6) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestSignedByte', CAST(JSON_VALUE(@p0, '$[0]') AS smallint)), [Reference] = JSON_MODIFY([Reference], 'strict $.TestSignedByte', CAST(JSON_VALUE(@p1, '$[0]') AS smallint)) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_single() + { + await base.Edit_single_property_single(); + + AssertSql( + @"@p0='[-7.234]' (Nullable = false) (Size = 8) +@p1='[-7.234]' (Nullable = false) (Size = 8) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestSingle', CAST(JSON_VALUE(@p0, '$[0]') AS real)), [Reference] = JSON_MODIFY([Reference], 'strict $.TestSingle', CAST(JSON_VALUE(@p1, '$[0]') AS real)) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_timespan() + { + await base.Edit_single_property_timespan(); + + AssertSql( + @"@p0='[""10:01:01.0070000""]' (Nullable = false) (Size = 20) +@p1='[""10:01:01.0070000""]' (Nullable = false) (Size = 20) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestTimeSpan', JSON_VALUE(@p0, '$[0]')), [Reference] = JSON_MODIFY([Reference], 'strict $.TestTimeSpan', JSON_VALUE(@p1, '$[0]')) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_uint16() + { + await base.Edit_single_property_uint16(); + + AssertSql( + @"@p0='[1534]' (Nullable = false) (Size = 6) +@p1='[1534]' (Nullable = false) (Size = 6) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestUnsignedInt16', CAST(JSON_VALUE(@p0, '$[0]') AS int)), [Reference] = JSON_MODIFY([Reference], 'strict $.TestUnsignedInt16', CAST(JSON_VALUE(@p1, '$[0]') AS int)) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_uint32() + { + await base.Edit_single_property_uint32(); + + AssertSql( + @"@p0='[1237775789]' (Nullable = false) (Size = 12) +@p1='[1237775789]' (Nullable = false) (Size = 12) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestUnsignedInt32', CAST(JSON_VALUE(@p0, '$[0]') AS bigint)), [Reference] = JSON_MODIFY([Reference], 'strict $.TestUnsignedInt32', CAST(JSON_VALUE(@p1, '$[0]') AS bigint)) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_single_property_uint64() + { + await base.Edit_single_property_uint64(); + + AssertSql( + @"@p0='[1234555555123456789]' (Nullable = false) (Size = 21) +@p1='[1234555555123456789]' (Nullable = false) (Size = 21) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestUnsignedInt64', CAST(JSON_VALUE(@p0, '$[0]') AS decimal(20,0))), [Reference] = JSON_MODIFY([Reference], 'strict $.TestUnsignedInt64', CAST(JSON_VALUE(@p1, '$[0]') AS decimal(20,0))) +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_two_properties_on_same_entity_updates_the_entire_entity() + { + await base.Edit_two_properties_on_same_entity_updates_the_entire_entity(); + + AssertSql( + @"@p0='{""TestBoolean"":false,""TestByte"":25,""TestCharacter"":""h"",""TestDateTime"":""2100-11-11T12:34:56"",""TestDateTimeOffset"":""2200-11-11T12:34:56-05:00"",""TestDecimal"":-123450.01,""TestDouble"":-1.2345,""TestGuid"":""00000000-0000-0000-0000-000000000000"",""TestInt16"":-12,""TestInt32"":32,""TestInt64"":64,""TestSignedByte"":-18,""TestSingle"":-1.4,""TestTimeSpan"":""06:05:04.0030000"",""TestUnsignedInt16"":12,""TestUnsignedInt32"":12345,""TestUnsignedInt64"":1234567867}' (Nullable = false) (Size = 436) +@p1='{""TestBoolean"":true,""TestByte"":255,""TestCharacter"":""a"",""TestDateTime"":""2000-01-01T12:34:56"",""TestDateTimeOffset"":""2000-01-01T12:34:56-08:00"",""TestDecimal"":-1234567890.01,""TestDouble"":-1.23456789,""TestGuid"":""12345678-1234-4321-7777-987654321000"",""TestInt16"":-1234,""TestInt32"":32,""TestInt64"":64,""TestSignedByte"":-128,""TestSingle"":-1.234,""TestTimeSpan"":""10:09:08.0070000"",""TestUnsignedInt16"":1234,""TestUnsignedInt32"":1234565789,""TestUnsignedInt64"":1234567890123456789}' (Nullable = false) (Size = 465) +@p2='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0]', JSON_QUERY(@p0)), [Reference] = @p1 +OUTPUT 1 +WHERE [Id] = @p2;", + // + @"SELECT TOP(2) [j].[Id], JSON_QUERY([j].[Collection],'$'), JSON_QUERY([j].[Reference],'$') +FROM [JsonEntitiesAllTypes] AS [j]"); + } + + public override async Task Edit_a_scalar_property_and_reference_navigation_on_the_same_entity() + { + await base.Edit_a_scalar_property_and_reference_navigation_on_the_same_entity(); + + AssertSql( + @"@p0='{""Date"":""2100-01-01T00:00:00"",""Enum"":""One"",""Fraction"":523.532,""OwnedCollectionLeaf"":[{""SomethingSomething"":""e1_r_r_c1""},{""SomethingSomething"":""e1_r_r_c2""}],""OwnedReferenceLeaf"":{""SomethingSomething"":""edit""}}' (Nullable = false) (Size = 207) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesBasic] SET [OwnedReferenceRoot] = JSON_MODIFY([OwnedReferenceRoot], 'strict $.OwnedReferenceBranch', JSON_QUERY(@p0)) +OUTPUT 1 +WHERE [Id] = @p1;", + // + @"SELECT TOP(2) [j].[Id], [j].[Name], JSON_QUERY([j].[OwnedCollectionRoot],'$'), JSON_QUERY([j].[OwnedReferenceRoot],'$') +FROM [JsonEntitiesBasic] AS [j]"); + } + + public override async Task Edit_a_scalar_property_and_collection_navigation_on_the_same_entity() + { + await base.Edit_a_scalar_property_and_collection_navigation_on_the_same_entity(); + + AssertSql( + @"@p0='{""Date"":""2100-01-01T00:00:00"",""Enum"":""One"",""Fraction"":523.532,""OwnedCollectionLeaf"":[{""SomethingSomething"":""edit""}],""OwnedReferenceLeaf"":{""SomethingSomething"":""e1_r_r_r""}}' (Nullable = false) (Size = 171) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesBasic] SET [OwnedReferenceRoot] = JSON_MODIFY([OwnedReferenceRoot], 'strict $.OwnedReferenceBranch', JSON_QUERY(@p0)) +OUTPUT 1 +WHERE [Id] = @p1;", + // + @"SELECT TOP(2) [j].[Id], [j].[Name], JSON_QUERY([j].[OwnedCollectionRoot],'$'), JSON_QUERY([j].[OwnedReferenceRoot],'$') +FROM [JsonEntitiesBasic] AS [j]"); + } + + public override async Task Edit_a_scalar_property_and_another_property_behind_reference_navigation_on_the_same_entity() + { + await base.Edit_a_scalar_property_and_another_property_behind_reference_navigation_on_the_same_entity(); + + AssertSql( + @"@p0='{""Date"":""2100-01-01T00:00:00"",""Enum"":""One"",""Fraction"":523.532,""OwnedCollectionLeaf"":[{""SomethingSomething"":""e1_r_r_c1""},{""SomethingSomething"":""e1_r_r_c2""}],""OwnedReferenceLeaf"":{""SomethingSomething"":""edit""}}' (Nullable = false) (Size = 207) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesBasic] SET [OwnedReferenceRoot] = JSON_MODIFY([OwnedReferenceRoot], 'strict $.OwnedReferenceBranch', JSON_QUERY(@p0)) +OUTPUT 1 +WHERE [Id] = @p1;", + // + @"SELECT TOP(2) [j].[Id], [j].[Name], JSON_QUERY([j].[OwnedCollectionRoot],'$'), JSON_QUERY([j].[OwnedReferenceRoot],'$') +FROM [JsonEntitiesBasic] AS [j]"); + } + protected override void ClearLog() => Fixture.TestSqlLoggerFactory.Clear(); private void AssertSql(params string[] expected)