Skip to content

Commit 7df1df6

Browse files
committed
Translate more COALESCE uses as ISNULL
1 parent 6676702 commit 7df1df6

File tree

1 file changed

+49
-27
lines changed

1 file changed

+49
-27
lines changed

src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -258,40 +258,62 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction
258258
case { IsBuiltIn: true, Arguments: not null }
259259
when string.Equals(sqlFunctionExpression.Name, "COALESCE", StringComparison.OrdinalIgnoreCase):
260260
{
261-
var type = sqlFunctionExpression.Type;
262-
var typeMapping = sqlFunctionExpression.TypeMapping;
263-
var defaultTypeMapping = _typeMappingSource.FindMapping(type);
264-
265261
// ISNULL always return a value having the same type as its first
266262
// argument. Ideally we would convert the argument to have the
267263
// desired type and type mapping, but currently EFCore has some
268264
// trouble in computing types of non-homogeneous expressions
269-
// (tracked in https://github.com/dotnet/efcore/issues/15586). To
270-
// stay on the safe side we only use ISNULL if:
271-
// - all sub-expressions have the same type as the expression
272-
// - all sub-expressions have the same type mapping as the expression
273-
// - the expression is using the default type mapping (combined
274-
// with the two above, this implies that all of the expressions
275-
// are using the default type mapping of the type)
276-
if (defaultTypeMapping == typeMapping
277-
&& sqlFunctionExpression.Arguments.All(a => a.Type == type && a.TypeMapping == typeMapping))
265+
// (tracked in https://github.com/dotnet/efcore/issues/15586).
266+
//
267+
// The main issue is the sizing of the type. Since sometimes the
268+
// computed size is wrong, stay on the safe side by expanding to the
269+
// maximum supported size with an approach similar to that used in
270+
// SqlServerStringAggregateMethodTranslator. This might result in
271+
// unneeded conversions, but should produce the correct results.
272+
var forceCast = sqlFunctionExpression.TypeMapping?.StoreTypeNameBase is
273+
"nvarchar" or "varchar" or "varbinary";
274+
275+
var typeMapping = sqlFunctionExpression.TypeMapping switch
276+
{
277+
{ StoreTypeNameBase: "nvarchar", Size: >= 0 and < 4000 } => _typeMappingSource.FindMapping(
278+
typeof(string),
279+
sqlFunctionExpression.TypeMapping.StoreTypeNameBase,
280+
unicode: true,
281+
size: 4000),
282+
{ StoreTypeNameBase: "varchar" or "varbinary", Size: >= 0 and < 8000 } => _typeMappingSource.FindMapping(
283+
typeof(string),
284+
sqlFunctionExpression.TypeMapping.StoreTypeNameBase,
285+
unicode: false,
286+
size: 8000),
287+
var t => t,
288+
};
289+
290+
var result = sqlFunctionExpression.Arguments[0];
291+
if (forceCast || result.TypeMapping?.StoreType != typeMapping?.StoreType)
292+
{
293+
result = new SqlUnaryExpression(
294+
ExpressionType.Convert,
295+
result,
296+
sqlFunctionExpression.Type,
297+
typeMapping
298+
);
299+
}
300+
301+
var length = sqlFunctionExpression.Arguments.Count;
302+
for (var i = 1; i < length; i++)
278303
{
279-
var head = sqlFunctionExpression.Arguments[0];
280-
sqlFunctionExpression = (SqlFunctionExpression)sqlFunctionExpression
281-
.Arguments
282-
.Skip(1)
283-
.Aggregate(
284-
head, (l, r) => new SqlFunctionExpression(
285-
"ISNULL",
286-
arguments: [l, r],
287-
nullable: true,
288-
argumentsPropagateNullability: [false, false],
289-
sqlFunctionExpression.Type,
290-
sqlFunctionExpression.TypeMapping
291-
));
304+
// propagate type and type mapping from the first argument,
305+
// nullability from COALESCE
306+
result = new SqlFunctionExpression(
307+
"ISNULL",
308+
arguments: [result, sqlFunctionExpression.Arguments[i]],
309+
nullable: i == length - 1 ? sqlFunctionExpression.IsNullable : true,
310+
argumentsPropagateNullability: [false, false],
311+
result.Type,
312+
result.TypeMapping
313+
);
292314
}
293315

294-
return base.VisitSqlFunction(sqlFunctionExpression);
316+
return base.VisitSqlFunction((SqlFunctionExpression)result);
295317
}
296318

297319
case SqlServerJsonObjectExpression jsonObject:

0 commit comments

Comments
 (0)