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;
339 OdbcCommand GetUpdateCommand ()
341 // FIXME: check validity of adapter
342 if (_updateCommand != null)
343 return _updateCommand;
348 _updateCommand = new OdbcCommand ();
349 _updateCommand.Connection = DataAdapter.SelectCommand.Connection;
350 _updateCommand.Transaction = DataAdapter.SelectCommand.Transaction;
351 _updateCommand.CommandType = CommandType.Text;
352 _updateCommand.UpdatedRowSource = UpdateRowSource.None;
354 string query = String.Format ("UPDATE {0} SET", QuoteIdentifier (TableName));
355 string [] setClause = new string [Schema.Rows.Count];
359 foreach (DataRow schemaRow in Schema.Rows) {
361 // exclude non updatable columns
362 if (! IsUpdatable (schemaRow))
365 string columnName = GetColumnName (schemaRow);
366 if (columnName == String.Empty)
367 throw new InvalidOperationException ("Cannot form update command. Column name is missing!");
369 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
370 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
372 // create column = value string
373 setClause [count] = String.Format ("{0} = ?", QuoteIdentifier(columnName));
374 AddParameter (_updateCommand, columnName, sqlDbType, length, columnName, DataRowVersion.Current);
378 // create where clause. odbc uses positional parameters. so where class
379 // is created seperate from the above loop.
380 string whereClause = CreateOptWhereClause (_updateCommand);
382 query = String.Format ("{0} {1} WHERE ({2})",
384 String.Join (", ", setClause, 0, count),
386 _updateCommand.CommandText = query;
387 return _updateCommand;
394 OdbcCommand GetDeleteCommand ()
396 // FIXME: check validity of adapter
397 if (_deleteCommand != null)
398 return _deleteCommand;
403 _deleteCommand = new OdbcCommand ();
404 _deleteCommand.Connection = DataAdapter.SelectCommand.Connection;
405 _deleteCommand.Transaction = DataAdapter.SelectCommand.Transaction;
406 _deleteCommand.CommandType = CommandType.Text;
407 _deleteCommand.UpdatedRowSource = UpdateRowSource.None;
409 string query = String.Format ("DELETE FROM {0}", QuoteIdentifier (TableName));
410 string whereClause = CreateOptWhereClause (_deleteCommand);
412 query = String.Format ("{0} WHERE ({1})", query, whereClause);
413 _deleteCommand.CommandText = query;
414 return _deleteCommand;
421 void RefreshSchema ()
424 if (SelectCommand == null)
425 throw new InvalidOperationException ("SelectCommand should be valid");
426 if (SelectCommand.Connection == null)
427 throw new InvalidOperationException ("SelectCommand's Connection should be valid");
429 CommandBehavior behavior = CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo;
430 if (SelectCommand.Connection.State != ConnectionState.Open) {
431 SelectCommand.Connection.Open ();
432 behavior |= CommandBehavior.CloseConnection;
435 OdbcDataReader reader = SelectCommand.ExecuteReader (behavior);
436 _schema = reader.GetSchemaTable ();
439 // force creation of commands
440 _insertCommand = null;
441 _updateCommand = null;
442 _deleteCommand = null;
443 _tableName = String.Empty;
448 protected override void ApplyParameterInfo (DbParameter dbParameter,
450 StatementType statementType,
453 throw new NotImplementedException ();
457 protected override string GetParameterName (int position)
459 throw new NotImplementedException ();
463 protected override string GetParameterName (string parameterName)
465 throw new NotImplementedException ();
469 protected override string GetParameterPlaceholder (int position)
471 throw new NotImplementedException ();
475 protected override void SetRowUpdatingHandler (DbDataAdapter adapter)
477 throw new NotImplementedException ();
488 string QuoteIdentifier (string unquotedIdentifier)
491 throw new NotImplementedException ();
493 if (unquotedIdentifier == null || unquotedIdentifier == String.Empty)
494 return unquotedIdentifier;
495 return String.Format ("{0}{1}{2}", QuotePrefix,
496 unquotedIdentifier, QuoteSuffix);
506 string UnquoteIdentifier (string quotedIdentifier)
509 throw new NotImplementedException ();
511 if (quotedIdentifier == null || quotedIdentifier == String.Empty)
512 return quotedIdentifier;
514 StringBuilder sb = new StringBuilder (quotedIdentifier.Length);
515 sb.Append (quotedIdentifier);
516 if (quotedIdentifier.StartsWith (QuotePrefix))
517 sb.Remove (0,QuotePrefix.Length);
518 if (quotedIdentifier.EndsWith (QuoteSuffix))
519 sb.Remove (sb.Length - QuoteSuffix.Length, QuoteSuffix.Length );
520 return sb.ToString ();
524 private void OnRowUpdating (object sender, OdbcRowUpdatingEventArgs args)
526 if (args.Command != null)
529 switch (args.StatementType) {
530 case StatementType.Insert:
531 args.Command = GetInsertCommand ();
533 case StatementType.Update:
534 args.Command = GetUpdateCommand ();
536 case StatementType.Delete:
537 args.Command = GetDeleteCommand ();
540 } catch (Exception e) {
542 args.Status = UpdateStatus.ErrorsOccurred;
547 #endregion // Methods