@@ -213,36 +213,56 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction
213
213
if ( sqlFunctionExpression is { IsBuiltIn : true , Arguments : not null }
214
214
&& string . Equals ( sqlFunctionExpression . Name , "COALESCE" , StringComparison . OrdinalIgnoreCase ) )
215
215
{
216
- var type = sqlFunctionExpression . Type ;
217
- var typeMapping = sqlFunctionExpression . TypeMapping ;
218
- var defaultTypeMapping = _typeMappingSource . FindMapping ( type ) ;
219
-
220
216
// ISNULL always return a value having the same type as its first
221
217
// argument. Ideally we would convert the argument to have the
222
218
// desired type and type mapping, but currently EFCore has some
223
219
// 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
+ ) ;
246
266
}
247
267
}
248
268
0 commit comments