@@ -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,6 +639,7 @@ 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.
@@ -615,11 +648,10 @@ protected override Expression VisitSqlParameter(SqlParameterExpression sqlParame
615
648
var parameter = _relationalCommandBuilder . Parameters . FirstOrDefault (
616
649
p =>
617
650
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 ) ;
651
+ && p is TypeMappedRelationalParameter { RelationalTypeMapping : var existingTypeMapping }
652
+ && string . Equals ( existingTypeMapping . StoreType , typeMapping . StoreType , StringComparison . OrdinalIgnoreCase )
653
+ && ( existingTypeMapping . Converter is null && typeMapping . Converter is null
654
+ || existingTypeMapping . Converter is not null && existingTypeMapping . Converter . Equals ( typeMapping . Converter ) ) ) ;
623
655
624
656
if ( parameter is null )
625
657
{
@@ -1132,6 +1164,28 @@ protected override Expression VisitRowNumber(RowNumberExpression rowNumberExpres
1132
1164
return rowNumberExpression ;
1133
1165
}
1134
1166
1167
+ /// <inheritdoc />
1168
+ protected override Expression VisitRowValue ( RowValueExpression rowValueExpression )
1169
+ {
1170
+ Sql . Append ( "(" ) ;
1171
+
1172
+ var values = rowValueExpression . Values ;
1173
+ var count = values . Count ;
1174
+ for ( var i = 0 ; i < count ; i ++ )
1175
+ {
1176
+ if ( i > 0 )
1177
+ {
1178
+ Sql . Append ( ", " ) ;
1179
+ }
1180
+
1181
+ Visit ( values [ i ] ) ;
1182
+ }
1183
+
1184
+ Sql . Append ( ")" ) ;
1185
+
1186
+ return rowValueExpression ;
1187
+ }
1188
+
1135
1189
/// <summary>
1136
1190
/// Generates a set operation in the relational command.
1137
1191
/// </summary>
@@ -1311,6 +1365,66 @@ void LiftPredicate(TableExpressionBase joinTable)
1311
1365
RelationalStrings . ExecuteOperationWithUnsupportedOperatorInSqlGeneration ( nameof ( RelationalQueryableExtensions . ExecuteUpdate ) ) ) ;
1312
1366
}
1313
1367
1368
+ /// <inheritdoc />
1369
+ protected override Expression VisitValues ( ValuesExpression valuesExpression )
1370
+ {
1371
+ _relationalCommandBuilder . Append ( "(" ) ;
1372
+
1373
+ GenerateValues ( valuesExpression ) ;
1374
+
1375
+ _relationalCommandBuilder
1376
+ . Append ( ")" )
1377
+ . Append ( AliasSeparator )
1378
+ . Append ( _sqlGenerationHelper . DelimitIdentifier ( valuesExpression . Alias ) ) ;
1379
+
1380
+ return valuesExpression ;
1381
+ }
1382
+
1383
+ /// <summary>
1384
+ /// Generates a VALUES expression.
1385
+ /// </summary>
1386
+ protected virtual void GenerateValues ( ValuesExpression valuesExpression )
1387
+ {
1388
+ var rowValues = valuesExpression . RowValues ;
1389
+
1390
+ // Some databases support providing the names of columns projected out of VALUES, e.g.
1391
+ // 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,
1392
+ // and generate a SELECT for it with the names, and a UNION ALL over the rest of the values.
1393
+ _relationalCommandBuilder . Append ( "SELECT " ) ;
1394
+
1395
+ Check . DebugAssert ( rowValues . Count > 0 , "rowValues.Count > 0" ) ;
1396
+ var firstRowValues = rowValues [ 0 ] . Values ;
1397
+ for ( var i = 0 ; i < firstRowValues . Count ; i ++ )
1398
+ {
1399
+ if ( i > 0 )
1400
+ {
1401
+ _relationalCommandBuilder . Append ( ", " ) ;
1402
+ }
1403
+
1404
+ Visit ( firstRowValues [ i ] ) ;
1405
+
1406
+ _relationalCommandBuilder
1407
+ . Append ( AliasSeparator )
1408
+ . Append ( _sqlGenerationHelper . DelimitIdentifier ( valuesExpression . ColumnNames [ i ] ) ) ;
1409
+ }
1410
+
1411
+ if ( rowValues . Count > 1 )
1412
+ {
1413
+ _relationalCommandBuilder . Append ( " UNION ALL VALUES " ) ;
1414
+
1415
+ for ( var i = 1 ; i < rowValues . Count ; i ++ )
1416
+ {
1417
+ // TODO: Do we want newlines here?
1418
+ if ( i > 1 )
1419
+ {
1420
+ _relationalCommandBuilder . Append ( ", " ) ;
1421
+ }
1422
+
1423
+ Visit ( valuesExpression . RowValues [ i ] ) ;
1424
+ }
1425
+ }
1426
+ }
1427
+
1314
1428
/// <inheritdoc />
1315
1429
protected override Expression VisitJsonScalar ( JsonScalarExpression jsonScalarExpression )
1316
1430
=> throw new InvalidOperationException (
0 commit comments