@@ -226,12 +226,7 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
226226 subQueryIndent = _relationalCommandBuilder . Indent ( ) ;
227227 }
228228
229- if ( IsNonComposedSetOperation ( selectExpression ) )
230- {
231- // Naked set operation
232- GenerateSetOperation ( ( SetOperationBase ) selectExpression . Tables [ 0 ] ) ;
233- }
234- else
229+ if ( ! TryGenerateWithoutWrappingSelect ( selectExpression ) )
235230 {
236231 _relationalCommandBuilder . Append ( "SELECT " ) ;
237232
@@ -300,6 +295,43 @@ protected override Expression VisitSelect(SelectExpression selectExpression)
300295 return selectExpression ;
301296 }
302297
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+
303335 /// <summary>
304336 /// Generates a pseudo FROM clause. Required by some providers when a query has no actual FROM clause.
305337 /// </summary>
@@ -371,16 +403,16 @@ protected override Expression VisitSqlFunction(SqlFunctionExpression sqlFunction
371403 /// <inheritdoc />
372404 protected override Expression VisitTableValuedFunction ( TableValuedFunctionExpression tableValuedFunctionExpression )
373405 {
374- if ( ! string . IsNullOrEmpty ( tableValuedFunctionExpression . StoreFunction . Schema ) )
406+ if ( ! string . IsNullOrEmpty ( tableValuedFunctionExpression . Schema ) )
375407 {
376408 _relationalCommandBuilder
377- . Append ( _sqlGenerationHelper . DelimitIdentifier ( tableValuedFunctionExpression . StoreFunction . Schema ) )
409+ . Append ( _sqlGenerationHelper . DelimitIdentifier ( tableValuedFunctionExpression . Schema ) )
378410 . Append ( "." ) ;
379411 }
380412
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 ) ;
384416
385417 _relationalCommandBuilder
386418 . Append ( name )
@@ -607,19 +639,22 @@ protected override Expression VisitSqlParameter(SqlParameterExpression sqlParame
607639 {
608640 var invariantName = sqlParameterExpression . Name ;
609641 var parameterName = sqlParameterExpression . Name ;
642+ var typeMapping = sqlParameterExpression . TypeMapping ! ;
610643
611644 // Try to see if a parameter already exists - if so, just integrate the same placeholder into the SQL instead of sending the same
612645 // data twice.
613646 // 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
614647 // 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.
615651 var parameter = _relationalCommandBuilder . Parameters . FirstOrDefault (
616652 p =>
617653 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 ) ) ) ;
623658
624659 if ( parameter is null )
625660 {
@@ -1132,6 +1167,28 @@ protected override Expression VisitRowNumber(RowNumberExpression rowNumberExpres
11321167 return rowNumberExpression ;
11331168 }
11341169
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+
11351192 /// <summary>
11361193 /// Generates a set operation in the relational command.
11371194 /// </summary>
@@ -1311,6 +1368,65 @@ void LiftPredicate(TableExpressionBase joinTable)
13111368 RelationalStrings . ExecuteOperationWithUnsupportedOperatorInSqlGeneration ( nameof ( RelationalQueryableExtensions . ExecuteUpdate ) ) ) ;
13121369 }
13131370
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+
13141430 /// <inheritdoc />
13151431 protected override Expression VisitJsonScalar ( JsonScalarExpression jsonScalarExpression )
13161432 => throw new InvalidOperationException (
0 commit comments