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)