2 // System.Data.SqlClient.SqlCommandBuilder.cs
5 // Tim Coleman (tim@timcoleman.com)
7 // Copyright (C) Tim Coleman, 2002
11 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System
.Collections
;
35 using System
.ComponentModel
;
37 using System
.Data
.Common
;
40 namespace System
.Data
.SqlClient
{
41 public sealed class SqlCommandBuilder
: Component
45 bool disposed
= false;
47 DataTable dbSchemaTable
;
48 SqlDataAdapter adapter
;
54 SqlCommand deleteCommand
;
55 SqlCommand insertCommand
;
56 SqlCommand updateCommand
;
58 // Used to construct WHERE clauses
59 static readonly string clause1
= "({0} IS NULL AND {1} IS NULL)";
60 static readonly string clause2
= "({0} = {1})";
66 public SqlCommandBuilder ()
70 quoteSuffix
= String
.Empty
;
71 quotePrefix
= String
.Empty
;
74 public SqlCommandBuilder (SqlDataAdapter adapter
)
77 DataAdapter
= adapter
;
80 #endregion // Constructors
84 [DataSysDescription ("The DataAdapter for which to automatically generate SqlCommands")]
86 public SqlDataAdapter DataAdapter
{
87 get { return adapter; }
91 adapter
.RowUpdating
+= new SqlRowUpdatingEventHandler (RowUpdatingHandler
);
95 private string QuotedTableName
{
96 get { return GetQuotedString (tableName); }
100 [DataSysDescription ("The character used in a text command as the opening quote for quoting identifiers that contain special characters.")]
101 [DesignerSerializationVisibility (DesignerSerializationVisibility
.Hidden
)]
102 public string QuotePrefix
{
103 get { return quotePrefix; }
105 if (dbSchemaTable
!= null)
106 throw new InvalidOperationException ("The QuotePrefix and QuoteSuffix properties cannot be changed once an Insert, Update, or Delete command has been generated.");
112 [DataSysDescription ("The character used in a text command as the closing quote for quoting identifiers that contain special characters.")]
113 [DesignerSerializationVisibility (DesignerSerializationVisibility
.Hidden
)]
114 public string QuoteSuffix
{
115 get { return quoteSuffix; }
117 if (dbSchemaTable
!= null)
118 throw new InvalidOperationException ("The QuotePrefix and QuoteSuffix properties cannot be changed once an Insert, Update, or Delete command has been generated.");
123 private SqlCommand SourceCommand
{
126 return adapter
.SelectCommand
;
131 #endregion // Properties
135 private void BuildCache (bool closeConnection
)
137 SqlCommand sourceCommand
= SourceCommand
;
138 if (sourceCommand
== null)
139 throw new InvalidOperationException ("The DataAdapter.SelectCommand property needs to be initialized.");
140 SqlConnection connection
= sourceCommand
.Connection
;
141 if (connection
== null)
142 throw new InvalidOperationException ("The DataAdapter.SelectCommand.Connection property needs to be initialized.");
144 if (dbSchemaTable
== null) {
145 if (connection
.State
== ConnectionState
.Open
)
146 closeConnection
= false;
150 SqlDataReader reader
= sourceCommand
.ExecuteReader (CommandBehavior
.SchemaOnly
| CommandBehavior
.KeyInfo
);
151 dbSchemaTable
= reader
.GetSchemaTable ();
155 BuildInformation (dbSchemaTable
);
159 private void BuildInformation (DataTable schemaTable
)
161 tableName
= String
.Empty
;
162 foreach (DataRow schemaRow
in schemaTable
.Rows
) {
163 if (tableName
== String
.Empty
)
164 tableName
= schemaRow
.IsNull ("BaseTableName") ? null : (string) schemaRow
["BaseTableName"];
165 else if (schemaRow
.IsNull ("BaseTableName")) {
166 if (tableName
!= null)
167 throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
168 } else if (tableName
!= (string) schemaRow
["BaseTableName"])
169 throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
171 dbSchemaTable
= schemaTable
;
174 private SqlCommand
CreateDeleteCommand (DataRow row
, DataTableMapping tableMapping
)
176 // If no table was found, then we can't do an delete
177 if (QuotedTableName
== String
.Empty
)
181 CreateNewCommand (ref deleteCommand
);
183 string command
= String
.Format ("DELETE FROM {0} ", QuotedTableName
);
184 StringBuilder columns
= new StringBuilder ();
185 StringBuilder whereClause
= new StringBuilder ();
186 string dsColumnName
= String
.Empty
;
187 bool keyFound
= false;
190 foreach (DataRow schemaRow
in dbSchemaTable
.Rows
) {
191 if (!IncludedInWhereClause (schemaRow
))
194 if (whereClause
.Length
> 0)
195 whereClause
.Append (" AND ");
197 bool isKey
= (bool) schemaRow
["IsKey"];
198 SqlParameter parameter
= null;
201 parameter
= deleteCommand
.Parameters
.Add (CreateParameter (parmIndex
++, schemaRow
));
203 dsColumnName
= tableMapping
.ColumnMappings
[parameter
.SourceColumn
].DataSetColumn
;
205 parameter
.Value
= row
[dsColumnName
, DataRowVersion
.Current
];
206 whereClause
.Append ("(");
207 whereClause
.Append (String
.Format (clause1
, GetQuotedString (parameter
.SourceColumn
), parameter
.ParameterName
));
208 whereClause
.Append (" OR ");
213 parameter
= deleteCommand
.Parameters
.Add (CreateParameter (parmIndex
++, schemaRow
));
215 dsColumnName
= tableMapping
.ColumnMappings
[parameter
.SourceColumn
].DataSetColumn
;
217 parameter
.Value
= row
[dsColumnName
, DataRowVersion
.Current
];
219 whereClause
.Append (String
.Format (clause2
, GetQuotedString (parameter
.SourceColumn
), parameter
.ParameterName
));
222 whereClause
.Append (")");
225 throw new InvalidOperationException ("Dynamic SQL generation for the DeleteCommand is not supported against a SelectCommand that does not return any key column information.");
227 // We're all done, so bring it on home
228 string sql
= String
.Format ("{0} WHERE ( {1} )", command
, whereClause
.ToString ());
229 deleteCommand
.CommandText
= sql
;
230 return deleteCommand
;
233 private SqlCommand
CreateInsertCommand (DataRow row
, DataTableMapping tableMapping
)
235 if (QuotedTableName
== String
.Empty
)
238 CreateNewCommand (ref insertCommand
);
240 string command
= String
.Format ("INSERT INTO {0}", QuotedTableName
);
242 StringBuilder columns
= new StringBuilder ();
243 StringBuilder values
= new StringBuilder ();
244 string dsColumnName
= String
.Empty
;
247 foreach (DataRow schemaRow
in dbSchemaTable
.Rows
) {
248 if (!IncludedInInsert (schemaRow
))
252 columns
.Append (" , ");
253 values
.Append (" , ");
256 SqlParameter parameter
= insertCommand
.Parameters
.Add (CreateParameter (parmIndex
++, schemaRow
));
258 dsColumnName
= tableMapping
.ColumnMappings
[parameter
.SourceColumn
].DataSetColumn
;
260 parameter
.Value
= row
[dsColumnName
];
262 columns
.Append (GetQuotedString (parameter
.SourceColumn
));
263 values
.Append (parameter
.ParameterName
);
266 sql
= String
.Format ("{0}( {1} ) VALUES ( {2} )", command
, columns
.ToString (), values
.ToString ());
267 insertCommand
.CommandText
= sql
;
268 return insertCommand
;
271 private void CreateNewCommand (ref SqlCommand command
)
273 SqlCommand sourceCommand
= SourceCommand
;
274 if (command
== null) {
275 command
= sourceCommand
.Connection
.CreateCommand ();
276 command
.CommandTimeout
= sourceCommand
.CommandTimeout
;
277 command
.Transaction
= sourceCommand
.Transaction
;
279 command
.CommandType
= CommandType
.Text
;
280 command
.UpdatedRowSource
= UpdateRowSource
.None
;
283 private SqlCommand
CreateUpdateCommand (DataRow row
, DataTableMapping tableMapping
)
285 // If no table was found, then we can't do an update
286 if (QuotedTableName
== String
.Empty
)
289 CreateNewCommand (ref updateCommand
);
291 string command
= String
.Format ("UPDATE {0} SET ", QuotedTableName
);
292 StringBuilder columns
= new StringBuilder ();
293 StringBuilder whereClause
= new StringBuilder ();
295 string dsColumnName
= String
.Empty
;
296 bool keyFound
= false;
298 // First, create the X=Y list for UPDATE
299 foreach (DataRow schemaRow
in dbSchemaTable
.Rows
) {
300 if (columns
.Length
> 0)
301 columns
.Append (" , ");
303 SqlParameter parameter
= updateCommand
.Parameters
.Add (CreateParameter (parmIndex
++, schemaRow
));
305 dsColumnName
= tableMapping
.ColumnMappings
[parameter
.SourceColumn
].DataSetColumn
;
307 parameter
.Value
= row
[dsColumnName
, DataRowVersion
.Proposed
];
309 columns
.Append (String
.Format ("{0} = {1}", GetQuotedString (parameter
.SourceColumn
), parameter
.ParameterName
));
312 // Now, create the WHERE clause. This may be optimizable, but it would be ugly to incorporate
313 // into the loop above. "Premature optimization is the root of all evil." -- Knuth
314 foreach (DataRow schemaRow
in dbSchemaTable
.Rows
) {
315 if (!IncludedInWhereClause (schemaRow
))
318 if (whereClause
.Length
> 0)
319 whereClause
.Append (" AND ");
321 bool isKey
= (bool) schemaRow
["IsKey"];
322 SqlParameter parameter
= null;
326 parameter
= updateCommand
.Parameters
.Add (CreateParameter (parmIndex
++, schemaRow
));
328 dsColumnName
= tableMapping
.ColumnMappings
[parameter
.SourceColumn
].DataSetColumn
;
330 parameter
.Value
= row
[dsColumnName
];
332 whereClause
.Append ("(");
333 whereClause
.Append (String
.Format (clause1
, GetQuotedString (parameter
.SourceColumn
), parameter
.ParameterName
));
334 whereClause
.Append (" OR ");
339 parameter
= updateCommand
.Parameters
.Add (CreateParameter (parmIndex
++, schemaRow
));
341 dsColumnName
= tableMapping
.ColumnMappings
[parameter
.SourceColumn
].DataSetColumn
;
343 parameter
.Value
= row
[dsColumnName
];
345 whereClause
.Append (String
.Format (clause2
, GetQuotedString (parameter
.SourceColumn
), parameter
.ParameterName
));
348 whereClause
.Append (")");
351 throw new InvalidOperationException ("Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not return any key column information.");
353 // We're all done, so bring it on home
354 string sql
= String
.Format ("{0}{1} WHERE ( {2} )", command
, columns
.ToString (), whereClause
.ToString ());
355 updateCommand
.CommandText
= sql
;
356 return updateCommand
;
359 private SqlParameter
CreateParameter (int parmIndex
, DataRow schemaRow
)
361 string name
= String
.Format ("@p{0}", parmIndex
);
362 string sourceColumn
= (string) schemaRow
["BaseColumnName"];
363 SqlDbType sqlDbType
= (SqlDbType
) schemaRow
["ProviderType"];
364 int size
= (int) schemaRow
["ColumnSize"];
366 return new SqlParameter (name
, sqlDbType
, size
, sourceColumn
);
369 public static void DeriveParameters (SqlCommand command
)
371 command
.DeriveParameters ();
374 protected override void Dispose (bool disposing
)
378 if (insertCommand
!= null)
379 insertCommand
.Dispose ();
380 if (deleteCommand
!= null)
381 deleteCommand
.Dispose ();
382 if (updateCommand
!= null)
383 updateCommand
.Dispose ();
384 if (dbSchemaTable
!= null)
385 dbSchemaTable
.Dispose ();
391 public SqlCommand
GetDeleteCommand ()
394 return CreateDeleteCommand (null, null);
397 public SqlCommand
GetInsertCommand ()
400 return CreateInsertCommand (null, null);
403 private string GetQuotedString (string value)
405 if (value == String
.Empty
|| value == null)
407 if (quotePrefix
== String
.Empty
&& quoteSuffix
== String
.Empty
)
409 return String
.Format ("{0}{1}{2}", quotePrefix
, value, quoteSuffix
);
412 public SqlCommand
GetUpdateCommand ()
415 return CreateUpdateCommand (null, null);
418 private bool IncludedInInsert (DataRow schemaRow
)
420 // If the parameter has one of these properties, then we don't include it in the insert:
421 // AutoIncrement, Hidden, Expression, RowVersion, ReadOnly
423 if (!schemaRow
.IsNull ("IsAutoIncrement") && (bool) schemaRow
["IsAutoIncrement"])
425 if (!schemaRow
.IsNull ("IsHidden") && (bool) schemaRow
["IsHidden"])
427 if (!schemaRow
.IsNull ("IsExpression") && (bool) schemaRow
["IsExpression"])
429 if (!schemaRow
.IsNull ("IsRowVersion") && (bool) schemaRow
["IsRowVersion"])
431 if (!schemaRow
.IsNull ("IsReadOnly") && (bool) schemaRow
["IsReadOnly"])
436 private bool IncludedInUpdate (DataRow schemaRow
)
438 // If the parameter has one of these properties, then we don't include it in the insert:
439 // AutoIncrement, Hidden, RowVersion
441 if ((bool) schemaRow
["IsAutoIncrement"])
443 if ((bool) schemaRow
["IsHidden"])
445 if ((bool) schemaRow
["IsRowVersion"])
450 private bool IncludedInWhereClause (DataRow schemaRow
)
452 if ((bool) schemaRow
["IsLong"])
457 [MonoTODO ("Figure out what else needs to be cleaned up when we refresh.")]
458 public void RefreshSchema ()
460 tableName
= String
.Empty
;
461 dbSchemaTable
= null;
464 #endregion // Methods
466 #region Event Handlers
468 private void RowUpdatingHandler (object sender
, SqlRowUpdatingEventArgs e
)
470 if (e
.Status
!= UpdateStatus
.Continue
)
473 switch (e
.StatementType
) {
474 case StatementType
.Delete
:
475 deleteCommand
= e
.Command
;
477 case StatementType
.Insert
:
478 insertCommand
= e
.Command
;
480 case StatementType
.Update
:
481 updateCommand
= e
.Command
;
490 switch (e
.StatementType
) {
491 case StatementType
.Delete
:
492 e
.Command
= CreateDeleteCommand (e
.Row
, e
.TableMapping
);
493 e
.Status
= UpdateStatus
.Continue
;
495 case StatementType
.Insert
:
496 e
.Command
= CreateInsertCommand (e
.Row
, e
.TableMapping
);
497 e
.Status
= UpdateStatus
.Continue
;
499 case StatementType
.Update
:
500 e
.Command
= CreateUpdateCommand (e
.Row
, e
.TableMapping
);
501 e
.Status
= UpdateStatus
.Continue
;
505 if (e
.Command
!= null && e
.Row
!= null) {
506 e
.Row
.AcceptChanges ();
507 e
.Status
= UpdateStatus
.SkipCurrentRow
;
510 catch (Exception exception
) {
511 e
.Errors
= exception
;
512 e
.Status
= UpdateStatus
.ErrorsOccurred
;
516 #endregion // Event Handlers