@@ -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