Skip to content

Commit 0bc51de

Browse files
committed
Translate more COALESCE uses as ISNULL
1 parent f364295 commit 0bc51de

File tree

1 file changed

+51
-26
lines changed

1 file changed

+51
-26
lines changed

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

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -213,37 +213,62 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction
213213
if (sqlFunctionExpression is { IsBuiltIn: true, Arguments: not null }
214214
&& string.Equals(sqlFunctionExpression.Name, "COALESCE", StringComparison.OrdinalIgnoreCase))
215215
{
216-
var type = sqlFunctionExpression.Type;
217-
var typeMapping = sqlFunctionExpression.TypeMapping;
218-
var defaultTypeMapping = _typeMappingSource.FindMapping(type);
219-
220216
// ISNULL always return a value having the same type as its first
221217
// argument. Ideally we would convert the argument to have the
222218
// desired type and type mapping, but currently EFCore has some
223219
// trouble in computing types of non-homogeneous expressions
224-
// (tracked in https://github.com/dotnet/efcore/issues/15586). To
225-
// stay on the safe side we only use ISNULL if:
226-
// - all sub-expressions have the same type as the expression
227-
// - all sub-expressions have the same type mapping as the expression
228-
// - the expression is using the default type mapping (combined
229-
// with the two above, this implies that all of the expressions
230-
// are using the default type mapping of the type)
231-
if (defaultTypeMapping == typeMapping
232-
&& sqlFunctionExpression.Arguments.All(a => a.Type == type && a.TypeMapping == typeMapping)) {
233-
234-
var head = sqlFunctionExpression.Arguments[0];
235-
sqlFunctionExpression = (SqlFunctionExpression)sqlFunctionExpression
236-
.Arguments
237-
.Skip(1)
238-
.Aggregate(head, (l, r) => new SqlFunctionExpression(
239-
"ISNULL",
240-
arguments: [l, r],
241-
nullable: true,
242-
argumentsPropagateNullability: [false, false],
243-
sqlFunctionExpression.Type,
244-
sqlFunctionExpression.TypeMapping
245-
));
220+
// (tracked in https://github.com/dotnet/efcore/issues/15586).
221+
//
222+
// The main issue is the sizing of the type. Since sometimes the
223+
// computed size is wrong, stay on the safe side by expanding to the
224+
// maximum supported size with an approach similar to that used in
225+
// SqlServerStringAggregateMethodTranslator. This might result in
226+
// unneeded conversions, but should produce the correct results.
227+
var forceCast = sqlFunctionExpression.TypeMapping?.StoreTypeNameBase is
228+
"nvarchar" or "varchar" or "varbinary";
229+
230+
var typeMapping = sqlFunctionExpression.TypeMapping switch
231+
{
232+
{ StoreTypeNameBase: "nvarchar", Size: >= 0 and < 4000 } => _typeMappingSource.FindMapping(
233+
typeof(string),
234+
sqlFunctionExpression.TypeMapping.StoreTypeNameBase,
235+
unicode: true,
236+
size: 4000),
237+
{ StoreTypeNameBase: "varchar" or "varbinary", Size: >= 0 and < 8000 } => _typeMappingSource.FindMapping(
238+
typeof(string),
239+
sqlFunctionExpression.TypeMapping.StoreTypeNameBase,
240+
unicode: false,
241+
size: 8000),
242+
var t => t,
243+
};
244+
245+
var result = sqlFunctionExpression.Arguments[0];
246+
if (forceCast || result.TypeMapping?.StoreType != typeMapping?.StoreType)
247+
{
248+
result = new SqlUnaryExpression(
249+
ExpressionType.Convert,
250+
result,
251+
sqlFunctionExpression.Type,
252+
typeMapping
253+
);
246254
}
255+
256+
var length = sqlFunctionExpression.Arguments.Count;
257+
for (var i = 1; i < length; i++)
258+
{
259+
// propagate type and type mapping from the first argument,
260+
// nullability from COALESCE
261+
result = new SqlFunctionExpression(
262+
"ISNULL",
263+
arguments: [result, sqlFunctionExpression.Arguments[i]],
264+
nullable: i == length - 1 ? sqlFunctionExpression.IsNullable : true,
265+
argumentsPropagateNullability: [false, false],
266+
result.Type,
267+
result.TypeMapping
268+
);
269+
}
270+
271+
sqlFunctionExpression = (SqlFunctionExpression)result;
247272
}
248273

249274
return base.VisitSqlFunction(sqlFunctionExpression);

0 commit comments

Comments
 (0)