From dc87c70771694578d403fb770bc77a125ad17f8b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 30 May 2025 23:52:31 +0000
Subject: [PATCH 1/3] Initial plan for issue
From cb71c3429927d5d274779e13e38f1fab556d878e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 31 May 2025 00:02:47 +0000
Subject: [PATCH 2/3] Fix double escaping of Chinese characters in JSON columns
during updates
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
---
.../Internal/SqlServerOwnedJsonTypeMapping.cs | 5 ++-
.../Update/JsonUpdateTestBase.cs | 34 ++++++++++++++++
.../Update/JsonUpdateSqlServerTest.cs | 40 +++++++++++++++++++
3 files changed, 78 insertions(+), 1 deletion(-)
diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs
index 801ac044755..ae0b4034853 100644
--- a/src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs
+++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs
@@ -105,7 +105,10 @@ protected virtual string EscapeSqlLiteral(string literal)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
protected override string GenerateNonNullSqlLiteral(object value)
- => $"'{EscapeSqlLiteral(JsonSerializer.Serialize(value))}'";
+ {
+ var jsonString = value is string str ? str : JsonSerializer.Serialize(value);
+ return $"'{EscapeSqlLiteral(jsonString)}'";
+ }
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
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();
From 4fedef575432c84b29bff5cccbc48bdb12de9feb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 31 May 2025 01:20:03 +0000
Subject: [PATCH 3/3] Implement proper fix for Chinese character escaping in
JSON using AppContext switch
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
---
.../Internal/SqlServerOwnedJsonTypeMapping.cs | 5 +----
src/EFCore/Storage/Json/JsonStringReaderWriter.cs | 15 ++++++++++++++-
2 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs
index ae0b4034853..801ac044755 100644
--- a/src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs
+++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerOwnedJsonTypeMapping.cs
@@ -105,10 +105,7 @@ protected virtual string EscapeSqlLiteral(string literal)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
protected override string GenerateNonNullSqlLiteral(object value)
- {
- var jsonString = value is string str ? str : JsonSerializer.Serialize(value);
- return $"'{EscapeSqlLiteral(jsonString)}'";
- }
+ => $"'{EscapeSqlLiteral(JsonSerializer.Serialize(value))}'";
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
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