Skip to content

Commit 2f2e9b3

Browse files
Copilotrenemadsen
andcommitted
Fix: Remove separate type mapping, use ClrType check instead
Removed MySqlComplexJsonTypeMapping entirely - it was interfering with SQL generation. Reverted all debug logging and MySqlTypeMappingSource changes. Now MySqlJsonTypeMapping.CustomizeDataReaderExpression() checks ClrType: - If ClrType == string: Don't convert (regular JSON column) - If ClrType != string: Convert string to MemoryStream (complex JSON type) This preserves a single type mapping for SQL generation while correctly handling both regular JSON columns and complex JSON types during data reading. The key insight: Type mappings are used during SQL generation, not just data reading. Having different type mappings broke JSON path expressions like JSON_VALUE(). By using a single type mapping and checking ClrType at data reading time, we fix the coercion error without breaking SQL generation. Co-authored-by: renemadsen <76994+renemadsen@users.noreply.github.com>
1 parent ccf1b89 commit 2f2e9b3

3 files changed

Lines changed: 40 additions & 168 deletions

File tree

src/EFCore.MySql/Diagnostics/MySqlCommandInterceptor.cs

Lines changed: 0 additions & 61 deletions
This file was deleted.

src/EFCore.MySql/Storage/Internal/MySqlJsonTypeMapping.cs

Lines changed: 38 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -19,107 +19,6 @@
1919

2020
namespace Pomelo.EntityFrameworkCore.MySql.Storage.Internal
2121
{
22-
/// <summary>
23-
/// Type mapping for complex JSON types (used when JSON is mapped to complex .NET types).
24-
/// Always converts string to MemoryStream since EF Core expects MemoryStream for complex JSON.
25-
/// </summary>
26-
public class MySqlComplexJsonTypeMapping : MySqlJsonTypeMapping<string>
27-
{
28-
public MySqlComplexJsonTypeMapping(
29-
[NotNull] string storeType,
30-
[CanBeNull] ValueConverter valueConverter,
31-
[CanBeNull] ValueComparer valueComparer,
32-
bool noBackslashEscapes,
33-
bool replaceLineBreaksWithCharFunction)
34-
: base(storeType, valueConverter, valueComparer, noBackslashEscapes, replaceLineBreaksWithCharFunction)
35-
{
36-
Console.WriteLine($"[DEBUG] MySqlComplexJsonTypeMapping constructor called - StoreType: {storeType}, Converter: {valueConverter?.GetType().Name ?? "null"}");
37-
}
38-
39-
protected MySqlComplexJsonTypeMapping(
40-
RelationalTypeMappingParameters parameters,
41-
MySqlDbType mySqlDbType,
42-
bool noBackslashEscapes,
43-
bool replaceLineBreaksWithCharFunction)
44-
: base(parameters, mySqlDbType, noBackslashEscapes, replaceLineBreaksWithCharFunction)
45-
{
46-
Console.WriteLine($"[DEBUG] MySqlComplexJsonTypeMapping protected constructor called - ClrType: {ClrType?.Name ?? "null"}, StoreType: {StoreType}");
47-
}
48-
49-
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
50-
{
51-
Console.WriteLine($"[DEBUG] MySqlComplexJsonTypeMapping.Clone(parameters) called - ClrType: {parameters.CoreParameters.ClrType?.Name ?? "null"}");
52-
return new MySqlComplexJsonTypeMapping(parameters, MySqlDbType, NoBackslashEscapes, ReplaceLineBreaksWithCharFunction);
53-
}
54-
55-
protected override RelationalTypeMapping Clone(bool? noBackslashEscapes = null, bool? replaceLineBreaksWithCharFunction = null)
56-
{
57-
Console.WriteLine($"[DEBUG] MySqlComplexJsonTypeMapping.Clone(bool) called - ClrType: {ClrType?.Name ?? "null"}, StoreType: {StoreType}");
58-
return new MySqlComplexJsonTypeMapping(
59-
Parameters,
60-
MySqlDbType,
61-
noBackslashEscapes ?? NoBackslashEscapes,
62-
replaceLineBreaksWithCharFunction ?? ReplaceLineBreaksWithCharFunction);
63-
}
64-
65-
/// <summary>
66-
/// Returns the method to be used for reading JSON values from the database.
67-
/// MySQL stores JSON as strings, so we use GetString.
68-
/// </summary>
69-
public override MethodInfo GetDataReaderMethod()
70-
{
71-
Console.WriteLine($"[DEBUG] MySqlComplexJsonTypeMapping.GetDataReaderMethod() called - ClrType: {ClrType?.Name ?? "null"}, returning: {_getString?.Name ?? "null"}");
72-
return _getString;
73-
}
74-
75-
/// <summary>
76-
/// For complex JSON, we ALWAYS convert string to MemoryStream.
77-
/// </summary>
78-
public override Expression CustomizeDataReaderExpression(Expression expression)
79-
{
80-
Console.WriteLine($"[DEBUG] MySqlComplexJsonTypeMapping.CustomizeDataReaderExpression() called - ExpressionType: {expression.Type?.Name ?? "null"}, ClrType: {ClrType?.Name ?? "null"}");
81-
82-
if (expression.Type == typeof(string))
83-
{
84-
// Validate that reflection lookups succeeded (using cached members from base class)
85-
if (_utf8Property == null || _getBytesMethod == null || _memoryStreamCtor == null)
86-
{
87-
throw new InvalidOperationException(
88-
"Failed to find required reflection members for JSON type mapping.");
89-
}
90-
91-
Console.WriteLine($"[DEBUG] MySqlComplexJsonTypeMapping: Converting string expression to MemoryStream");
92-
93-
// Convert string to MemoryStream: new MemoryStream(Encoding.UTF8.GetBytes(stringValue))
94-
return Expression.New(
95-
_memoryStreamCtor,
96-
Expression.Call(
97-
Expression.Property(null, _utf8Property),
98-
_getBytesMethod,
99-
expression));
100-
}
101-
102-
Console.WriteLine($"[DEBUG] MySqlComplexJsonTypeMapping: No conversion, calling base.CustomizeDataReaderExpression");
103-
return base.CustomizeDataReaderExpression(expression);
104-
}
105-
106-
public override string GenerateSqlLiteral(object value)
107-
{
108-
Console.WriteLine($"[DEBUG] MySqlComplexJsonTypeMapping.GenerateSqlLiteral called - value type: {value?.GetType()?.Name ?? "null"}");
109-
var result = base.GenerateSqlLiteral(value);
110-
Console.WriteLine($"[DEBUG] MySqlComplexJsonTypeMapping.GenerateSqlLiteral result: {result}");
111-
return result;
112-
}
113-
114-
public override string GenerateProviderValueSqlLiteral(object value)
115-
{
116-
Console.WriteLine($"[DEBUG] MySqlComplexJsonTypeMapping.GenerateProviderValueSqlLiteral called - value type: {value?.GetType()?.Name ?? "null"}");
117-
var result = base.GenerateProviderValueSqlLiteral(value);
118-
Console.WriteLine($"[DEBUG] MySqlComplexJsonTypeMapping.GenerateProviderValueSqlLiteral result: {result}");
119-
return result;
120-
}
121-
}
122-
12322
public class MySqlJsonTypeMapping<T> : MySqlJsonTypeMapping
12423
{
12524
public static new MySqlJsonTypeMapping<T> Default { get; } = new("json", null, null, false, true);
@@ -234,6 +133,44 @@ protected override void ConfigureParameter(DbParameter parameter)
234133
}
235134
}
236135

136+
/// <summary>
137+
/// Returns the method to be used for reading JSON values from the database.
138+
/// MySQL stores JSON as strings, so we use GetString instead of the default GetFieldValue&lt;T&gt;.
139+
/// </summary>
140+
public override MethodInfo GetDataReaderMethod()
141+
=> _getString;
142+
143+
/// <summary>
144+
/// Customizes the data reader expression for JSON types.
145+
/// MySQL stores JSON as strings, but EF Core expects MemoryStream for complex JSON types.
146+
/// We only convert for non-string CLR types (complex JSON types), not for regular JSON columns mapped to string.
147+
/// </summary>
148+
public override Expression CustomizeDataReaderExpression(Expression expression)
149+
{
150+
// Only convert for complex JSON types (where ClrType is NOT string)
151+
// For regular JSON columns mapped to string, don't convert
152+
if (expression.Type == typeof(string) && ClrType != typeof(string))
153+
{
154+
// Validate that reflection lookups succeeded
155+
if (_utf8Property == null || _getBytesMethod == null || _memoryStreamCtor == null)
156+
{
157+
throw new InvalidOperationException(
158+
"Failed to find required reflection members for JSON type mapping. " +
159+
"This may indicate an incompatible version of the .NET runtime.");
160+
}
161+
162+
// Convert string to MemoryStream: new MemoryStream(Encoding.UTF8.GetBytes(stringValue))
163+
return Expression.New(
164+
_memoryStreamCtor,
165+
Expression.Call(
166+
Expression.Property(null, _utf8Property),
167+
_getBytesMethod,
168+
expression));
169+
}
170+
171+
return base.CustomizeDataReaderExpression(expression);
172+
}
173+
237174
void IMySqlCSharpRuntimeAnnotationTypeMappingCodeGenerator.Create(
238175
CSharpRuntimeAnnotationCodeGeneratorParameters codeGeneratorParameters,
239176
CSharpRuntimeAnnotationCodeGeneratorDependencies codeGeneratorDependencies)

src/EFCore.MySql/Storage/Internal/MySqlTypeMappingSource.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ private readonly RelationalTypeMapping _binaryRowVersion6
8989

9090
// JSON default mapping
9191
private MySqlJsonTypeMapping<string> _jsonDefaultString;
92-
private MySqlComplexJsonTypeMapping _jsonComplexType;
9392

9493
// Scaffolding type mappings
9594
private readonly MySqlCodeGenerationMemberAccessTypeMapping _codeGenerationMemberAccess = MySqlCodeGenerationMemberAccessTypeMapping.Default;
@@ -136,7 +135,6 @@ private void Initialize()
136135
: null;
137136

138137
_jsonDefaultString = new MySqlJsonTypeMapping<string>("json", null, null, _options.NoBackslashEscapes, _options.ReplaceLineBreaksWithCharFunction);
139-
_jsonComplexType = new MySqlComplexJsonTypeMapping("json", null, null, _options.NoBackslashEscapes, _options.ReplaceLineBreaksWithCharFunction);
140138

141139
_storeTypeMappings
142140
= new Dictionary<string, RelationalTypeMapping[]>(StringComparer.OrdinalIgnoreCase)
@@ -323,11 +321,10 @@ private RelationalTypeMapping FindRawMapping(RelationalTypeMappingInfo mappingIn
323321
// fail immediately.
324322

325323
// Special case for JSON columns: EF Core passes JsonTypePlaceholder as the CLR type
326-
// when creating JSON columns for complex types/collections. Return our complex JSON mapping.
324+
// when creating JSON columns for complex types/collections. Return our JSON mapping.
327325
if (clrType?.Name == "JsonTypePlaceholder" && storeTypeName.Equals("json", StringComparison.OrdinalIgnoreCase))
328326
{
329-
Console.WriteLine($"[DEBUG] MySqlTypeMappingSource: Detected JsonTypePlaceholder, returning _jsonComplexType");
330-
return _jsonComplexType;
327+
return _jsonDefaultString;
331328
}
332329

333330
return clrType == null
@@ -350,7 +347,6 @@ private RelationalTypeMapping FindRawMapping(RelationalTypeMappingInfo mappingIn
350347
// Works for both MySQL (native JSON type) and MariaDB (JSON alias for LONGTEXT)
351348
if (storeTypeName.Equals("json", StringComparison.OrdinalIgnoreCase))
352349
{
353-
Console.WriteLine($"[DEBUG] MySqlTypeMappingSource: JSON store type with CLR type: {clrType?.Name ?? "null"}, returning _jsonDefaultString");
354350
// Return JSON mapping for any CLR type since JSON can serialize any object
355351
// The "json" store type works for both:
356352
// - MySQL 5.7.8+: Creates native JSON column with binary storage

0 commit comments

Comments
 (0)