Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
/// </summary>
protected override string GenerateNonNullSqlLiteral(object value)
=> $"'{EscapeSqlLiteral(JsonSerializer.Serialize(value))}'";
{
var jsonString = value is string str ? str : JsonSerializer.Serialize(value);
return $"'{EscapeSqlLiteral(jsonString)}'";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't fix the issue, see #32152. The root cause is the use of WriteStringValue in ToJsonTyped. We'd need to specify JavaScriptEncoder.UnsafeRelaxedJsonEscaping via JsonSerializerOptions to avoid escaping. However, this could break users that rely on the characters being escaped, so we should only do it if AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32152", out var enabled32152) && enabled32152 is true. We can store this value in a private static readonly field called UseOldBehavior32152

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're absolutely right. I've implemented the proper fix as you suggested. Instead of modifying GenerateNonNullSqlLiteral, I:

  1. Added JavaScriptEncoder.UnsafeRelaxedJsonEscaping to JsonStringReaderWriter.ToJsonTyped
  2. Added UseOldBehavior32152 AppContext switch for Microsoft.EntityFrameworkCore.Issue32152
  3. When the switch is enabled (old behavior), it uses the original escaping; otherwise uses relaxed escaping

This addresses the root cause in WriteStringValue and fixes Chinese character escaping while maintaining backward compatibility. Commit: 4fedef5

}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<JsonEntityBasic>().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<JsonEntityBasic>().SingleAsync(x => x.Id == 1);
Assert.Equal("测试2", result.OwnedReferenceRoot.OwnedReferenceBranch.OwnedReferenceLeaf.SomethingSomething);
});

public void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction)
=> facade.UseTransaction(transaction.GetDbTransaction());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ArgumentOutOfRangeException>(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();

Expand Down
Loading