2 // System.Data.Common.DbCommandBuilder
5 // Tim Coleman (tim@timcoleman.com)
7 // Copyright (C) Tim Coleman, 2003
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.
33 #if NET_2_0 || TARGET_JVM
35 using System.ComponentModel;
39 namespace System.Data.Common {
40 public abstract class DbCommandBuilder : Component
42 bool _setAllValues = false;
43 bool _disposed = false;
45 DataTable _dbSchemaTable;
46 DbDataAdapter _dbDataAdapter = null;
47 private CatalogLocation _catalogLocation = CatalogLocation.Start;
48 private ConflictOption _conflictOption;
50 private string _tableName;
51 private string _catalogSeperator = ".";
52 private string _quotePrefix;
53 private string _quoteSuffix;
54 private string _schemaSeperator = ".";
55 private DbCommand _dbCommand = null;
57 // Used to construct WHERE clauses
58 static readonly string clause1 = "({0} = 1 AND {1} IS NULL)";
59 static readonly string clause2 = "({0} = {1})";
61 DbCommand _deleteCommand;
62 DbCommand _insertCommand;
63 DbCommand _updateCommand;
67 protected DbCommandBuilder ()
71 #endregion // Constructors
75 private void BuildCache (bool closeConnection)
77 DbCommand sourceCommand = SourceCommand;
78 if (sourceCommand == null)
79 throw new InvalidOperationException ("The DataAdapter.SelectCommand property needs to be initialized.");
80 DbConnection connection = sourceCommand.Connection;
81 if (connection == null)
82 throw new InvalidOperationException ("The DataAdapter.SelectCommand.Connection property needs to be initialized.");
84 if (_dbSchemaTable == null) {
85 if (connection.State == ConnectionState.Open)
86 closeConnection = false;
90 DbDataReader reader = sourceCommand.ExecuteReader (CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo);
91 _dbSchemaTable = reader.GetSchemaTable ();
95 BuildInformation (_dbSchemaTable);
99 private string QuotedTableName {
100 get { return GetQuotedString (_tableName); }
103 private string GetQuotedString (string value)
105 if (value == String.Empty || value == null)
107 if (_quotePrefix == String.Empty && _quoteSuffix == String.Empty)
109 return String.Format ("{0}{1}{2}", _quotePrefix, value, _quoteSuffix);
112 private void BuildInformation (DataTable schemaTable)
114 _tableName = String.Empty;
115 foreach (DataRow schemaRow in schemaTable.Rows) {
116 if (schemaRow.IsNull ("BaseTableName") || (string) schemaRow ["BaseTableName"] == String.Empty)
119 if (_tableName == String.Empty)
120 _tableName = (string) schemaRow ["BaseTableName"];
121 else if (_tableName != (string) schemaRow["BaseTableName"])
122 throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
124 if (_tableName == String.Empty)
125 throw new InvalidOperationException ("Dynamic SQL generation is not supported with no base table.");
126 _dbSchemaTable = schemaTable;
129 private bool IncludedInInsert (DataRow schemaRow)
131 // If the parameter has one of these properties, then we don't include it in the insert:
132 // AutoIncrement, Hidden, Expression, RowVersion, ReadOnly
134 if (!schemaRow.IsNull ("IsAutoIncrement") && (bool) schemaRow ["IsAutoIncrement"])
136 // if (!schemaRow.IsNull ("IsHidden") && (bool) schemaRow ["IsHidden"])
138 if (!schemaRow.IsNull ("IsExpression") && (bool) schemaRow ["IsExpression"])
140 if (!schemaRow.IsNull ("IsRowVersion") && (bool) schemaRow ["IsRowVersion"])
142 if (!schemaRow.IsNull ("IsReadOnly") && (bool) schemaRow ["IsReadOnly"])
147 private bool IncludedInUpdate (DataRow schemaRow)
149 // If the parameter has one of these properties, then we don't include it in the insert:
150 // AutoIncrement, Hidden, RowVersion
152 if (!schemaRow.IsNull ("IsAutoIncrement") && (bool) schemaRow ["IsAutoIncrement"])
154 // if (!schemaRow.IsNull ("IsHidden") && (bool) schemaRow ["IsHidden"])
156 if (!schemaRow.IsNull ("IsRowVersion") && (bool) schemaRow ["IsRowVersion"])
158 if (!schemaRow.IsNull ("IsExpression") && (bool) schemaRow ["IsExpression"])
160 if (!schemaRow.IsNull ("IsReadOnly") && (bool) schemaRow ["IsReadOnly"])
166 private bool IncludedInWhereClause (DataRow schemaRow)
168 if ((bool) schemaRow ["IsLong"])
173 private DbCommand CreateDeleteCommand (bool option)
175 // If no table was found, then we can't do an delete
176 if (QuotedTableName == String.Empty)
179 CreateNewCommand (ref _deleteCommand);
181 string command = String.Format ("DELETE FROM {0}", QuotedTableName);
182 StringBuilder whereClause = new StringBuilder ();
183 bool keyFound = false;
186 foreach (DataRow schemaRow in _dbSchemaTable.Rows) {
187 if ((bool)schemaRow["IsExpression"] == true)
189 if (!IncludedInWhereClause (schemaRow))
192 if (whereClause.Length > 0)
193 whereClause.Append (" AND ");
195 bool isKey = (bool) schemaRow ["IsKey"];
196 DbParameter parameter = null;
201 //ms.net 1.1 generates the null check for columns even if AllowDBNull is false
202 //while ms.net 2.0 does not. Anyways, since both forms are logically equivalent
203 //following the 2.0 approach
204 bool allowNull = (bool) schemaRow ["AllowDBNull"];
205 if (!isKey && allowNull) {
206 parameter = _deleteCommand.CreateParameter ();
208 parameter.ParameterName = String.Format ("@{0}",
209 schemaRow ["BaseColumnName"]);
211 parameter.ParameterName = String.Format ("@p{0}", parmIndex++);
213 String sourceColumnName = (string) schemaRow ["BaseColumnName"];
216 whereClause.Append ("(");
217 whereClause.Append (String.Format (clause1, parameter.ParameterName,
218 GetQuotedString (sourceColumnName)));
219 whereClause.Append (" OR ");
224 index = CreateParameter (_deleteCommand, schemaRow);
226 index = CreateParameter (_deleteCommand, parmIndex++, schemaRow);
228 parameter = _deleteCommand.Parameters [index];
229 parameter.SourceVersion = DataRowVersion.Original;
231 whereClause.Append (String.Format (clause2, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
233 if (!isKey && allowNull)
234 whereClause.Append (")");
237 throw new InvalidOperationException ("Dynamic SQL generation for the DeleteCommand is not supported against a SelectCommand that does not return any key column information.");
239 // We're all done, so bring it on home
240 string sql = String.Format ("{0} WHERE ({1})", command, whereClause.ToString ());
241 _deleteCommand.CommandText = sql;
242 _dbCommand = _deleteCommand;
243 return _deleteCommand;
246 private DbCommand CreateInsertCommand (bool option)
248 if (QuotedTableName == String.Empty)
251 CreateNewCommand (ref _insertCommand);
253 string command = String.Format ("INSERT INTO {0}", QuotedTableName);
255 StringBuilder columns = new StringBuilder ();
256 StringBuilder values = new StringBuilder ();
259 foreach (DataRow schemaRow in _dbSchemaTable.Rows) {
260 if (!IncludedInInsert (schemaRow))
264 columns.Append (", ");
265 values.Append (", ");
270 index = CreateParameter (_insertCommand, schemaRow);
272 index = CreateParameter (_insertCommand, parmIndex++, schemaRow);
274 DbParameter parameter = _insertCommand.Parameters [index];
275 parameter.SourceVersion = DataRowVersion.Current;
277 columns.Append (GetQuotedString (parameter.SourceColumn));
278 values.Append (parameter.ParameterName);
281 sql = String.Format ("{0} ({1}) VALUES ({2})", command, columns.ToString (), values.ToString ());
282 _insertCommand.CommandText = sql;
283 _dbCommand = _insertCommand;
284 return _insertCommand;
287 private void CreateNewCommand (ref DbCommand command)
289 DbCommand sourceCommand = SourceCommand;
290 if (command == null) {
291 command = sourceCommand.Connection.CreateCommand ();
292 command.CommandTimeout = sourceCommand.CommandTimeout;
293 command.Transaction = sourceCommand.Transaction;
295 command.CommandType = CommandType.Text;
296 command.UpdatedRowSource = UpdateRowSource.None;
297 command.Parameters.Clear ();
300 private DbCommand CreateUpdateCommand (bool option)
302 // If no table was found, then we can't do an update
303 if (QuotedTableName == String.Empty)
306 CreateNewCommand (ref _updateCommand);
308 string command = String.Format ("UPDATE {0} SET ", QuotedTableName);
309 StringBuilder columns = new StringBuilder ();
310 StringBuilder whereClause = new StringBuilder ();
312 bool keyFound = false;
314 // First, create the X=Y list for UPDATE
315 foreach (DataRow schemaRow in _dbSchemaTable.Rows) {
316 if (!IncludedInUpdate (schemaRow))
318 if (columns.Length > 0)
319 columns.Append (", ");
324 index = CreateParameter (_updateCommand, schemaRow);
326 index = CreateParameter (_updateCommand, parmIndex++, schemaRow);
328 DbParameter parameter = _updateCommand.Parameters [index];
329 parameter.SourceVersion = DataRowVersion.Current;
331 columns.Append (String.Format ("{0} = {1}", GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
334 // Now, create the WHERE clause. This may be optimizable, but it would be ugly to incorporate
335 // into the loop above. "Premature optimization is the root of all evil." -- Knuth
336 foreach (DataRow schemaRow in _dbSchemaTable.Rows) {
337 if ((bool) schemaRow ["IsExpression"] == true)
340 if (!IncludedInWhereClause (schemaRow))
343 if (whereClause.Length > 0)
344 whereClause.Append (" AND ");
346 bool isKey = (bool) schemaRow ["IsKey"];
347 DbParameter parameter = null;
352 //ms.net 1.1 generates the null check for columns even if AllowDBNull is false
353 //while ms.net 2.0 does not. Anyways, since both forms are logically equivalent
354 //following the 2.0 approach
355 bool allowNull = (bool) schemaRow ["AllowDBNull"];
357 if (!isKey && allowNull) {
358 parameter = _updateCommand.CreateParameter ();
360 parameter.ParameterName = String.Format ("@{0}",
361 schemaRow ["BaseColumnName"]);
363 parameter.ParameterName = String.Format ("@p{0}", parmIndex++);
366 whereClause.Append ("(");
367 whereClause.Append (String.Format (clause1, parameter.ParameterName,
368 GetQuotedString ((string) schemaRow ["BaseColumnName"])));
369 whereClause.Append (" OR ");
373 index = CreateParameter (_updateCommand, schemaRow);
375 index = CreateParameter (_updateCommand, parmIndex++, schemaRow);
377 parameter = _updateCommand.Parameters [index];
378 parameter.SourceVersion = DataRowVersion.Original;
380 whereClause.Append (String.Format (clause2, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
382 if (!isKey && allowNull)
383 whereClause.Append (")");
386 throw new InvalidOperationException ("Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not return any key column information.");
388 // We're all done, so bring it on home
389 string sql = String.Format ("{0}{1} WHERE ({2})", command, columns.ToString (), whereClause.ToString ());
390 _updateCommand.CommandText = sql;
391 _dbCommand = _updateCommand;
392 return _updateCommand;
395 private int CreateParameter (DbCommand _dbCommand, int parmIndex, DataRow schemaRow)
397 DbParameter parameter = _dbCommand.CreateParameter ();
398 parameter.ParameterName = String.Format ("@p{0}", parmIndex);
399 parameter.SourceColumn = (string) schemaRow ["BaseColumnName"];
400 parameter.Size = (int) schemaRow ["ColumnSize"];
401 return _dbCommand.Parameters.Add (parameter);
404 private int CreateParameter (DbCommand _dbCommand, DataRow schemaRow)
406 DbParameter parameter = _dbCommand.CreateParameter ();
407 parameter.ParameterName = String.Format ("@{0}",
408 schemaRow ["BaseColumnName"]);
409 parameter.SourceColumn = (string) schemaRow ["BaseColumnName"];
410 parameter.Size = (int) schemaRow ["ColumnSize"];
411 return _dbCommand.Parameters.Add (parameter);
414 [DefaultValue (CatalogLocation.Start)]
415 public virtual CatalogLocation CatalogLocation {
416 get { return _catalogLocation; }
417 set { _catalogLocation = value; }
421 public virtual string CatalogSeparator {
422 get { return _catalogSeperator; }
423 set { if (value != null) _catalogSeperator = value; }
426 [DefaultValue (ConflictOption.CompareAllSearchableValues)]
427 public virtual ConflictOption ConflictOption {
428 get { return _conflictOption; }
429 set { _conflictOption = value; }
432 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
434 public DbDataAdapter DataAdapter {
435 get { return _dbDataAdapter; }
436 set { if (value != null) _dbDataAdapter = value; }
440 public virtual string QuotePrefix {
441 get { return _quotePrefix; }
442 set { if (value != null) _quotePrefix = value; }
446 public virtual string QuoteSuffix {
447 get { return _quoteSuffix; }
448 set { if (value != null) _quoteSuffix = value; }
452 public virtual string SchemaSeparator {
453 get { return _schemaSeperator; }
454 set { if (value != null) _schemaSeperator = value; }
457 [DefaultValue (false)]
458 public bool SetAllValues {
459 get { return _setAllValues; }
460 set { _setAllValues = value; }
463 private DbCommand SourceCommand {
465 if (_dbDataAdapter != null)
466 return _dbDataAdapter.SelectCommand;
470 #endregion // Properties
474 protected abstract void ApplyParameterInfo (DbParameter parameter,
476 StatementType statementType,
479 protected override void Dispose (bool disposing)
483 if (_insertCommand != null)
484 _insertCommand.Dispose ();
485 if (_deleteCommand != null)
486 _deleteCommand.Dispose ();
487 if (_updateCommand != null)
488 _updateCommand.Dispose ();
489 if (_dbSchemaTable != null)
490 _dbSchemaTable.Dispose ();
496 public DbCommand GetDeleteCommand ()
499 if (_deleteCommand == null)
500 return CreateDeleteCommand (false);
501 return _deleteCommand;
504 public DbCommand GetDeleteCommand (bool option)
507 if (_deleteCommand == null)
508 return CreateDeleteCommand (option);
509 return _deleteCommand;
512 public DbCommand GetInsertCommand ()
515 if (_insertCommand == null)
516 return CreateInsertCommand (false);
517 return _insertCommand;
520 public DbCommand GetInsertCommand (bool option)
523 if (_insertCommand == null)
524 return CreateInsertCommand (option);
525 return _insertCommand;
528 public DbCommand GetUpdateCommand ()
531 if (_updateCommand == null)
532 return CreateUpdateCommand (false);
533 return _updateCommand;
536 public DbCommand GetUpdateCommand (bool option)
539 if (_updateCommand == null)
540 return CreateUpdateCommand (option);
541 return _updateCommand;
544 protected virtual DbCommand InitializeCommand (DbCommand command)
546 if (_dbCommand == null) {
547 _dbCommand = SourceCommand;
549 _dbCommand.CommandTimeout = 30;
550 _dbCommand.Transaction = null;
551 _dbCommand.CommandType = CommandType.Text;
552 _dbCommand.UpdatedRowSource = UpdateRowSource.None;
558 public virtual string QuoteIdentifier (string unquotedIdentifier)
560 if (unquotedIdentifier == null) {
561 throw new ArgumentNullException("Unquoted identifier parameter cannot be null");
563 return String.Format ("{0}{1}{2}", this.QuotePrefix, unquotedIdentifier, this.QuoteSuffix);
566 public virtual string UnquoteIdentifier (string quotedIdentifier)
568 if (quotedIdentifier == null) {
569 throw new ArgumentNullException ("Quoted identifier parameter cannot be null");
571 string unquotedIdentifier = quotedIdentifier.Trim ();
572 if (unquotedIdentifier.StartsWith (this.QuotePrefix)) {
573 unquotedIdentifier = unquotedIdentifier.Remove (0, 1);
575 if (unquotedIdentifier.EndsWith (this.QuoteSuffix)) {
576 unquotedIdentifier = unquotedIdentifier.Remove (unquotedIdentifier.Length - 1, 1);
578 return unquotedIdentifier;
581 public virtual void RefreshSchema ()
583 _tableName = String.Empty;
584 _dbSchemaTable = null;
585 CreateNewCommand (ref _deleteCommand);
586 CreateNewCommand (ref _updateCommand);
587 CreateNewCommand (ref _insertCommand);
590 protected void RowUpdatingHandler (RowUpdatingEventArgs args)
592 if (args.Command != null)
595 switch (args.StatementType) {
596 case StatementType.Insert:
597 args.Command = GetInsertCommand ();
599 case StatementType.Update:
600 args.Command = GetUpdateCommand ();
602 case StatementType.Delete:
603 args.Command = GetDeleteCommand ();
606 } catch (Exception e) {
608 args.Status = UpdateStatus.ErrorsOccurred;
612 protected abstract string GetParameterName (int parameterOrdinal);
613 protected abstract string GetParameterName (String parameterName);
614 protected abstract string GetParameterPlaceholder (int parameterOrdinal);
616 protected abstract void SetRowUpdatingHandler (DbDataAdapter adapter);
618 protected virtual DataTable GetSchemaTable (DbCommand cmd)
620 using (DbDataReader rdr = cmd.ExecuteReader ())
621 return rdr.GetSchemaTable ();
624 #endregion // Methods