2 // System.Data.Odbc.OdbcCommandBuilder
5 // Umadevi S (sumadevi@novell.com)
6 // Sureshkumar T (tsureshkumar@novell.com)
8 // Copyright (C) Novell Inc, 2004
12 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 using System.Data.Common;
37 using System.ComponentModel;
39 namespace System.Data.Odbc
42 /// Provides a means of automatically generating single-table commands used to reconcile changes made to a DataSet with the associated database. This class cannot be inherited.
46 public sealed class OdbcCommandBuilder : DbCommandBuilder
48 public sealed class OdbcCommandBuilder : Component
53 private OdbcDataAdapter _adapter;
54 private string _quotePrefix;
55 private string _quoteSuffix;
57 private DataTable _schema;
58 private string _tableName;
59 private OdbcCommand _insertCommand;
60 private OdbcCommand _updateCommand;
61 private OdbcCommand _deleteCommand;
65 private OdbcRowUpdatingEventHandler rowUpdatingHandler;
71 public OdbcCommandBuilder ()
74 _quotePrefix = String.Empty;
75 _quoteSuffix = String.Empty;
76 rowUpdatingHandler = null;
79 public OdbcCommandBuilder (OdbcDataAdapter adapter)
82 DataAdapter = adapter;
85 #endregion // Constructors
89 [OdbcDescriptionAttribute ("The DataAdapter for which to automatically generate OdbcCommands")]
95 OdbcDataAdapter DataAdapter {
100 if (_adapter == value)
103 if (rowUpdatingHandler != null)
104 rowUpdatingHandler = new OdbcRowUpdatingEventHandler (OnRowUpdating);
106 if (_adapter != null)
107 _adapter.RowUpdating -= rowUpdatingHandler;
109 if (_adapter != null)
110 _adapter.RowUpdating += rowUpdatingHandler;
114 private OdbcCommand SelectCommand
117 if (DataAdapter == null)
119 return DataAdapter.SelectCommand;
123 private DataTable Schema
132 private string TableName
135 if (_tableName != String.Empty)
138 DataRow [] schemaRows = Schema.Select ("BaseTableName is not null and BaseTableName <> ''");
139 if (schemaRows.Length > 1) {
140 string tableName = (string) schemaRows [0] ["BaseTableName"];
141 foreach (DataRow schemaRow in schemaRows) {
142 if ( (string) schemaRow ["BaseTableName"] != tableName)
143 throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
146 if (schemaRows.Length == 0)
147 throw new InvalidOperationException ("Cannot determine the base table name. Cannot proceed");
148 _tableName = schemaRows [0] ["BaseTableName"].ToString ();
153 [BrowsableAttribute (false)]
154 [OdbcDescriptionAttribute ("The prefix string wrapped around sql objects")]
155 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
166 _quotePrefix = value;
170 [BrowsableAttribute (false)]
171 [OdbcDescriptionAttribute ("The suffix string wrapped around sql objects")]
172 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
183 _quoteSuffix = value;
187 #endregion // Properties
192 public static void DeriveParameters (OdbcCommand command)
194 throw new NotImplementedException ();
202 void Dispose (bool disposing)
208 // dispose managed resource
209 if (_insertCommand != null) _insertCommand.Dispose ();
210 if (_updateCommand != null) _updateCommand.Dispose ();
211 if (_deleteCommand != null) _deleteCommand.Dispose ();
212 if (_schema != null) _insertCommand.Dispose ();
214 _insertCommand = null;
215 _updateCommand = null;
216 _deleteCommand = null;
222 private bool IsUpdatable (DataRow schemaRow)
224 if ( (! schemaRow.IsNull ("IsAutoIncrement") && (bool) schemaRow ["IsAutoIncrement"])
225 || (! schemaRow.IsNull ("IsHidden") && (bool) schemaRow ["IsHidden"])
226 || (! schemaRow.IsNull ("IsExpression") && (bool) schemaRow ["IsExpression"])
227 || (! schemaRow.IsNull ("IsRowVersion") && (bool) schemaRow ["IsRowVersion"])
228 || (! schemaRow.IsNull ("IsReadOnly") && (bool) schemaRow ["IsReadOnly"])
234 private string GetColumnName (DataRow schemaRow)
236 string columnName = schemaRow.IsNull ("BaseColumnName") ? String.Empty : (string) schemaRow ["BaseColumnName"];
237 if (columnName == String.Empty)
238 columnName = schemaRow.IsNull ("ColumnName") ? String.Empty : (string) schemaRow ["ColumnName"];
242 private OdbcParameter AddParameter (OdbcCommand cmd, string paramName, OdbcType odbcType,
243 int length, string sourceColumnName, DataRowVersion rowVersion)
246 if (length >= 0 && sourceColumnName != String.Empty)
247 param = cmd.Parameters.Add (paramName, odbcType, length, sourceColumnName);
249 param = cmd.Parameters.Add (paramName, odbcType);
250 param.SourceVersion = rowVersion;
255 * creates where clause for optimistic concurrency
257 private string CreateOptWhereClause (OdbcCommand command, bool option)
259 string [] whereClause = new string [Schema.Rows.Count];
263 foreach (DataRow schemaRow in Schema.Rows) {
265 // exclude non updatable columns
266 if (! IsUpdatable (schemaRow))
269 string columnName = null;
271 columnName = GetColumnName (schemaRow);
273 columnName = String.Format ("@p{0}", count);
275 if (columnName == String.Empty)
276 throw new InvalidOperationException ("Cannot form delete command. Column name is missing!");
278 bool allowNull = schemaRow.IsNull ("AllowDBNull") || (bool) schemaRow ["AllowDBNull"];
279 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
280 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
283 whereClause [count] = String.Format ("((? = 1 AND {0} IS NULL) OR ({0} = ?))",
285 AddParameter (command, columnName, sqlDbType, length, columnName, DataRowVersion.Original);
286 AddParameter (command, columnName, sqlDbType, length, columnName, DataRowVersion.Original);
288 whereClause [count] = String.Format ( "({0} = ?)", columnName);
289 AddParameter (command, columnName, sqlDbType, length, columnName, DataRowVersion.Original);
295 return String.Join (" AND ", whereClause, 0, count);
298 private void CreateNewCommand (ref OdbcCommand command)
300 OdbcCommand sourceCommand = SelectCommand;
301 if (command == null) {
302 command = new OdbcCommand ();
303 command.Connection = sourceCommand.Connection;
304 command.CommandTimeout = sourceCommand.CommandTimeout;
305 command.Transaction = sourceCommand.Transaction;
307 command.CommandType = CommandType.Text;
308 command.UpdatedRowSource = UpdateRowSource.None;
309 command.Parameters.Clear ();
312 private OdbcCommand CreateInsertCommand (bool option)
314 CreateNewCommand (ref _insertCommand);
316 string query = String.Format ("INSERT INTO {0}", QuoteIdentifier (TableName));
317 string [] columns = new string [Schema.Rows.Count];
318 string [] values = new string [Schema.Rows.Count];
322 foreach (DataRow schemaRow in Schema.Rows) {
324 // exclude non updatable columns
325 if (! IsUpdatable (schemaRow))
328 string columnName = null;
331 columnName = GetColumnName (schemaRow);
333 columnName = String.Format ("@p{0}", count);
335 if (columnName == String.Empty)
336 throw new InvalidOperationException ("Cannot form insert command. Column name is missing!");
338 // create column string & value string
339 columns [count] = QuoteIdentifier(columnName);
340 values [count++] = "?";
342 // create parameter and add
343 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
344 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
346 AddParameter (_insertCommand, columnName, sqlDbType, length, columnName, DataRowVersion.Current);
349 query = String.Format ("{0} ({1}) VALUES ({2})",
351 String.Join (", ", columns, 0, count),
352 String.Join (", ", values, 0, count) );
353 _insertCommand.CommandText = query;
354 return _insertCommand;
361 OdbcCommand GetInsertCommand ()
363 // FIXME: check validity of adapter
364 if (_insertCommand != null)
365 return _insertCommand;
370 return CreateInsertCommand (false);
374 public new OdbcCommand GetInsertCommand (bool option)
376 // FIXME: check validity of adapter
377 if (_insertCommand != null)
378 return _insertCommand;
383 return CreateInsertCommand (option);
387 private OdbcCommand CreateUpdateCommand (bool option)
389 CreateNewCommand (ref _updateCommand);
391 string query = String.Format ("UPDATE {0} SET", QuoteIdentifier (TableName));
392 string [] setClause = new string [Schema.Rows.Count];
396 foreach (DataRow schemaRow in Schema.Rows) {
398 // exclude non updatable columns
399 if (! IsUpdatable (schemaRow))
402 string columnName = null;
404 columnName = GetColumnName (schemaRow);
406 columnName = String.Format ("@p{0}", count);
408 if (columnName == String.Empty)
409 throw new InvalidOperationException ("Cannot form update command. Column name is missing!");
411 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
412 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
414 // create column = value string
415 setClause [count] = String.Format ("{0} = ?", QuoteIdentifier(columnName));
416 AddParameter (_updateCommand, columnName, sqlDbType, length, columnName, DataRowVersion.Current);
420 // create where clause. odbc uses positional parameters. so where class
421 // is created seperate from the above loop.
422 string whereClause = CreateOptWhereClause (_updateCommand, option);
424 query = String.Format ("{0} {1} WHERE ({2})",
426 String.Join (", ", setClause, 0, count),
428 _updateCommand.CommandText = query;
429 return _updateCommand;
436 OdbcCommand GetUpdateCommand ()
438 // FIXME: check validity of adapter
439 if (_updateCommand != null)
440 return _updateCommand;
445 return CreateUpdateCommand (false);
449 public new OdbcCommand GetUpdateCommand (bool option)
451 // FIXME: check validity of adapter
452 if (_updateCommand != null)
453 return _updateCommand;
458 return CreateUpdateCommand (option);
462 private OdbcCommand CreateDeleteCommand (bool option)
464 CreateNewCommand (ref _deleteCommand);
466 string query = String.Format ("DELETE FROM {0}", QuoteIdentifier (TableName));
467 string whereClause = CreateOptWhereClause (_deleteCommand, option);
469 query = String.Format ("{0} WHERE ({1})", query, whereClause);
470 _deleteCommand.CommandText = query;
471 return _deleteCommand;
478 OdbcCommand GetDeleteCommand ()
480 // FIXME: check validity of adapter
481 if (_deleteCommand != null)
482 return _deleteCommand;
487 return CreateDeleteCommand (false);
491 public new OdbcCommand GetDeleteCommand (bool option)
493 // FIXME: check validity of adapter
494 if (_deleteCommand != null)
495 return _deleteCommand;
500 return CreateDeleteCommand (option);
509 void RefreshSchema ()
512 if (SelectCommand == null)
513 throw new InvalidOperationException ("SelectCommand should be valid");
514 if (SelectCommand.Connection == null)
515 throw new InvalidOperationException ("SelectCommand's Connection should be valid");
517 CommandBehavior behavior = CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo;
518 if (SelectCommand.Connection.State != ConnectionState.Open) {
519 SelectCommand.Connection.Open ();
520 behavior |= CommandBehavior.CloseConnection;
523 OdbcDataReader reader = SelectCommand.ExecuteReader (behavior);
524 _schema = reader.GetSchemaTable ();
527 // force creation of commands
528 _insertCommand = null;
529 _updateCommand = null;
530 _deleteCommand = null;
531 _tableName = String.Empty;
535 protected override void ApplyParameterInfo (DbParameter dbParameter,
537 StatementType statementType,
540 OdbcParameter parameter = (OdbcParameter) dbParameter;
541 parameter.Size = int.Parse (row ["ColumnSize"].ToString ());
542 if (row ["NumericPrecision"] != DBNull.Value) {
543 parameter.Precision = byte.Parse (row ["NumericPrecision"].ToString ());
545 if (row ["NumericScale"] != DBNull.Value) {
546 parameter.Scale = byte.Parse (row ["NumericScale"].ToString ());
548 parameter.DbType = (DbType) row ["ProviderType"];
551 protected override string GetParameterName (int position)
553 return String.Format("@p{0}", position);
556 protected override string GetParameterName (string parameterName)
558 return String.Format("@{0}", parameterName);
561 protected override string GetParameterPlaceholder (int position)
563 return GetParameterName (position);
566 // FIXME: According to MSDN - "if this method is called again with
567 // the same DbDataAdapter, the DbCommandBuilder is unregistered for
568 // that DbDataAdapter's RowUpdating event" - this behaviour is yet
570 protected override void SetRowUpdatingHandler (DbDataAdapter adapter)
572 if (!(adapter is OdbcDataAdapter)) {
573 throw new InvalidOperationException ("Adapter needs to be a SqlDataAdapter");
576 if (rowUpdatingHandler == null)
577 rowUpdatingHandler = new OdbcRowUpdatingEventHandler (OnRowUpdating);
579 ((OdbcDataAdapter) adapter).RowUpdating += rowUpdatingHandler;
589 string QuoteIdentifier (string unquotedIdentifier)
591 if (unquotedIdentifier == null || unquotedIdentifier == String.Empty)
592 return unquotedIdentifier;
593 return String.Format ("{0}{1}{2}", QuotePrefix,
594 unquotedIdentifier, QuoteSuffix);
598 // FIXME: Not sure what the extra "connection" param does!
599 public string QuoteIdentifier (string unquotedIdentifier, OdbcConnection connection)
601 return QuoteIdentifier (unquotedIdentifier);
604 public string UnquoteIdentifier (string quotedIdentifier, OdbcConnection connection)
606 return UnquoteIdentifier (quotedIdentifier);
615 string UnquoteIdentifier (string quotedIdentifier)
617 if (quotedIdentifier == null || quotedIdentifier == String.Empty)
618 return quotedIdentifier;
620 StringBuilder sb = new StringBuilder (quotedIdentifier.Length);
621 sb.Append (quotedIdentifier);
622 if (quotedIdentifier.StartsWith (QuotePrefix))
623 sb.Remove (0,QuotePrefix.Length);
624 if (quotedIdentifier.EndsWith (QuoteSuffix))
625 sb.Remove (sb.Length - QuoteSuffix.Length, QuoteSuffix.Length );
626 return sb.ToString ();
629 private void OnRowUpdating (object sender, OdbcRowUpdatingEventArgs args)
631 if (args.Command != null)
633 Console.WriteLine (Environment.StackTrace);
635 switch (args.StatementType) {
636 case StatementType.Insert:
637 args.Command = GetInsertCommand ();
639 case StatementType.Update:
640 args.Command = GetUpdateCommand ();
642 case StatementType.Delete:
643 args.Command = GetDeleteCommand ();
646 } catch (Exception e) {
648 args.Status = UpdateStatus.ErrorsOccurred;
653 #endregion // Methods