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 (!schemaRow.IsNull ("IsExpression") && (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 ");
222 parameter = CreateParameter (_deleteCommand, String.Format ("@{0}", option ? schemaRow ["BaseColumnName"] : "p" + parmIndex++), schemaRow);
223 parameter.SourceVersion = DataRowVersion.Original;
225 whereClause.Append (String.Format (clause2, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
227 if (!isKey && allowNull)
228 whereClause.Append (")");
231 throw new InvalidOperationException ("Dynamic SQL generation for the DeleteCommand is not supported against a SelectCommand that does not return any key column information.");
233 // We're all done, so bring it on home
234 string sql = String.Format ("{0} WHERE ({1})", command, whereClause.ToString ());
235 _deleteCommand.CommandText = sql;
236 _dbCommand = _deleteCommand;
237 return _deleteCommand;
240 private DbCommand CreateInsertCommand (bool option)
242 if (QuotedTableName == String.Empty)
245 CreateNewCommand (ref _insertCommand);
247 string command = String.Format ("INSERT INTO {0}", QuotedTableName);
249 StringBuilder columns = new StringBuilder ();
250 StringBuilder values = new StringBuilder ();
253 foreach (DataRow schemaRow in _dbSchemaTable.Rows) {
254 if (!IncludedInInsert (schemaRow))
257 if (columns.Length > 0) {
258 columns.Append (", ");
259 values.Append (", ");
262 DbParameter parameter = CreateParameter (_insertCommand, String.Format ("@{0}", option ? schemaRow ["BaseColumnName"] : "p" + parmIndex++), schemaRow);
263 parameter.SourceVersion = DataRowVersion.Current;
265 columns.Append (GetQuotedString (parameter.SourceColumn));
266 values.Append (parameter.ParameterName);
269 sql = String.Format ("{0} ({1}) VALUES ({2})", command, columns.ToString (), values.ToString ());
270 _insertCommand.CommandText = sql;
271 _dbCommand = _insertCommand;
272 return _insertCommand;
275 private void CreateNewCommand (ref DbCommand command)
277 DbCommand sourceCommand = SourceCommand;
278 if (command == null) {
279 command = sourceCommand.Connection.CreateCommand ();
280 command.CommandTimeout = sourceCommand.CommandTimeout;
281 command.Transaction = sourceCommand.Transaction;
283 command.CommandType = CommandType.Text;
284 command.UpdatedRowSource = UpdateRowSource.None;
285 command.Parameters.Clear ();
288 private DbCommand CreateUpdateCommand (bool option)
290 // If no table was found, then we can't do an update
291 if (QuotedTableName == String.Empty)
294 CreateNewCommand (ref _updateCommand);
296 string command = String.Format ("UPDATE {0} SET ", QuotedTableName);
297 StringBuilder columns = new StringBuilder ();
298 StringBuilder whereClause = new StringBuilder ();
300 bool keyFound = false;
302 // First, create the X=Y list for UPDATE
303 foreach (DataRow schemaRow in _dbSchemaTable.Rows) {
304 if (!IncludedInUpdate (schemaRow))
306 if (columns.Length > 0)
307 columns.Append (", ");
309 DbParameter parameter = CreateParameter (_updateCommand, String.Format ("@{0}", option ? schemaRow ["BaseColumnName"] : "p" + parmIndex++), schemaRow);
310 parameter.SourceVersion = DataRowVersion.Current;
312 columns.Append (String.Format ("{0} = {1}", GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
315 // Now, create the WHERE clause. This may be optimizable, but it would be ugly to incorporate
316 // into the loop above. "Premature optimization is the root of all evil." -- Knuth
317 foreach (DataRow schemaRow in _dbSchemaTable.Rows) {
318 if (!schemaRow.IsNull ("IsExpression") && (bool) schemaRow ["IsExpression"] == true)
321 if (!IncludedInWhereClause (schemaRow))
324 if (whereClause.Length > 0)
325 whereClause.Append (" AND ");
327 bool isKey = (bool) schemaRow ["IsKey"];
328 DbParameter parameter = null;
333 //ms.net 1.1 generates the null check for columns even if AllowDBNull is false
334 //while ms.net 2.0 does not. Anyways, since both forms are logically equivalent
335 //following the 2.0 approach
336 bool allowNull = (bool) schemaRow ["AllowDBNull"];
337 if (!isKey && allowNull) {
338 parameter = _updateCommand.CreateParameter ();
340 parameter.ParameterName = String.Format ("@{0} IS NULL",
341 schemaRow ["BaseColumnName"]);
343 parameter.ParameterName = String.Format ("@p{0}", parmIndex++);
346 whereClause.Append ("(");
347 whereClause.Append (String.Format (clause1, parameter.ParameterName,
348 GetQuotedString ((string) schemaRow ["BaseColumnName"])));
349 whereClause.Append (" OR ");
353 parameter = CreateParameter (_updateCommand, String.Format ("@Original_{0}", schemaRow ["BaseColumnName"]), schemaRow);
355 parameter = CreateParameter (_updateCommand, String.Format ("@p{0}", parmIndex++), schemaRow);
356 parameter.SourceVersion = DataRowVersion.Original;
358 whereClause.Append (String.Format (clause2, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
360 if (!isKey && allowNull)
361 whereClause.Append (")");
364 throw new InvalidOperationException ("Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not return any key column information.");
366 // We're all done, so bring it on home
367 string sql = String.Format ("{0}{1} WHERE ({2})", command, columns.ToString (), whereClause.ToString ());
368 _updateCommand.CommandText = sql;
369 _dbCommand = _updateCommand;
370 return _updateCommand;
373 private DbParameter CreateParameter (DbCommand _dbCommand, string parameterName, DataRow schemaRow)
375 DbParameter parameter = _dbCommand.CreateParameter ();
376 parameter.ParameterName = parameterName;
377 parameter.SourceColumn = (string) schemaRow ["BaseColumnName"];
378 parameter.Size = (int) schemaRow ["ColumnSize"];
379 _dbCommand.Parameters.Add (parameter);
383 [DefaultValue (CatalogLocation.Start)]
384 public virtual CatalogLocation CatalogLocation {
385 get { return _catalogLocation; }
386 set { _catalogLocation = value; }
390 public virtual string CatalogSeparator {
391 get { return _catalogSeperator; }
392 set { if (value != null) _catalogSeperator = value; }
395 [DefaultValue (ConflictOption.CompareAllSearchableValues)]
396 public virtual ConflictOption ConflictOption {
397 get { return _conflictOption; }
398 set { _conflictOption = value; }
401 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
403 public DbDataAdapter DataAdapter {
404 get { return _dbDataAdapter; }
405 set { if (value != null) _dbDataAdapter = value; }
409 public virtual string QuotePrefix {
410 get { return _quotePrefix; }
411 set { if (value != null) _quotePrefix = value; }
415 public virtual string QuoteSuffix {
416 get { return _quoteSuffix; }
417 set { if (value != null) _quoteSuffix = value; }
421 public virtual string SchemaSeparator {
422 get { return _schemaSeperator; }
423 set { if (value != null) _schemaSeperator = value; }
426 [DefaultValue (false)]
427 public bool SetAllValues {
428 get { return _setAllValues; }
429 set { _setAllValues = value; }
432 private DbCommand SourceCommand {
434 if (_dbDataAdapter != null)
435 return _dbDataAdapter.SelectCommand;
439 #endregion // Properties
443 protected abstract void ApplyParameterInfo (DbParameter parameter,
445 StatementType statementType,
448 protected override void Dispose (bool disposing)
452 if (_insertCommand != null)
453 _insertCommand.Dispose ();
454 if (_deleteCommand != null)
455 _deleteCommand.Dispose ();
456 if (_updateCommand != null)
457 _updateCommand.Dispose ();
458 if (_dbSchemaTable != null)
459 _dbSchemaTable.Dispose ();
465 public DbCommand GetDeleteCommand ()
468 if (_deleteCommand == null)
469 return CreateDeleteCommand (false);
470 return _deleteCommand;
473 public DbCommand GetDeleteCommand (bool option)
476 if (_deleteCommand == null)
477 return CreateDeleteCommand (option);
478 return _deleteCommand;
481 public DbCommand GetInsertCommand ()
484 if (_insertCommand == null)
485 return CreateInsertCommand (false);
486 return _insertCommand;
489 public DbCommand GetInsertCommand (bool option)
492 if (_insertCommand == null)
493 return CreateInsertCommand (option);
494 return _insertCommand;
497 public DbCommand GetUpdateCommand ()
500 if (_updateCommand == null)
501 return CreateUpdateCommand (false);
502 return _updateCommand;
505 public DbCommand GetUpdateCommand (bool option)
508 if (_updateCommand == null)
509 return CreateUpdateCommand (option);
510 return _updateCommand;
513 protected virtual DbCommand InitializeCommand (DbCommand command)
515 if (_dbCommand == null) {
516 _dbCommand = SourceCommand;
518 _dbCommand.CommandTimeout = 30;
519 _dbCommand.Transaction = null;
520 _dbCommand.CommandType = CommandType.Text;
521 _dbCommand.UpdatedRowSource = UpdateRowSource.None;
527 public virtual string QuoteIdentifier (string unquotedIdentifier)
529 if (unquotedIdentifier == null) {
530 throw new ArgumentNullException("Unquoted identifier parameter cannot be null");
532 return String.Format ("{0}{1}{2}", this.QuotePrefix, unquotedIdentifier, this.QuoteSuffix);
535 public virtual string UnquoteIdentifier (string quotedIdentifier)
537 if (quotedIdentifier == null) {
538 throw new ArgumentNullException ("Quoted identifier parameter cannot be null");
540 string unquotedIdentifier = quotedIdentifier.Trim ();
541 if (unquotedIdentifier.StartsWith (this.QuotePrefix)) {
542 unquotedIdentifier = unquotedIdentifier.Remove (0, 1);
544 if (unquotedIdentifier.EndsWith (this.QuoteSuffix)) {
545 unquotedIdentifier = unquotedIdentifier.Remove (unquotedIdentifier.Length - 1, 1);
547 return unquotedIdentifier;
550 public virtual void RefreshSchema ()
552 _tableName = String.Empty;
553 _dbSchemaTable = null;
554 _deleteCommand = null;
555 _updateCommand = null;
556 _insertCommand = null;
559 protected void RowUpdatingHandler (RowUpdatingEventArgs args)
561 if (args.Command != null)
564 switch (args.StatementType) {
565 case StatementType.Insert:
566 args.Command = GetInsertCommand ();
568 case StatementType.Update:
569 args.Command = GetUpdateCommand ();
571 case StatementType.Delete:
572 args.Command = GetDeleteCommand ();
575 } catch (Exception e) {
577 args.Status = UpdateStatus.ErrorsOccurred;
581 protected abstract string GetParameterName (int parameterOrdinal);
582 protected abstract string GetParameterName (String parameterName);
583 protected abstract string GetParameterPlaceholder (int parameterOrdinal);
585 protected abstract void SetRowUpdatingHandler (DbDataAdapter adapter);
587 protected virtual DataTable GetSchemaTable (DbCommand cmd)
589 using (DbDataReader rdr = cmd.ExecuteReader ())
590 return rdr.GetSchemaTable ();
593 #endregion // Methods