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;
69 public OdbcCommandBuilder ()
72 _quotePrefix = String.Empty;
73 _quoteSuffix = String.Empty;
76 public OdbcCommandBuilder (OdbcDataAdapter adapter)
79 DataAdapter = adapter;
82 #endregion // Constructors
86 [OdbcDescriptionAttribute ("The DataAdapter for which to automatically generate OdbcCommands")]
92 OdbcDataAdapter DataAdapter {
97 if (_adapter == value)
100 if (_adapter != null)
101 _adapter.RowUpdating -= new OdbcRowUpdatingEventHandler (OnRowUpdating);
103 if (_adapter != null)
104 _adapter.RowUpdating += new OdbcRowUpdatingEventHandler (OnRowUpdating);
109 private OdbcCommand SelectCommand
112 if (DataAdapter == null)
114 return DataAdapter.SelectCommand;
118 private DataTable Schema
127 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)]
160 _quotePrefix = value;
164 [BrowsableAttribute (false)]
165 [OdbcDescriptionAttribute ("The suffix string wrapped around sql objects")]
166 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
176 _quoteSuffix = value;
180 #endregion // Properties
185 public static void DeriveParameters (OdbcCommand command)
187 throw new NotImplementedException ();
190 protected override void Dispose (bool disposing)
196 // dispose managed resource
197 if (_insertCommand != null) _insertCommand.Dispose ();
198 if (_updateCommand != null) _updateCommand.Dispose ();
199 if (_deleteCommand != null) _deleteCommand.Dispose ();
200 if (_schema != null) _insertCommand.Dispose ();
202 _insertCommand = null;
203 _updateCommand = null;
204 _deleteCommand = null;
210 private bool IsUpdatable (DataRow schemaRow)
212 if ( (! schemaRow.IsNull ("IsAutoIncrement") && (bool) schemaRow ["IsAutoIncrement"])
213 || (! schemaRow.IsNull ("IsHidden") && (bool) schemaRow ["IsHidden"])
214 || (! schemaRow.IsNull ("IsExpression") && (bool) schemaRow ["IsExpression"])
215 || (! schemaRow.IsNull ("IsRowVersion") && (bool) schemaRow ["IsRowVersion"])
216 || (! schemaRow.IsNull ("IsReadOnly") && (bool) schemaRow ["IsReadOnly"])
222 private string GetColumnName (DataRow schemaRow)
224 string columnName = schemaRow.IsNull ("BaseColumnName") ? String.Empty : (string) schemaRow ["BaseColumnName"];
225 if (columnName == String.Empty)
226 columnName = schemaRow.IsNull ("ColumnName") ? String.Empty : (string) schemaRow ["ColumnName"];
230 private OdbcParameter AddParameter (OdbcCommand cmd, string paramName, OdbcType odbcType,
231 int length, string sourceColumnName, DataRowVersion rowVersion)
234 if (length >= 0 && sourceColumnName != String.Empty)
235 param = cmd.Parameters.Add (paramName, odbcType, length, sourceColumnName);
237 param = cmd.Parameters.Add (paramName, odbcType);
238 param.SourceVersion = rowVersion;
243 * creates where clause for optimistic concurrency
245 private string CreateOptWhereClause (OdbcCommand command)
247 string [] whereClause = new string [Schema.Rows.Count];
251 foreach (DataRow schemaRow in Schema.Rows) {
253 // exclude non updatable columns
254 if (! IsUpdatable (schemaRow))
257 string columnName = GetColumnName (schemaRow);
258 if (columnName == String.Empty)
259 throw new InvalidOperationException ("Cannot form delete command. Column name is missing!");
261 bool allowNull = schemaRow.IsNull ("AllowDBNull") || (bool) schemaRow ["AllowDBNull"];
262 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
263 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
266 whereClause [count] = String.Format ("((? = 1 AND {0} IS NULL) OR ({0} = ?))",
268 AddParameter (command, columnName, sqlDbType, length, columnName, DataRowVersion.Original);
269 AddParameter (command, columnName, sqlDbType, length, columnName, DataRowVersion.Original);
271 whereClause [count] = String.Format ( "({0} = ?)", columnName);
272 AddParameter (command, columnName, sqlDbType, length, columnName, DataRowVersion.Original);
278 return String.Join (" AND ", whereClause, 0, count);
285 OdbcCommand GetInsertCommand ()
287 // FIXME: check validity of adapter
288 if (_insertCommand != null)
289 return _insertCommand;
294 _insertCommand = new OdbcCommand ();
295 _insertCommand.Connection = DataAdapter.SelectCommand.Connection;
296 _insertCommand.Transaction = DataAdapter.SelectCommand.Transaction;
297 _insertCommand.CommandType = CommandType.Text;
298 _insertCommand.UpdatedRowSource = UpdateRowSource.None;
300 string query = String.Format ("INSERT INTO {0}", QuoteIdentifier (TableName));
301 string [] columns = new string [Schema.Rows.Count];
302 string [] values = new string [Schema.Rows.Count];
306 foreach (DataRow schemaRow in Schema.Rows) {
308 // exclude non updatable columns
309 if (! IsUpdatable (schemaRow))
312 string columnName = GetColumnName (schemaRow);
313 if (columnName == String.Empty)
314 throw new InvalidOperationException ("Cannot form insert command. Column name is missing!");
316 // create column string & value string
317 columns [count] = QuoteIdentifier(columnName);
318 values [count++] = "?";
320 // create parameter and add
321 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
322 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
324 AddParameter (_insertCommand, columnName, sqlDbType, length, columnName, DataRowVersion.Current);
327 query = String.Format ("{0} ({1}) VALUES ({2})",
329 String.Join (", ", columns, 0, count),
330 String.Join (", ", values, 0, count) );
331 _insertCommand.CommandText = query;
332 return _insertCommand;
337 public new OdbcCommand GetInsertCommand (bool option)
339 // FIXME: check validity of adapter
340 if (_insertCommand != null)
341 return _insertCommand;
346 if (option == false) {
347 return GetInsertCommand ();
349 throw new NotImplementedException ();
358 OdbcCommand GetUpdateCommand ()
360 // FIXME: check validity of adapter
361 if (_updateCommand != null)
362 return _updateCommand;
367 _updateCommand = new OdbcCommand ();
368 _updateCommand.Connection = DataAdapter.SelectCommand.Connection;
369 _updateCommand.Transaction = DataAdapter.SelectCommand.Transaction;
370 _updateCommand.CommandType = CommandType.Text;
371 _updateCommand.UpdatedRowSource = UpdateRowSource.None;
373 string query = String.Format ("UPDATE {0} SET", QuoteIdentifier (TableName));
374 string [] setClause = new string [Schema.Rows.Count];
378 foreach (DataRow schemaRow in Schema.Rows) {
380 // exclude non updatable columns
381 if (! IsUpdatable (schemaRow))
384 string columnName = GetColumnName (schemaRow);
385 if (columnName == String.Empty)
386 throw new InvalidOperationException ("Cannot form update command. Column name is missing!");
388 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
389 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
391 // create column = value string
392 setClause [count] = String.Format ("{0} = ?", QuoteIdentifier(columnName));
393 AddParameter (_updateCommand, columnName, sqlDbType, length, columnName, DataRowVersion.Current);
397 // create where clause. odbc uses positional parameters. so where class
398 // is created seperate from the above loop.
399 string whereClause = CreateOptWhereClause (_updateCommand);
401 query = String.Format ("{0} {1} WHERE ({2})",
403 String.Join (", ", setClause, 0, count),
405 _updateCommand.CommandText = query;
406 return _updateCommand;
411 public new OdbcCommand GetUpdateCommand (bool option)
413 // FIXME: check validity of adapter
414 if (_updateCommand != null)
415 return _updateCommand;
420 if (option == false) {
421 return GetUpdateCommand ();
423 throw new NotImplementedException ();
432 OdbcCommand GetDeleteCommand ()
434 // FIXME: check validity of adapter
435 if (_deleteCommand != null)
436 return _deleteCommand;
441 _deleteCommand = new OdbcCommand ();
442 _deleteCommand.Connection = DataAdapter.SelectCommand.Connection;
443 _deleteCommand.Transaction = DataAdapter.SelectCommand.Transaction;
444 _deleteCommand.CommandType = CommandType.Text;
445 _deleteCommand.UpdatedRowSource = UpdateRowSource.None;
447 string query = String.Format ("DELETE FROM {0}", QuoteIdentifier (TableName));
448 string whereClause = CreateOptWhereClause (_deleteCommand);
450 query = String.Format ("{0} WHERE ({1})", query, whereClause);
451 _deleteCommand.CommandText = query;
452 return _deleteCommand;
457 public new OdbcCommand GetDeleteCommand (bool option)
459 // FIXME: check validity of adapter
460 if (_deleteCommand != null)
461 return _deleteCommand;
466 if (option == false) {
467 return GetDeleteCommand ();
469 throw new NotImplementedException ();
478 void RefreshSchema ()
481 if (SelectCommand == null)
482 throw new InvalidOperationException ("SelectCommand should be valid");
483 if (SelectCommand.Connection == null)
484 throw new InvalidOperationException ("SelectCommand's Connection should be valid");
486 CommandBehavior behavior = CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo;
487 if (SelectCommand.Connection.State != ConnectionState.Open) {
488 SelectCommand.Connection.Open ();
489 behavior |= CommandBehavior.CloseConnection;
492 OdbcDataReader reader = SelectCommand.ExecuteReader (behavior);
493 _schema = reader.GetSchemaTable ();
496 // force creation of commands
497 _insertCommand = null;
498 _updateCommand = null;
499 _deleteCommand = null;
500 _tableName = String.Empty;
505 protected override void ApplyParameterInfo (DbParameter dbParameter,
507 StatementType statementType,
510 throw new NotImplementedException ();
514 protected override string GetParameterName (int position)
516 throw new NotImplementedException ();
520 protected override string GetParameterName (string parameterName)
522 throw new NotImplementedException ();
526 protected override string GetParameterPlaceholder (int position)
528 throw new NotImplementedException ();
532 protected override void SetRowUpdatingHandler (DbDataAdapter adapter)
534 throw new NotImplementedException ();
545 string QuoteIdentifier (string unquotedIdentifier)
549 throw new NotImplementedException ();
552 if (unquotedIdentifier == null || unquotedIdentifier == String.Empty)
553 return unquotedIdentifier;
554 return String.Format ("{0}{1}{2}", QuotePrefix,
555 unquotedIdentifier, QuoteSuffix);
567 string UnquoteIdentifier (string quotedIdentifier)
570 throw new NotImplementedException ();
572 if (quotedIdentifier == null || quotedIdentifier == String.Empty)
573 return quotedIdentifier;
575 StringBuilder sb = new StringBuilder (quotedIdentifier.Length);
576 sb.Append (quotedIdentifier);
577 if (quotedIdentifier.StartsWith (QuotePrefix))
578 sb.Remove (0,QuotePrefix.Length);
579 if (quotedIdentifier.EndsWith (QuoteSuffix))
580 sb.Remove (sb.Length - QuoteSuffix.Length, QuoteSuffix.Length );
581 return sb.ToString ();
585 private void OnRowUpdating (object sender, OdbcRowUpdatingEventArgs args)
587 if (args.Command != null)
590 switch (args.StatementType) {
591 case StatementType.Insert:
592 args.Command = GetInsertCommand ();
594 case StatementType.Update:
595 args.Command = GetUpdateCommand ();
597 case StatementType.Delete:
598 args.Command = GetDeleteCommand ();
601 } catch (Exception e) {
603 args.Status = UpdateStatus.ErrorsOccurred;
608 #endregion // Methods