Skip to content

Commit de7f18d

Browse files
committed
Translate more COALESCE uses as ISNULL
1 parent 31dbcea commit de7f18d

File tree

1 file changed

+46
-26
lines changed

1 file changed

+46
-26
lines changed

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

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -213,36 +213,56 @@ 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, which is expanded to
223+
// the maximum supported size with an approach similar to that used
224+
// in SqlServerStringAggregateMethodTranslator. This might result in
225+
// unneeded conversions, but should produce the correct results.
226+
227+
var typeMapping = sqlFunctionExpression.TypeMapping switch
228+
{
229+
{ StoreTypeNameBase: "nvarchar", Size: >= 0 and < 4000 } => _typeMappingSource.FindMapping(
230+
typeof(string),
231+
sqlFunctionExpression.TypeMapping.StoreTypeNameBase,
232+
unicode: true,
233+
size: 4000),
234+
{ StoreType: "varchar" or "varbinary", Size: >= 0 and < 8000 } => _typeMappingSource.FindMapping(
235+
typeof(string),
236+
sqlFunctionExpression.TypeMapping.StoreTypeNameBase,
237+
unicode: false,
238+
size: 8000),
239+
var t => t,
240+
};
241+
242+
var result = sqlFunctionExpression.Arguments[0];
243+
if (result.TypeMapping?.StoreType != typeMapping?.StoreType)
244+
{
245+
result = new SqlUnaryExpression(
246+
ExpressionType.Convert,
247+
result,
248+
sqlFunctionExpression.Type,
249+
typeMapping
250+
);
251+
}
252+
253+
var length = sqlFunctionExpression.Arguments.Count;
254+
for (var i = 1; i < length; i++)
255+
{
256+
// propagate type and type mapping from the first argument,
257+
// nullability from COALESCE
258+
result = new SqlFunctionExpression(
259+
"ISNULL",
260+
arguments: [result, sqlFunctionExpression.Arguments[i]],
261+
nullable: i == length - 1 ? sqlFunctionExpression.IsNullable : true,
262+
argumentsPropagateNullability: [false, false],
263+
result.Type,
264+
result.TypeMapping
265+
);
246266
}
247267
}
248268

0 commit comments

Comments
 (0)