Skip to content

Commit 5553bee

Browse files
rojiWhatzGames
authored andcommitted
Fix scalar casting from string for POCO/DOM JSON mode (npgsql#3340)
Fixes npgsql#2774 Fixes npgsql#3158
1 parent c217da3 commit 5553bee

File tree

2 files changed

+24
-62
lines changed

2 files changed

+24
-62
lines changed

src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonDomTranslator.cs

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,11 @@ public NpgsqlJsonDomTranslator(
130130
typeof(string),
131131
_stringTypeMapping);
132132

133+
// The PostgreSQL traversal operator always returns text - for these scalar-returning methods, apply a conversion from string.
133134
return method.Name == nameof(JsonElement.GetString)
134135
? traversalToText
135-
: ConvertFromText(traversalToText, method.ReturnType);
136+
: _sqlExpressionFactory.Convert(
137+
traversalToText, method.ReturnType, _typeMappingSource.FindMapping(method.ReturnType, _model));
136138
}
137139

138140
if (method == GetArrayLength)
@@ -152,30 +154,4 @@ public NpgsqlJsonDomTranslator(
152154

153155
return null;
154156
}
155-
156-
// The PostgreSQL traversal operator always returns text, so we need to convert to int, bool, etc.
157-
private SqlExpression ConvertFromText(SqlExpression expression, Type returnType)
158-
{
159-
switch (Type.GetTypeCode(returnType))
160-
{
161-
case TypeCode.Boolean:
162-
case TypeCode.Byte:
163-
case TypeCode.DateTime:
164-
case TypeCode.Decimal:
165-
case TypeCode.Double:
166-
case TypeCode.Int16:
167-
case TypeCode.Int32:
168-
case TypeCode.Int64:
169-
case TypeCode.SByte:
170-
case TypeCode.Single:
171-
case TypeCode.UInt16:
172-
case TypeCode.UInt32:
173-
case TypeCode.UInt64:
174-
return _sqlExpressionFactory.Convert(expression, returnType, _typeMappingSource.FindMapping(returnType, _model));
175-
default:
176-
return returnType == typeof(Guid)
177-
? _sqlExpressionFactory.Convert(expression, returnType, _typeMappingSource.FindMapping(returnType, _model))
178-
: expression;
179-
}
180-
}
181157
}

src/EFCore.PG/Query/ExpressionTranslators/Internal/NpgsqlJsonPocoTranslator.cs

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ public NpgsqlJsonPocoTranslator(
9797
/// doing so can result in application failures when updating to a new Entity Framework Core release.
9898
/// </summary>
9999
public virtual SqlExpression? TranslateMemberAccess(SqlExpression instance, SqlExpression member, Type returnType)
100-
=> instance switch
100+
{
101+
return instance switch
101102
{
102103
// The first time we see a JSON traversal it's on a column - create a JsonTraversalExpression.
103104
// Traversals on top of that get appended into the same expression.
@@ -119,6 +120,25 @@ PgJsonTraversalExpression prevPathTraversal
119120
_ => null
120121
};
121122

123+
// The PostgreSQL traversal operator always returns text.
124+
// If the type returned is a scalar (int, bool, etc.), we need to apply a conversion from string.
125+
SqlExpression ConvertFromText(SqlExpression expression, Type returnType)
126+
=> _typeMappingSource.FindMapping(returnType.UnwrapNullableType(), _model) switch
127+
{
128+
// Type mapping not found - this isn't a scalar
129+
null => expression,
130+
131+
// Arrays are dealt with as JSON arrays, not array scalars
132+
NpgsqlArrayTypeMapping => expression,
133+
134+
// Text types don't a a conversion to string
135+
{ StoreTypeNameBase: "text" or "varchar" or "char" } => expression,
136+
137+
// For any other type mapping, this is a scalar; apply a conversion to the type from string.
138+
var mapping => _sqlExpressionFactory.Convert(expression, returnType, mapping)
139+
};
140+
}
141+
122142
/// <summary>
123143
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
124144
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -159,38 +179,4 @@ PgJsonTraversalExpression prevPathTraversal
159179
return null;
160180
}
161181
}
162-
163-
// The PostgreSQL traversal operator always returns text, so we need to convert to int, bool, etc.
164-
private SqlExpression ConvertFromText(SqlExpression expression, Type returnType)
165-
{
166-
var unwrappedReturnType = returnType.UnwrapNullableType();
167-
168-
switch (Type.GetTypeCode(unwrappedReturnType))
169-
{
170-
case TypeCode.Boolean:
171-
case TypeCode.Byte:
172-
case TypeCode.DateTime:
173-
case TypeCode.Decimal:
174-
case TypeCode.Double:
175-
case TypeCode.Int16:
176-
case TypeCode.Int32:
177-
case TypeCode.Int64:
178-
case TypeCode.SByte:
179-
case TypeCode.Single:
180-
case TypeCode.UInt16:
181-
case TypeCode.UInt32:
182-
case TypeCode.UInt64:
183-
return _sqlExpressionFactory.Convert(expression, returnType, _typeMappingSource.FindMapping(returnType, _model));
184-
}
185-
186-
if (unwrappedReturnType == typeof(Guid)
187-
|| unwrappedReturnType == typeof(DateTimeOffset)
188-
|| unwrappedReturnType == typeof(DateOnly)
189-
|| unwrappedReturnType == typeof(TimeOnly))
190-
{
191-
return _sqlExpressionFactory.Convert(expression, returnType, _typeMappingSource.FindMapping(returnType, _model));
192-
}
193-
194-
return expression;
195-
}
196182
}

0 commit comments

Comments
 (0)