@@ -156,25 +156,16 @@ protected override Expression VisitUpdate(UpdateExpression updateExpression)
156156 }
157157
158158 // SQL Server 2025 modify method (https://learn.microsoft.com/sql/t-sql/data-types/json-data-type#modify-method)
159- // We generate the syntax here manually rather than just visiting the SqlFunctionExpression because:
160- // 1. The JSON column (function instance) needs to be rendered *without* the column, unlike elsewhere.
161- // 2. The JSON path is packaged as a SqlConstantExpression over IReadOnlyList<PathSegment> which we unpack here
162- // and from which we generate the jsonpath string.
163- // In any case this isn't a standard setter of the form SET x = y, but rather just SET [x].modify(...).
159+ // This requires special handling since modify isn't a standard setter of the form SET x = y, but rather just
160+ // SET [x].modify(...).
164161 if ( value is SqlFunctionExpression
165162 {
166163 Name : "modify" ,
167- Instance : ColumnExpression { TypeMapping . StoreType : "json" } jsonColumn ,
168- Arguments : [ SqlConstantExpression { Value : IReadOnlyList < PathSegment > jsonPath } , var item ]
164+ IsBuiltIn : true ,
165+ Instance : ColumnExpression { TypeMapping . StoreType : "json" } instance
169166 } )
170167 {
171- Sql
172- . Append ( _sqlGenerationHelper . DelimitIdentifier ( jsonColumn . Name ) )
173- . Append ( ".modify(" ) ;
174- GenerateJsonPath ( jsonPath ) ;
175- Sql . Append ( ", " ) ;
176- Visit ( item ) ;
177- Sql . Append ( ")" ) ;
168+ Visit ( value ) ;
178169 continue ;
179170 }
180171
@@ -260,40 +251,68 @@ protected override Expression VisitSqlConstant(SqlConstantExpression sqlConstant
260251 /// </summary>
261252 protected override Expression VisitSqlFunction ( SqlFunctionExpression sqlFunctionExpression )
262253 {
263- if ( sqlFunctionExpression is { IsBuiltIn : true , Arguments : not null }
264- && string . Equals ( sqlFunctionExpression . Name , "COALESCE" , StringComparison . OrdinalIgnoreCase ) )
254+ switch ( sqlFunctionExpression )
265255 {
266- var type = sqlFunctionExpression . Type ;
267- var typeMapping = sqlFunctionExpression . TypeMapping ;
268- var defaultTypeMapping = _typeMappingSource . FindMapping ( type ) ;
269-
270- // ISNULL always return a value having the same type as its first
271- // argument. Ideally we would convert the argument to have the
272- // desired type and type mapping, but currently EFCore has some
273- // trouble in computing types of non-homogeneous expressions
274- // (tracked in https://github.com/dotnet/efcore/issues/15586). To
275- // stay on the safe side we only use ISNULL if:
276- // - all sub-expressions have the same type as the expression
277- // - all sub-expressions have the same type mapping as the expression
278- // - the expression is using the default type mapping (combined
279- // with the two above, this implies that all of the expressions
280- // are using the default type mapping of the type)
281- if ( defaultTypeMapping == typeMapping
282- && sqlFunctionExpression . Arguments . All ( a => a . Type == type && a . TypeMapping == typeMapping ) )
256+ case { IsBuiltIn : true , Arguments : not null }
257+ when string . Equals ( sqlFunctionExpression . Name , "COALESCE" , StringComparison . OrdinalIgnoreCase ) :
283258 {
284- var head = sqlFunctionExpression . Arguments [ 0 ] ;
285- sqlFunctionExpression = ( SqlFunctionExpression ) sqlFunctionExpression
286- . Arguments
287- . Skip ( 1 )
288- . Aggregate (
289- head , ( l , r ) => new SqlFunctionExpression (
290- "ISNULL" ,
291- arguments : [ l , r ] ,
292- nullable : true ,
293- argumentsPropagateNullability : [ false , false ] ,
294- sqlFunctionExpression . Type ,
295- sqlFunctionExpression . TypeMapping
296- ) ) ;
259+ var type = sqlFunctionExpression . Type ;
260+ var typeMapping = sqlFunctionExpression . TypeMapping ;
261+ var defaultTypeMapping = _typeMappingSource . FindMapping ( type ) ;
262+
263+ // ISNULL always return a value having the same type as its first
264+ // argument. Ideally we would convert the argument to have the
265+ // desired type and type mapping, but currently EFCore has some
266+ // trouble in computing types of non-homogeneous expressions
267+ // (tracked in https://github.com/dotnet/efcore/issues/15586). To
268+ // stay on the safe side we only use ISNULL if:
269+ // - all sub-expressions have the same type as the expression
270+ // - all sub-expressions have the same type mapping as the expression
271+ // - the expression is using the default type mapping (combined
272+ // with the two above, this implies that all of the expressions
273+ // are using the default type mapping of the type)
274+ if ( defaultTypeMapping == typeMapping
275+ && sqlFunctionExpression . Arguments . All ( a => a . Type == type && a . TypeMapping == typeMapping ) )
276+ {
277+ var head = sqlFunctionExpression . Arguments [ 0 ] ;
278+ sqlFunctionExpression = ( SqlFunctionExpression ) sqlFunctionExpression
279+ . Arguments
280+ . Skip ( 1 )
281+ . Aggregate (
282+ head , ( l , r ) => new SqlFunctionExpression (
283+ "ISNULL" ,
284+ arguments : [ l , r ] ,
285+ nullable : true ,
286+ argumentsPropagateNullability : [ false , false ] ,
287+ sqlFunctionExpression . Type ,
288+ sqlFunctionExpression . TypeMapping
289+ ) ) ;
290+ }
291+
292+ return base . VisitSqlFunction ( sqlFunctionExpression ) ;
293+ }
294+
295+ // SQL Server 2025 modify method (https://learn.microsoft.com/sql/t-sql/data-types/json-data-type#modify-method)
296+ // We get here only from within UPDATE setters.
297+ // We generate the syntax here manually rather than just using the regular function visitation logic since
298+ // the JSON column (function instance) needs to be rendered *without* the column, unlike elsewhere.
299+ case
300+ {
301+ Name : "modify" ,
302+ IsBuiltIn : true ,
303+ Instance : ColumnExpression { TypeMapping . StoreType : "json" } jsonColumn ,
304+ Arguments : [ SqlConstantExpression { Value : IReadOnlyList < PathSegment > jsonPath } , var item ]
305+ } :
306+ {
307+ Sql
308+ . Append ( _sqlGenerationHelper . DelimitIdentifier ( jsonColumn . Name ) )
309+ . Append ( ".modify(" ) ;
310+ GenerateJsonPath ( jsonPath ) ;
311+ Sql . Append ( ", " ) ;
312+ Visit ( item ) ;
313+ Sql . Append ( ")" ) ;
314+
315+ return sqlFunctionExpression ;
297316 }
298317 }
299318
0 commit comments