@@ -24,11 +24,12 @@ namespace Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement
2424 /// </summary>
2525 public sealed class RowCreate : RowEditBase
2626 {
27- private const string InsertScriptStart = "INSERT INTO {0}" ;
28- private const string InsertScriptColumns = "({0})" ;
29- private const string InsertScriptOut = " OUTPUT {0}" ;
30- private const string InsertScriptDefault = " DEFAULT VALUES" ;
31- private const string InsertScriptValues = " VALUES ({0})" ;
27+ private const string DeclareStatement = "DECLARE {0} TABLE ({1})" ;
28+ private const string InsertOutputDefaultStatement = "INSERT INTO {0} OUTPUT {1} INTO {2} DEFAULT VALUES" ;
29+ private const string InsertOutputValuesStatement = "INSERT INTO {0}({1}) OUTPUT {2} INTO {3} VALUES ({4})" ;
30+ private const string InsertScriptDefaultStatement = "INSERT INTO {0} DEFAULT VALUES" ;
31+ private const string InsertScriptValuesStatement = "INSERT INTO {0}({1}) VALUES ({2})" ;
32+ private const string SelectStatement = "SELECT {0} FROM {1}" ;
3233
3334 internal readonly CellUpdate [ ] newCells ;
3435
@@ -88,13 +89,72 @@ public override DbCommand GetCommand(DbConnection connection)
8889 {
8990 Validate . IsNotNull ( nameof ( connection ) , connection ) ;
9091
91- // Build the script and generate a command
92- ScriptBuildResult result = BuildInsertScript ( forCommand : true ) ;
92+ // Process the cells and columns
93+ List < string > declareColumns = new List < string > ( ) ;
94+ List < string > inColumnNames = new List < string > ( ) ;
95+ List < string > outClauseColumnNames = new List < string > ( ) ;
96+ List < string > inValues = new List < string > ( ) ;
97+ List < SqlParameter > inParameters = new List < SqlParameter > ( ) ;
98+ List < string > selectColumns = new List < string > ( ) ;
99+ for ( int i = 0 ; i < AssociatedObjectMetadata . Columns . Length ; i ++ )
100+ {
101+ DbColumnWrapper column = AssociatedResultSet . Columns [ i ] ;
102+ EditColumnMetadata metadata = AssociatedObjectMetadata . Columns [ i ] ;
103+ CellUpdate cell = newCells [ i ] ;
104+
105+ // Add the output columns regardless of whether the column is read only
106+ outClauseColumnNames . Add ( $ "inserted.{ metadata . EscapedName } ") ;
107+ declareColumns . Add ( $ "{ metadata . EscapedName } { ToSqlScript . FormatColumnType ( column , useSemanticEquivalent : true ) } ") ;
108+ selectColumns . Add ( metadata . EscapedName ) ;
109+
110+ // Continue if we're not inserting a value for this column
111+ if ( ! IsCellValueProvided ( column , cell , DefaultValues [ i ] ) )
112+ {
113+ continue ;
114+ }
115+
116+ // Add the input column
117+ inColumnNames . Add ( metadata . EscapedName ) ;
118+
119+ // Add the input values as parameters
120+ string paramName = $ "@Value{ RowId } _{ i } ";
121+ inValues . Add ( paramName ) ;
122+ inParameters . Add ( new SqlParameter ( paramName , column . SqlDbType ) { Value = cell . Value } ) ;
123+ }
124+
125+ // Put everything together into a single query
126+ // Step 1) Build a temp table for inserting output values into
127+ string tempTableName = $ "@Insert{ RowId } Output";
128+ string declareStatement = string . Format ( DeclareStatement , tempTableName , string . Join ( ", " , declareColumns ) ) ;
129+
130+ // Step 2) Build the insert statement
131+ string joinedOutClauseNames = string . Join ( ", " , outClauseColumnNames ) ;
132+ string insertStatement = inValues . Count > 0
133+ ? string . Format ( InsertOutputValuesStatement ,
134+ AssociatedObjectMetadata . EscapedMultipartName ,
135+ string . Join ( ", " , inColumnNames ) ,
136+ joinedOutClauseNames ,
137+ tempTableName ,
138+ string . Join ( ", " , inValues ) )
139+ : string . Format ( InsertOutputDefaultStatement ,
140+ AssociatedObjectMetadata . EscapedMultipartName ,
141+ joinedOutClauseNames ,
142+ tempTableName ) ;
143+
144+ // Step 3) Build the select statement
145+ string selectStatement = string . Format ( SelectStatement , string . Join ( ", " , selectColumns ) , tempTableName ) ;
93146
147+ // Step 4) Put it all together into a results object
148+ StringBuilder query = new StringBuilder ( ) ;
149+ query . AppendLine ( declareStatement ) ;
150+ query . AppendLine ( insertStatement ) ;
151+ query . Append ( selectStatement ) ;
152+
153+ // Build the command
94154 DbCommand command = connection . CreateCommand ( ) ;
95- command . CommandText = result . ScriptText ;
155+ command . CommandText = query . ToString ( ) ;
96156 command . CommandType = CommandType . Text ;
97- command . Parameters . AddRange ( result . ScriptParameters ) ;
157+ command . Parameters . AddRange ( inParameters . ToArray ( ) ) ;
98158
99159 return command ;
100160 }
@@ -123,7 +183,32 @@ public override EditRow GetEditRow(DbCellValue[] cachedRow)
123183 /// <returns>INSERT INTO statement</returns>
124184 public override string GetScript ( )
125185 {
126- return BuildInsertScript ( forCommand : false ) . ScriptText ;
186+ // Process the cells and columns
187+ List < string > inColumns = new List < string > ( ) ;
188+ List < string > inValues = new List < string > ( ) ;
189+ for ( int i = 0 ; i < AssociatedObjectMetadata . Columns . Length ; i ++ )
190+ {
191+ DbColumnWrapper column = AssociatedResultSet . Columns [ i ] ;
192+ CellUpdate cell = newCells [ i ] ;
193+
194+ // Continue if we're not inserting a value for this column
195+ if ( ! IsCellValueProvided ( column , cell , DefaultValues [ i ] ) )
196+ {
197+ continue ;
198+ }
199+
200+ // Column is provided
201+ inColumns . Add ( AssociatedObjectMetadata . Columns [ i ] . EscapedName ) ;
202+ inValues . Add ( ToSqlScript . FormatValue ( cell . AsDbCellValue , column ) ) ;
203+ }
204+
205+ // Build the insert statement
206+ return inValues . Count > 0
207+ ? string . Format ( InsertScriptValuesStatement ,
208+ AssociatedObjectMetadata . EscapedMultipartName ,
209+ string . Join ( ", " , inColumns ) ,
210+ string . Join ( ", " , inValues ) )
211+ : string . Format ( InsertScriptDefaultStatement , AssociatedObjectMetadata . EscapedMultipartName ) ;
127212 }
128213
129214 /// <summary>
@@ -173,111 +258,40 @@ public override EditUpdateCellResult SetCell(int columnId, string newValue)
173258 #endregion
174259
175260 /// <summary>
176- /// Generates an INSERT script that will insert this row
261+ /// Verifies the column and cell, ensuring a column that needs a value has one.
177262 /// </summary>
178- /// <param name="forCommand">
179- /// If <c>true</c> the script will be generated with an OUTPUT clause for returning all
180- /// values in the inserted row (including computed values). The script will also generate
181- /// parameters for inserting the values.
182- /// If <c>false</c> the script will not have an OUTPUT clause and will have the values
183- /// directly inserted into the script (with proper escaping, of course).
184- /// </param>
185- /// <returns>A script build result object with the script text and any parameters</returns>
263+ /// <param name="column">Column that will be inserted into</param>
264+ /// <param name="cell">Current cell value for this row</param>
265+ /// <param name="defaultCell">Default value for the column in this row</param>
186266 /// <exception cref="InvalidOperationException">
187- /// Thrown if there are columns that are not readonly, do not have default values, and were
188- /// not assigned values.
267+ /// Thrown if the column needs a value but it is not provided
189268 /// </exception>
190- private ScriptBuildResult BuildInsertScript ( bool forCommand )
191- {
192- // Process all the columns in this table
193- List < string > inValues = new List < string > ( ) ;
194- List < string > inColumns = new List < string > ( ) ;
195- List < string > outColumns = new List < string > ( ) ;
196- List < SqlParameter > sqlParameters = new List < SqlParameter > ( ) ;
197- for ( int i = 0 ; i < AssociatedObjectMetadata . Columns . Length ; i ++ )
269+ /// <returns>
270+ /// <c>true</c> If the column has a value provided
271+ /// <c>false</c> If the column does not have a value provided (column is read-only, has default, etc)
272+ /// </returns>
273+ private static bool IsCellValueProvided ( DbColumnWrapper column , CellUpdate cell , string defaultCell )
274+ {
275+ // Skip columns that cannot be updated
276+ if ( ! column . IsUpdatable )
198277 {
199- DbColumnWrapper column = AssociatedResultSet . Columns [ i ] ;
200- CellUpdate cell = newCells [ i ] ;
201-
202- // Add an out column if we're doing this for a command
203- if ( forCommand )
204- {
205- outColumns . Add ( $ "inserted.{ ToSqlScript . FormatIdentifier ( column . ColumnName ) } ") ;
206- }
278+ return false ;
279+ }
207280
208- // Skip columns that cannot be updated
209- if ( ! column . IsUpdatable )
281+ // Make sure a value was provided for the cell
282+ if ( cell == null )
283+ {
284+ // If the column is not nullable and there is not default defined, then fail
285+ if ( ! column . AllowDBNull . HasTrue ( ) && defaultCell == null )
210286 {
211- continue ;
287+ throw new InvalidOperationException ( SR . EditDataCreateScriptMissingValue ( column . ColumnName ) ) ;
212288 }
213-
214- // Make sure a value was provided for the cell
215- if ( cell == null )
216- {
217- // If the column is not nullable and there is no default defined, then fail
218- if ( ! column . AllowDBNull . HasTrue ( ) && DefaultValues [ i ] == null )
219- {
220- throw new InvalidOperationException ( SR . EditDataCreateScriptMissingValue ( column . ColumnName ) ) ;
221- }
222289
223- // There is a default value (or omitting the value is fine), so trust the db will apply it correctly
224- continue ;
225- }
226-
227- // Add the input values
228- if ( forCommand )
229- {
230- // Since this script is for command use, add parameter for the input value to the list
231- string paramName = $ "@Value{ RowId } _{ i } ";
232- inValues . Add ( paramName ) ;
233-
234- SqlParameter param = new SqlParameter ( paramName , cell . Column . SqlDbType ) { Value = cell . Value } ;
235- sqlParameters . Add ( param ) ;
236- }
237- else
238- {
239- // This script isn't for command use, add the value, formatted for insertion
240- inValues . Add ( ToSqlScript . FormatValue ( cell . Value , column ) ) ;
241- }
242-
243- // Add the column to the in columns
244- inColumns . Add ( ToSqlScript . FormatIdentifier ( column . ColumnName ) ) ;
245- }
246-
247- // Begin the script (ie, INSERT INTO blah)
248- StringBuilder queryBuilder = new StringBuilder ( ) ;
249- queryBuilder . AppendFormat ( InsertScriptStart , AssociatedObjectMetadata . EscapedMultipartName ) ;
250-
251- // Add the input columns (if there are any)
252- if ( inColumns . Count > 0 )
253- {
254- string joinedInColumns = string . Join ( ", " , inColumns ) ;
255- queryBuilder . AppendFormat ( InsertScriptColumns , joinedInColumns ) ;
256- }
257-
258- // Add the output columns (this will be empty if we are not building for command)
259- if ( outColumns . Count > 0 )
260- {
261- string joinedOutColumns = string . Join ( ", " , outColumns ) ;
262- queryBuilder . AppendFormat ( InsertScriptOut , joinedOutColumns ) ;
263- }
264-
265- // Add the input values (if there any) or use the default values
266- if ( inValues . Count > 0 )
267- {
268- string joinedInValues = string . Join ( ", " , inValues ) ;
269- queryBuilder . AppendFormat ( InsertScriptValues , joinedInValues ) ;
270- }
271- else
272- {
273- queryBuilder . AppendFormat ( InsertScriptDefault ) ;
290+ // There is a default value (or omitting the value is fine), so trust the db will apply it correctly
291+ return false ;
274292 }
275293
276- return new ScriptBuildResult
277- {
278- ScriptText = queryBuilder . ToString ( ) ,
279- ScriptParameters = sqlParameters . ToArray ( )
280- } ;
294+ return true ;
281295 }
282296
283297 private EditCell GetEditCell ( CellUpdate cell , int index )
@@ -301,11 +315,5 @@ private EditCell GetEditCell(CellUpdate cell, int index)
301315 }
302316 return new EditCell ( dbCell , isDirty : true ) ;
303317 }
304-
305- private class ScriptBuildResult
306- {
307- public string ScriptText { get ; set ; }
308- public SqlParameter [ ] ScriptParameters { get ; set ; }
309- }
310318 }
311319}
0 commit comments