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 ()
73 _quotePrefix = string.Empty;
74 _quoteSuffix = string.Empty;
77 public OdbcCommandBuilder (OdbcDataAdapter adapter)
80 DataAdapter = adapter;
83 #endregion // Constructors
87 [OdbcDescriptionAttribute ("The DataAdapter for which to automatically generate OdbcCommands")]
93 OdbcDataAdapter DataAdapter {
98 if (_adapter == value)
101 if (rowUpdatingHandler != null)
102 rowUpdatingHandler = new OdbcRowUpdatingEventHandler (OnRowUpdating);
104 if (_adapter != null)
105 _adapter.RowUpdating -= rowUpdatingHandler;
107 if (_adapter != null)
108 _adapter.RowUpdating += rowUpdatingHandler;
112 private OdbcCommand SelectCommand {
114 if (DataAdapter == null)
116 return DataAdapter.SelectCommand;
120 private DataTable Schema {
128 private string TableName {
130 if (_tableName != string.Empty)
133 DataRow [] schemaRows = Schema.Select ("BaseTableName is not null and BaseTableName <> ''");
134 if (schemaRows.Length > 1) {
135 string tableName = (string) schemaRows [0] ["BaseTableName"];
136 foreach (DataRow schemaRow in schemaRows) {
137 if ( (string) schemaRow ["BaseTableName"] != tableName)
138 throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
141 if (schemaRows.Length == 0)
142 throw new InvalidOperationException ("Cannot determine the base table name. Cannot proceed");
143 _tableName = schemaRows [0] ["BaseTableName"].ToString ();
148 [BrowsableAttribute (false)]
149 [OdbcDescriptionAttribute ("The prefix string wrapped around sql objects")]
150 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
161 _quotePrefix = value;
165 [BrowsableAttribute (false)]
166 [OdbcDescriptionAttribute ("The suffix string wrapped around sql objects")]
167 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
178 _quoteSuffix = value;
182 #endregion // Properties
187 public static void DeriveParameters (OdbcCommand command)
189 throw new NotImplementedException ();
197 void Dispose (bool disposing)
203 // dispose managed resource
204 if (_insertCommand != null)
205 _insertCommand.Dispose ();
206 if (_updateCommand != null)
207 _updateCommand.Dispose ();
208 if (_deleteCommand != null)
209 _deleteCommand.Dispose ();
211 _insertCommand.Dispose ();
213 _insertCommand = null;
214 _updateCommand = null;
215 _deleteCommand = null;
221 private bool IsUpdatable (DataRow schemaRow)
223 if ( (! schemaRow.IsNull ("IsAutoIncrement") && (bool) schemaRow ["IsAutoIncrement"])
224 || (! schemaRow.IsNull ("IsHidden") && (bool) schemaRow ["IsHidden"])
225 || (! schemaRow.IsNull ("IsExpression") && (bool) schemaRow ["IsExpression"])
226 || (! schemaRow.IsNull ("IsRowVersion") && (bool) schemaRow ["IsRowVersion"])
227 || (! schemaRow.IsNull ("IsReadOnly") && (bool) schemaRow ["IsReadOnly"])
233 private string GetColumnName (DataRow schemaRow)
235 string columnName = schemaRow.IsNull ("BaseColumnName") ? String.Empty : (string) schemaRow ["BaseColumnName"];
236 if (columnName == String.Empty)
237 columnName = schemaRow.IsNull ("ColumnName") ? String.Empty : (string) schemaRow ["ColumnName"];
241 private OdbcParameter AddParameter (OdbcCommand cmd, string paramName, OdbcType odbcType,
242 int length, string sourceColumnName, DataRowVersion rowVersion)
245 if (length >= 0 && sourceColumnName != String.Empty)
246 param = cmd.Parameters.Add (paramName, odbcType, length, sourceColumnName);
248 param = cmd.Parameters.Add (paramName, odbcType);
249 param.SourceVersion = rowVersion;
254 * creates where clause for optimistic concurrency
256 private string CreateOptWhereClause (OdbcCommand command, bool option)
258 string [] whereClause = new string [Schema.Rows.Count];
262 foreach (DataRow schemaRow in Schema.Rows) {
263 // exclude non updatable columns
264 if (! IsUpdatable (schemaRow))
267 string columnName = null;
269 columnName = GetColumnName (schemaRow);
271 columnName = String.Format ("@p{0}", count);
273 if (columnName == String.Empty)
274 throw new InvalidOperationException ("Cannot form delete command. Column name is missing!");
276 bool allowNull = schemaRow.IsNull ("AllowDBNull") || (bool) schemaRow ["AllowDBNull"];
277 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
278 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
281 whereClause [count] = String.Format ("((? = 1 AND {0} IS NULL) OR ({0} = ?))",
283 AddParameter (command, columnName, sqlDbType, length, columnName, DataRowVersion.Original);
284 AddParameter (command, columnName, sqlDbType, length, columnName, DataRowVersion.Original);
286 whereClause [count] = String.Format ( "({0} = ?)", columnName);
287 AddParameter (command, columnName, sqlDbType, length, columnName, DataRowVersion.Original);
293 return String.Join (" AND ", whereClause, 0, count);
296 private void CreateNewCommand (ref OdbcCommand command)
298 OdbcCommand sourceCommand = SelectCommand;
299 if (command == null) {
300 command = new OdbcCommand ();
301 command.Connection = sourceCommand.Connection;
302 command.CommandTimeout = sourceCommand.CommandTimeout;
303 command.Transaction = sourceCommand.Transaction;
305 command.CommandType = CommandType.Text;
306 command.UpdatedRowSource = UpdateRowSource.None;
307 command.Parameters.Clear ();
310 private OdbcCommand CreateInsertCommand (bool option)
312 CreateNewCommand (ref _insertCommand);
314 string query = String.Format ("INSERT INTO {0}", QuoteIdentifier (TableName));
315 string [] columns = new string [Schema.Rows.Count];
316 string [] values = new string [Schema.Rows.Count];
320 foreach (DataRow schemaRow in Schema.Rows) {
321 // exclude non updatable columns
322 if (! IsUpdatable (schemaRow))
325 string columnName = null;
328 columnName = GetColumnName (schemaRow);
330 columnName = String.Format ("@p{0}", count);
332 if (columnName == String.Empty)
333 throw new InvalidOperationException ("Cannot form insert command. Column name is missing!");
335 // create column string & value string
336 columns [count] = QuoteIdentifier(columnName);
337 values [count++] = "?";
339 // create parameter and add
340 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
341 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
343 AddParameter (_insertCommand, columnName, sqlDbType, length, columnName, DataRowVersion.Current);
346 query = String.Format ("{0} ({1}) VALUES ({2})",
348 String.Join (", ", columns, 0, count),
349 String.Join (", ", values, 0, count) );
350 _insertCommand.CommandText = query;
351 return _insertCommand;
358 OdbcCommand GetInsertCommand ()
360 // FIXME: check validity of adapter
361 if (_insertCommand != null)
362 return _insertCommand;
367 return CreateInsertCommand (false);
371 public new OdbcCommand GetInsertCommand (bool useColumnsForParameterNames)
373 // FIXME: check validity of adapter
374 if (_insertCommand != null)
375 return _insertCommand;
380 return CreateInsertCommand (useColumnsForParameterNames);
384 private OdbcCommand CreateUpdateCommand (bool option)
386 CreateNewCommand (ref _updateCommand);
388 string query = String.Format ("UPDATE {0} SET", QuoteIdentifier (TableName));
389 string [] setClause = new string [Schema.Rows.Count];
393 foreach (DataRow schemaRow in Schema.Rows) {
394 // exclude non updatable columns
395 if (! IsUpdatable (schemaRow))
398 string columnName = null;
400 columnName = GetColumnName (schemaRow);
402 columnName = String.Format ("@p{0}", count);
404 if (columnName == String.Empty)
405 throw new InvalidOperationException ("Cannot form update command. Column name is missing!");
407 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
408 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
410 // create column = value string
411 setClause [count] = String.Format ("{0} = ?", QuoteIdentifier(columnName));
412 AddParameter (_updateCommand, columnName, sqlDbType, length, columnName, DataRowVersion.Current);
416 // create where clause. odbc uses positional parameters. so where class
417 // is created seperate from the above loop.
418 string whereClause = CreateOptWhereClause (_updateCommand, option);
420 query = String.Format ("{0} {1} WHERE ({2})",
422 String.Join (", ", setClause, 0, count),
424 _updateCommand.CommandText = query;
425 return _updateCommand;
432 OdbcCommand GetUpdateCommand ()
434 // FIXME: check validity of adapter
435 if (_updateCommand != null)
436 return _updateCommand;
441 return CreateUpdateCommand (false);
445 public new OdbcCommand GetUpdateCommand (bool useColumnsForParameterNames)
447 // FIXME: check validity of adapter
448 if (_updateCommand != null)
449 return _updateCommand;
454 return CreateUpdateCommand (useColumnsForParameterNames);
458 private OdbcCommand CreateDeleteCommand (bool option)
460 CreateNewCommand (ref _deleteCommand);
462 string query = String.Format ("DELETE FROM {0}", QuoteIdentifier (TableName));
463 string whereClause = CreateOptWhereClause (_deleteCommand, option);
465 query = String.Format ("{0} WHERE ({1})", query, whereClause);
466 _deleteCommand.CommandText = query;
467 return _deleteCommand;
474 OdbcCommand GetDeleteCommand ()
476 // FIXME: check validity of adapter
477 if (_deleteCommand != null)
478 return _deleteCommand;
483 return CreateDeleteCommand (false);
487 public new OdbcCommand GetDeleteCommand (bool useColumnsForParameterNames)
489 // FIXME: check validity of adapter
490 if (_deleteCommand != null)
491 return _deleteCommand;
496 return CreateDeleteCommand (useColumnsForParameterNames);
505 void RefreshSchema ()
508 if (SelectCommand == null)
509 throw new InvalidOperationException ("SelectCommand should be valid");
510 if (SelectCommand.Connection == null)
511 throw new InvalidOperationException ("SelectCommand's Connection should be valid");
513 CommandBehavior behavior = CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo;
514 if (SelectCommand.Connection.State != ConnectionState.Open) {
515 SelectCommand.Connection.Open ();
516 behavior |= CommandBehavior.CloseConnection;
519 OdbcDataReader reader = SelectCommand.ExecuteReader (behavior);
520 _schema = reader.GetSchemaTable ();
523 // force creation of commands
524 _insertCommand = null;
525 _updateCommand = null;
526 _deleteCommand = null;
527 _tableName = String.Empty;
531 protected override void ApplyParameterInfo (DbParameter parameter,
533 StatementType statementType,
536 OdbcParameter odbcParam = (OdbcParameter) parameter;
537 odbcParam.Size = int.Parse (row ["ColumnSize"].ToString ());
538 if (row ["NumericPrecision"] != DBNull.Value)
539 odbcParam.Precision = byte.Parse (row ["NumericPrecision"].ToString ());
540 if (row ["NumericScale"] != DBNull.Value)
541 odbcParam.Scale = byte.Parse (row ["NumericScale"].ToString ());
542 odbcParam.DbType = (DbType) row ["ProviderType"];
545 protected override string GetParameterName (int parameterOrdinal)
547 return String.Format("@p{0}", parameterOrdinal);
550 protected override string GetParameterName (string parameterName)
552 return String.Format("@{0}", parameterName);
555 protected override string GetParameterPlaceholder (int parameterOrdinal)
557 return GetParameterName (parameterOrdinal);
560 // FIXME: According to MSDN - "if this method is called again with
561 // the same DbDataAdapter, the DbCommandBuilder is unregistered for
562 // that DbDataAdapter's RowUpdating event" - this behaviour is yet
564 protected override void SetRowUpdatingHandler (DbDataAdapter adapter)
566 if (!(adapter is OdbcDataAdapter))
567 throw new InvalidOperationException ("Adapter needs to be a SqlDataAdapter");
568 if (rowUpdatingHandler == null)
569 rowUpdatingHandler = new OdbcRowUpdatingEventHandler (OnRowUpdating);
571 ((OdbcDataAdapter) adapter).RowUpdating += rowUpdatingHandler;
580 string QuoteIdentifier (string unquotedIdentifier)
582 if (unquotedIdentifier == null || unquotedIdentifier.Length == 0)
583 return unquotedIdentifier;
584 return String.Format ("{0}{1}{2}", QuotePrefix,
585 unquotedIdentifier, QuoteSuffix);
589 // FIXME: Not sure what the extra "connection" param does!
590 public string QuoteIdentifier (string unquotedIdentifier, OdbcConnection connection)
592 return QuoteIdentifier (unquotedIdentifier);
595 public string UnquoteIdentifier (string quotedIdentifier, OdbcConnection connection)
597 return UnquoteIdentifier (quotedIdentifier);
600 public override string UnquoteIdentifier (string quotedIdentifier)
602 if (quotedIdentifier == null || quotedIdentifier.Length == 0)
603 return quotedIdentifier;
605 StringBuilder sb = new StringBuilder (quotedIdentifier.Length);
606 sb.Append (quotedIdentifier);
607 if (quotedIdentifier.StartsWith (QuotePrefix))
608 sb.Remove (0,QuotePrefix.Length);
609 if (quotedIdentifier.EndsWith (QuoteSuffix))
610 sb.Remove (sb.Length - QuoteSuffix.Length, QuoteSuffix.Length );
611 return sb.ToString ();
615 private void OnRowUpdating (object sender, OdbcRowUpdatingEventArgs args)
617 if (args.Command != null)
620 switch (args.StatementType) {
621 case StatementType.Insert:
622 args.Command = GetInsertCommand ();
624 case StatementType.Update:
625 args.Command = GetUpdateCommand ();
627 case StatementType.Delete:
628 args.Command = GetDeleteCommand ();
631 } catch (Exception e) {
633 args.Status = UpdateStatus.ErrorsOccurred;
637 #endregion // Methods