@@ -226,12 +226,7 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
226
226
subQueryIndent = _relationalCommandBuilder . Indent ( ) ;
227
227
}
228
228
229
- if ( IsNonComposedSetOperation ( selectExpression ) )
230
- {
231
- // Naked set operation
232
- GenerateSetOperation ( ( SetOperationBase ) selectExpression . Tables [ 0 ] ) ;
233
- }
234
- else
229
+ if ( ! TryGenerateWithoutWrappingSelect ( selectExpression ) )
235
230
{
236
231
_relationalCommandBuilder . Append ( "SELECT " ) ;
237
232
@@ -300,6 +295,43 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
300
295
return selectExpression ;
301
296
}
302
297
298
+ /// <summary>
299
+ /// If possible, generates the expression contained within the provided <paramref name="selectExpression" /> without the wrapping
300
+ /// SELECT. This can be done for set operations and VALUES, which can appear as top-level statements without needing to be wrapped
301
+ /// in SELECT.
302
+ /// </summary>
303
+ protected virtual bool TryGenerateWithoutWrappingSelect ( SelectExpression selectExpression )
304
+ {
305
+ if ( IsNonComposedSetOperation ( selectExpression ) )
306
+ {
307
+ GenerateSetOperation ( ( SetOperationBase ) selectExpression . Tables [ 0 ] ) ;
308
+ return true ;
309
+ }
310
+
311
+ if ( selectExpression is
312
+ {
313
+ Tables : [ ValuesExpression valuesExpression ] ,
314
+ Offset : null ,
315
+ Limit : null ,
316
+ IsDistinct : false ,
317
+ Predicate : null ,
318
+ Having : null ,
319
+ Orderings . Count : 0 ,
320
+ GroupBy . Count : 0 ,
321
+ }
322
+ && selectExpression . Projection . Count == valuesExpression . ColumnNames . Count
323
+ && selectExpression . Projection . Select (
324
+ ( pe , index ) => pe . Expression is ColumnExpression column
325
+ && column . Name == valuesExpression . ColumnNames [ index ] )
326
+ . All ( e => e ) )
327
+ {
328
+ GenerateValues ( valuesExpression ) ;
329
+ return true ;
330
+ }
331
+
332
+ return false ;
333
+ }
334
+
303
335
/// <summary>
304
336
/// Generates a pseudo FROM clause. Required by some providers when a query has no actual FROM clause.
305
337
/// </summary>
@@ -371,16 +403,16 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction
371
403
/// <inheritdoc />
372
404
protected override Expression VisitTableValuedFunction ( TableValuedFunctionExpression tableValuedFunctionExpression )
373
405
{
374
- if ( ! string . IsNullOrEmpty ( tableValuedFunctionExpression . StoreFunction . Schema ) )
406
+ if ( ! string . IsNullOrEmpty ( tableValuedFunctionExpression . Schema ) )
375
407
{
376
408
_relationalCommandBuilder
377
- . Append ( _sqlGenerationHelper . DelimitIdentifier ( tableValuedFunctionExpression . StoreFunction . Schema ) )
409
+ . Append ( _sqlGenerationHelper . DelimitIdentifier ( tableValuedFunctionExpression . Schema ) )
378
410
. Append ( "." ) ;
379
411
}
380
412
381
- var name = tableValuedFunctionExpression . StoreFunction . IsBuiltIn
382
- ? tableValuedFunctionExpression . StoreFunction . Name
383
- : _sqlGenerationHelper . DelimitIdentifier ( tableValuedFunctionExpression . StoreFunction . Name ) ;
413
+ var name = tableValuedFunctionExpression . IsBuiltIn
414
+ ? tableValuedFunctionExpression . Name
415
+ : _sqlGenerationHelper . DelimitIdentifier ( tableValuedFunctionExpression . Name ) ;
384
416
385
417
_relationalCommandBuilder
386
418
. Append ( name )
@@ -607,19 +639,22 @@ protected override Expression VisitSqlParameter(SqlParameterExpression sqlParame
607
639
{
608
640
var invariantName = sqlParameterExpression . Name ;
609
641
var parameterName = sqlParameterExpression . Name ;
642
+ var typeMapping = sqlParameterExpression . TypeMapping ! ;
610
643
611
644
// Try to see if a parameter already exists - if so, just integrate the same placeholder into the SQL instead of sending the same
612
645
// data twice.
613
646
// Note that if the type mapping differs, we do send the same data twice (e.g. the same string may be sent once as Unicode, once as
614
647
// non-Unicode).
648
+ // TODO: Note that we perform Equals comparison on the value converter. We should be able to do reference comparison, but for
649
+ // that we need to ensure that there's only ever one type mapping instance (i.e. no type mappings are ever instantiated out of the
650
+ // type mapping source). See #30677.
615
651
var parameter = _relationalCommandBuilder . Parameters . FirstOrDefault (
616
652
p =>
617
653
p . InvariantName == parameterName
618
- && p is TypeMappedRelationalParameter typeMappedRelationalParameter
619
- && string . Equals (
620
- typeMappedRelationalParameter . RelationalTypeMapping . StoreType , sqlParameterExpression . TypeMapping ! . StoreType ,
621
- StringComparison . OrdinalIgnoreCase )
622
- && typeMappedRelationalParameter . RelationalTypeMapping . Converter == sqlParameterExpression . TypeMapping ! . Converter ) ;
654
+ && p is TypeMappedRelationalParameter { RelationalTypeMapping : var existingTypeMapping }
655
+ && string . Equals ( existingTypeMapping . StoreType , typeMapping . StoreType , StringComparison . OrdinalIgnoreCase )
656
+ && ( existingTypeMapping . Converter is null && typeMapping . Converter is null
657
+ || existingTypeMapping . Converter is not null && existingTypeMapping . Converter . Equals ( typeMapping . Converter ) ) ) ;
623
658
624
659
if ( parameter is null )
625
660
{
@@ -1132,6 +1167,28 @@ protected override Expression VisitRowNumber(RowNumberExpression rowNumberExpres
1132
1167
return rowNumberExpression ;
1133
1168
}
1134
1169
1170
+ /// <inheritdoc />
1171
+ protected override Expression VisitRowValue ( RowValueExpression rowValueExpression )
1172
+ {
1173
+ Sql . Append ( "(" ) ;
1174
+
1175
+ var values = rowValueExpression . Values ;
1176
+ var count = values . Count ;
1177
+ for ( var i = 0 ; i < count ; i ++ )
1178
+ {
1179
+ if ( i > 0 )
1180
+ {
1181
+ Sql . Append ( ", " ) ;
1182
+ }
1183
+
1184
+ Visit ( values [ i ] ) ;
1185
+ }
1186
+
1187
+ Sql . Append ( ")" ) ;
1188
+
1189
+ return rowValueExpression ;
1190
+ }
1191
+
1135
1192
/// <summary>
1136
1193
/// Generates a set operation in the relational command.
1137
1194
/// </summary>
@@ -1311,6 +1368,65 @@ void LiftPredicate(TableExpressionBase joinTable)
1311
1368
RelationalStrings . ExecuteOperationWithUnsupportedOperatorInSqlGeneration ( nameof ( RelationalQueryableExtensions . ExecuteUpdate ) ) ) ;
1312
1369
}
1313
1370
1371
+ /// <inheritdoc />
1372
+ protected override Expression VisitValues ( ValuesExpression valuesExpression )
1373
+ {
1374
+ _relationalCommandBuilder . Append ( "(" ) ;
1375
+
1376
+ GenerateValues ( valuesExpression ) ;
1377
+
1378
+ _relationalCommandBuilder
1379
+ . Append ( ")" )
1380
+ . Append ( AliasSeparator )
1381
+ . Append ( _sqlGenerationHelper . DelimitIdentifier ( valuesExpression . Alias ) ) ;
1382
+
1383
+ return valuesExpression ;
1384
+ }
1385
+
1386
+ /// <summary>
1387
+ /// Generates a VALUES expression.
1388
+ /// </summary>
1389
+ protected virtual void GenerateValues ( ValuesExpression valuesExpression )
1390
+ {
1391
+ var rowValues = valuesExpression . RowValues ;
1392
+
1393
+ // Some databases support providing the names of columns projected out of VALUES, e.g.
1394
+ // SQL Server/PG: (VALUES (1, 3), (2, 4)) AS x(a, b). Others unfortunately don't; so by default, we extract out the first row,
1395
+ // and generate a SELECT for it with the names, and a UNION ALL over the rest of the values.
1396
+ _relationalCommandBuilder . Append ( "SELECT " ) ;
1397
+
1398
+ Check . DebugAssert ( rowValues . Count > 0 , "rowValues.Count > 0" ) ;
1399
+ var firstRowValues = rowValues [ 0 ] . Values ;
1400
+ for ( var i = 0 ; i < firstRowValues . Count ; i ++ )
1401
+ {
1402
+ if ( i > 0 )
1403
+ {
1404
+ _relationalCommandBuilder . Append ( ", " ) ;
1405
+ }
1406
+
1407
+ Visit ( firstRowValues [ i ] ) ;
1408
+
1409
+ _relationalCommandBuilder
1410
+ . Append ( AliasSeparator )
1411
+ . Append ( _sqlGenerationHelper . DelimitIdentifier ( valuesExpression . ColumnNames [ i ] ) ) ;
1412
+ }
1413
+
1414
+ if ( rowValues . Count > 1 )
1415
+ {
1416
+ _relationalCommandBuilder . Append ( " UNION ALL VALUES " ) ;
1417
+
1418
+ for ( var i = 1 ; i < rowValues . Count ; i ++ )
1419
+ {
1420
+ if ( i > 1 )
1421
+ {
1422
+ _relationalCommandBuilder . Append ( ", " ) ;
1423
+ }
1424
+
1425
+ Visit ( valuesExpression . RowValues [ i ] ) ;
1426
+ }
1427
+ }
1428
+ }
1429
+
1314
1430
/// <inheritdoc />
1315
1431
protected override Expression VisitJsonScalar ( JsonScalarExpression jsonScalarExpression )
1316
1432
=> throw new InvalidOperationException (
0 commit comments