diff --git a/src/EFCore/Storage/Json/JsonStringReaderWriter.cs b/src/EFCore/Storage/Json/JsonStringReaderWriter.cs index 471cb8dd351..e954bc780e4 100644 --- a/src/EFCore/Storage/Json/JsonStringReaderWriter.cs +++ b/src/EFCore/Storage/Json/JsonStringReaderWriter.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.Encodings.Web; using System.Text.Json; namespace Microsoft.EntityFrameworkCore.Storage.Json; @@ -12,6 +13,9 @@ public sealed class JsonStringReaderWriter : JsonValueReaderWriter { private static readonly PropertyInfo InstanceProperty = typeof(JsonStringReaderWriter).GetProperty(nameof(Instance))!; + private static readonly bool UseOldBehavior32152 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32152", out var enabled32152) && enabled32152; + /// /// The singleton instance of this stateless reader/writer. /// @@ -27,7 +31,16 @@ public override string FromJsonTyped(ref Utf8JsonReaderManager manager, object? /// public override void ToJsonTyped(Utf8JsonWriter writer, string value) - => writer.WriteStringValue(value); + { + if (UseOldBehavior32152) + { + writer.WriteStringValue(value); + } + else + { + writer.WriteStringValue(JsonEncodedText.Encode(value, JavaScriptEncoder.UnsafeRelaxedJsonEscaping)); + } + } /// public override Expression ConstructorExpression diff --git a/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateTestBase.cs b/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateTestBase.cs index 90f5e03f5d4..d36a773550b 100644 --- a/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Update/JsonUpdateTestBase.cs @@ -3633,6 +3633,40 @@ public virtual Task Add_and_update_nested_optional_primitive_collection(bool? va } }); + [ConditionalFact] + public virtual Task Edit_single_property_with_chinese_characters() + => TestHelpers.ExecuteWithStrategyInTransactionAsync( + CreateContext, + UseTransaction, + async context => + { + var query = await context.JsonEntitiesBasic.ToListAsync(); + var entity = query.Single(x => x.Id == 1); + entity.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething = "测试1"; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(x => x.Id == 1); + Assert.Equal("测试1", result.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething); + }, + async context => + { + var query = await context.JsonEntitiesBasic.ToListAsync(); + var entity = query.Single(x => x.Id == 1); + entity.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething = "测试2"; + + ClearLog(); + await context.SaveChangesAsync(); + }, + async context => + { + var result = await context.Set().SingleAsync(x => x.Id == 1); + Assert.Equal("测试2", result.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething); + }); + public void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateSqlServerTest.cs index 42fee62b579..e8394557470 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateSqlServerTest.cs @@ -2535,6 +2535,46 @@ public override Task Edit_single_property_collection_of_collection_of_nullable_i public override Task Edit_single_property_collection_of_collection_of_single() => Assert.ThrowsAsync(base.Edit_single_property_collection_of_collection_of_single); + public override async Task Edit_single_property_with_chinese_characters() + { + await base.Edit_single_property_with_chinese_characters(); + + AssertSql( + """ +@p0='{"Name":"Root","Number":7,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2101-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":10.1,"Id":10,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"测试1"}}}' (Nullable = false) (Size = 386) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesBasic] SET [OwnedReferenceRoot] = @p0 +OUTPUT 1 +WHERE [Id] = @p1; +""", + // + """ +SELECT TOP(2) [j].[Id], [j].[EntityBasicId], [j].[Name], [j].[OwnedCollectionRoot], [j].[OwnedReferenceRoot] +FROM [JsonEntitiesBasic] AS [j] +WHERE [j].[Id] = 1 +""", + // + """ +@p0='{"Name":"Root","Number":7,"OwnedCollectionBranch":[],"OwnedReferenceBranch":{"Date":"2101-01-01T00:00:00","Enum":2,"Enums":[-1,-1,2],"Fraction":10.1,"Id":10,"NullableEnum":-1,"NullableEnums":[null,-1,2],"OwnedCollectionLeaf":[{"SomethingSomething":"e1_r_r_c1"},{"SomethingSomething":"e1_r_r_c2"}],"OwnedReferenceLeaf":{"SomethingSomething":"测试2"}}}' (Nullable = false) (Size = 386) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonEntitiesBasic] SET [OwnedReferenceRoot] = @p0 +OUTPUT 1 +WHERE [Id] = @p1; +""", + // + """ +SELECT TOP(2) [j].[Id], [j].[EntityBasicId], [j].[Name], [j].[OwnedCollectionRoot], [j].[OwnedReferenceRoot] +FROM [JsonEntitiesBasic] AS [j] +WHERE [j].[Id] = 1 +"""); + } + protected override void ClearLog() => Fixture.TestSqlLoggerFactory.Clear();