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;
55 private string _quotePrefix;
56 private string _quoteSuffix;
59 private DataTable _schema;
60 private string _tableName;
61 private OdbcCommand _insertCommand;
62 private OdbcCommand _updateCommand;
63 private OdbcCommand _deleteCommand;
67 private OdbcRowUpdatingEventHandler rowUpdatingHandler;
73 public OdbcCommandBuilder ()
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 ();
149 [BrowsableAttribute (false)]
150 [OdbcDescriptionAttribute ("The prefix string wrapped around sql objects")]
151 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
152 public string QuotePrefix {
154 if (_quotePrefix == null)
159 if (IsCommandGenerated)
160 throw new InvalidOperationException (
161 "QuotePrefix cannot be set after " +
162 "an Insert, Update or Delete command " +
163 "has been generated.");
164 _quotePrefix = value;
168 [BrowsableAttribute (false)]
169 [OdbcDescriptionAttribute ("The suffix string wrapped around sql objects")]
170 [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
171 public string QuoteSuffix {
173 if (_quoteSuffix == null)
178 if (IsCommandGenerated)
179 throw new InvalidOperationException (
180 "QuoteSuffix cannot be set after " +
181 "an Insert, Update or Delete command " +
182 "has been generated.");
183 _quoteSuffix = value;
188 #endregion // Properties
193 public static void DeriveParameters (OdbcCommand command)
195 throw new NotImplementedException ();
203 void Dispose (bool disposing)
209 // dispose managed resource
210 if (_insertCommand != null)
211 _insertCommand.Dispose ();
212 if (_updateCommand != null)
213 _updateCommand.Dispose ();
214 if (_deleteCommand != null)
215 _deleteCommand.Dispose ();
219 _insertCommand = null;
220 _updateCommand = null;
221 _deleteCommand = null;
227 private bool IsUpdatable (DataRow schemaRow)
229 if ( (! schemaRow.IsNull ("IsAutoIncrement") && (bool) schemaRow ["IsAutoIncrement"])
230 || (! schemaRow.IsNull ("IsRowVersion") && (bool) schemaRow ["IsRowVersion"])
231 || (! schemaRow.IsNull ("IsReadOnly") && (bool) schemaRow ["IsReadOnly"])
232 || (schemaRow.IsNull ("BaseTableName") || ((string) schemaRow ["BaseTableName"]).Length == 0)
238 private string GetColumnName (DataRow schemaRow)
240 string columnName = schemaRow.IsNull ("BaseColumnName") ? String.Empty : (string) schemaRow ["BaseColumnName"];
241 if (columnName == String.Empty)
242 columnName = schemaRow.IsNull ("ColumnName") ? String.Empty : (string) schemaRow ["ColumnName"];
246 private OdbcParameter AddParameter (OdbcCommand cmd, string paramName, OdbcType odbcType,
247 int length, string sourceColumnName, DataRowVersion rowVersion)
250 if (length >= 0 && sourceColumnName != String.Empty)
251 param = cmd.Parameters.Add (paramName, odbcType, length, sourceColumnName);
253 param = cmd.Parameters.Add (paramName, odbcType);
254 param.SourceVersion = rowVersion;
259 * creates where clause for optimistic concurrency
261 private string CreateOptWhereClause (OdbcCommand command, int paramCount)
263 string [] whereClause = new string [Schema.Rows.Count];
267 foreach (DataRow schemaRow in Schema.Rows) {
268 // exclude non updatable columns
269 if (! IsUpdatable (schemaRow))
272 string columnName = GetColumnName (schemaRow);
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 [partCount++] = String.Format ("((? = 1 AND {0} IS NULL) OR ({0} = ?))",
282 GetQuotedString (columnName));
283 OdbcParameter nullParam = AddParameter (
285 GetParameterName (++paramCount),
293 DataRowVersion.Original);
295 AddParameter (command, GetParameterName (++paramCount),
296 sqlDbType, length, columnName,
297 DataRowVersion.Original);
299 whereClause [partCount++] = String.Format ("({0} = ?)",
300 GetQuotedString (columnName));
301 AddParameter (command, GetParameterName (++paramCount),
302 sqlDbType, length, columnName,
303 DataRowVersion.Original);
307 return String.Join (" AND ", whereClause, 0, partCount);
310 private void CreateNewCommand (ref OdbcCommand command)
312 OdbcCommand sourceCommand = SelectCommand;
313 if (command == null) {
314 command = new OdbcCommand ();
315 command.Connection = sourceCommand.Connection;
316 command.CommandTimeout = sourceCommand.CommandTimeout;
317 command.Transaction = sourceCommand.Transaction;
319 command.CommandType = CommandType.Text;
320 command.UpdatedRowSource = UpdateRowSource.None;
321 command.Parameters.Clear ();
324 private OdbcCommand CreateInsertCommand (bool option)
326 CreateNewCommand (ref _insertCommand);
328 string query = String.Format ("INSERT INTO {0}", GetQuotedString (TableName));
329 string [] columns = new string [Schema.Rows.Count];
330 string [] values = new string [Schema.Rows.Count];
334 foreach (DataRow schemaRow in Schema.Rows) {
335 // exclude non updatable columns
336 if (! IsUpdatable (schemaRow))
339 string columnName = GetColumnName (schemaRow);
340 if (columnName == String.Empty)
341 throw new InvalidOperationException ("Cannot form insert command. Column name is missing!");
343 // create column string & value string
344 columns [count] = GetQuotedString (columnName);
345 values [count++] = "?";
347 // create parameter and add
348 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
349 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
351 AddParameter (_insertCommand, GetParameterName (count),
352 sqlDbType, length, columnName, DataRowVersion.Current);
355 query = String.Format (
357 "{0} ({1}) VALUES ({2})",
359 "{0}( {1} ) VALUES ( {2} )",
363 String.Join (", ", columns, 0, count),
364 String.Join (", ", values, 0, count));
366 String.Join (" , ", columns, 0, count),
367 String.Join (" , ", values, 0, count));
369 _insertCommand.CommandText = query;
370 return _insertCommand;
377 OdbcCommand GetInsertCommand ()
379 // FIXME: check validity of adapter
380 if (_insertCommand != null)
381 return _insertCommand;
386 return CreateInsertCommand (false);
390 public new OdbcCommand GetInsertCommand (bool useColumnsForParameterNames)
392 // FIXME: check validity of adapter
393 if (_insertCommand != null)
394 return _insertCommand;
399 return CreateInsertCommand (useColumnsForParameterNames);
403 private OdbcCommand CreateUpdateCommand (bool option)
405 CreateNewCommand (ref _updateCommand);
407 string query = String.Format ("UPDATE {0} SET", GetQuotedString (TableName));
408 string [] setClause = new string [Schema.Rows.Count];
412 foreach (DataRow schemaRow in Schema.Rows) {
413 // exclude non updatable columns
414 if (! IsUpdatable (schemaRow))
417 string columnName = GetColumnName (schemaRow);
418 if (columnName == String.Empty)
419 throw new InvalidOperationException ("Cannot form update command. Column name is missing!");
421 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
422 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
424 // create column = value string
425 setClause [count++] = String.Format ("{0} = ?", GetQuotedString (columnName));
426 AddParameter (_updateCommand, GetParameterName (count),
427 sqlDbType, length, columnName, DataRowVersion.Current);
430 // create where clause. odbc uses positional parameters. so where class
431 // is created seperate from the above loop.
432 string whereClause = CreateOptWhereClause (_updateCommand, count);
434 query = String.Format (
436 "{0} {1} WHERE ({2})",
438 "{0} {1} WHERE ( {2} )",
442 String.Join (", ", setClause, 0, count),
444 String.Join (" , ", setClause, 0, count),
447 _updateCommand.CommandText = query;
448 return _updateCommand;
455 OdbcCommand GetUpdateCommand ()
457 // FIXME: check validity of adapter
458 if (_updateCommand != null)
459 return _updateCommand;
464 return CreateUpdateCommand (false);
468 public new OdbcCommand GetUpdateCommand (bool useColumnsForParameterNames)
470 // FIXME: check validity of adapter
471 if (_updateCommand != null)
472 return _updateCommand;
477 return CreateUpdateCommand (useColumnsForParameterNames);
481 private OdbcCommand CreateDeleteCommand (bool option)
483 CreateNewCommand (ref _deleteCommand);
485 string query = String.Format (
491 GetQuotedString (TableName));
492 string whereClause = CreateOptWhereClause (_deleteCommand, 0);
494 query = String.Format (
502 _deleteCommand.CommandText = query;
503 return _deleteCommand;
510 OdbcCommand GetDeleteCommand ()
512 // FIXME: check validity of adapter
513 if (_deleteCommand != null)
514 return _deleteCommand;
519 return CreateDeleteCommand (false);
523 public new OdbcCommand GetDeleteCommand (bool useColumnsForParameterNames)
525 // FIXME: check validity of adapter
526 if (_deleteCommand != null)
527 return _deleteCommand;
532 return CreateDeleteCommand (useColumnsForParameterNames);
541 void RefreshSchema ()
544 if (SelectCommand == null)
545 throw new InvalidOperationException ("SelectCommand should be valid");
546 if (SelectCommand.Connection == null)
547 throw new InvalidOperationException ("SelectCommand's Connection should be valid");
549 CommandBehavior behavior = CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo;
550 if (SelectCommand.Connection.State != ConnectionState.Open) {
551 SelectCommand.Connection.Open ();
552 behavior |= CommandBehavior.CloseConnection;
555 OdbcDataReader reader = SelectCommand.ExecuteReader (behavior);
556 _schema = reader.GetSchemaTable ();
559 // force creation of commands
560 _insertCommand = null;
561 _updateCommand = null;
562 _deleteCommand = null;
563 _tableName = String.Empty;
569 string GetParameterName (int parameterOrdinal)
572 return String.Format ("p{0}", parameterOrdinal);
574 return String.Format ("@p{0}", parameterOrdinal);
580 protected override void ApplyParameterInfo (DbParameter parameter,
582 StatementType statementType,
585 OdbcParameter odbcParam = (OdbcParameter) parameter;
586 odbcParam.Size = int.Parse (datarow ["ColumnSize"].ToString ());
587 if (datarow ["NumericPrecision"] != DBNull.Value)
588 odbcParam.Precision = byte.Parse (datarow ["NumericPrecision"].ToString ());
589 if (datarow ["NumericScale"] != DBNull.Value)
590 odbcParam.Scale = byte.Parse (datarow ["NumericScale"].ToString ());
591 odbcParam.DbType = (DbType) datarow ["ProviderType"];
594 protected override string GetParameterName (string parameterName)
596 return String.Format("@{0}", parameterName);
599 protected override string GetParameterPlaceholder (int parameterOrdinal)
601 return GetParameterName (parameterOrdinal);
604 // FIXME: According to MSDN - "if this method is called again with
605 // the same DbDataAdapter, the DbCommandBuilder is unregistered for
606 // that DbDataAdapter's RowUpdating event" - this behaviour is yet
608 protected override void SetRowUpdatingHandler (DbDataAdapter adapter)
610 if (!(adapter is OdbcDataAdapter))
611 throw new InvalidOperationException ("Adapter needs to be a SqlDataAdapter");
612 if (rowUpdatingHandler == null)
613 rowUpdatingHandler = new OdbcRowUpdatingEventHandler (OnRowUpdating);
615 ((OdbcDataAdapter) adapter).RowUpdating += rowUpdatingHandler;
618 public override string QuoteIdentifier (string unquotedIdentifier)
620 return QuoteIdentifier (unquotedIdentifier, null);
623 public string QuoteIdentifier (string unquotedIdentifier, OdbcConnection connection)
625 if (unquotedIdentifier == null)
626 throw new ArgumentNullException ("unquotedIdentifier");
628 string prefix = QuotePrefix;
629 string suffix = QuoteSuffix;
631 if (QuotePrefix.Length == 0) {
632 if (connection == null)
633 throw new InvalidOperationException (
634 "An open connection is required if "
635 + "QuotePrefix is not set.");
636 prefix = suffix = GetQuoteCharacter (connection);
639 if (prefix.Length > 0 && prefix != " ") {
641 if (suffix.Length > 0)
642 escaped = unquotedIdentifier.Replace (
643 suffix, suffix + suffix);
645 escaped = unquotedIdentifier;
646 return string.Concat (prefix, escaped, suffix);
648 return unquotedIdentifier;
651 public string UnquoteIdentifier (string quotedIdentifier, OdbcConnection connection)
653 return UnquoteIdentifier (quotedIdentifier);
656 public override string UnquoteIdentifier (string quotedIdentifier)
658 if (quotedIdentifier == null || quotedIdentifier.Length == 0)
659 return quotedIdentifier;
661 StringBuilder sb = new StringBuilder (quotedIdentifier.Length);
662 sb.Append (quotedIdentifier);
663 if (quotedIdentifier.StartsWith (QuotePrefix))
664 sb.Remove (0,QuotePrefix.Length);
665 if (quotedIdentifier.EndsWith (QuoteSuffix))
666 sb.Remove (sb.Length - QuoteSuffix.Length, QuoteSuffix.Length );
667 return sb.ToString ();
671 private void OnRowUpdating (object sender, OdbcRowUpdatingEventArgs args)
673 if (args.Command != null)
676 switch (args.StatementType) {
677 case StatementType.Insert:
678 args.Command = GetInsertCommand ();
680 case StatementType.Update:
681 args.Command = GetUpdateCommand ();
683 case StatementType.Delete:
684 args.Command = GetDeleteCommand ();
687 } catch (Exception e) {
689 args.Status = UpdateStatus.ErrorsOccurred;
693 string GetQuotedString (string unquotedIdentifier)
695 string prefix = QuotePrefix;
696 string suffix = QuoteSuffix;
698 if (prefix.Length == 0 && suffix.Length == 0)
699 return unquotedIdentifier;
701 return String.Format ("{0}{1}{2}", prefix,
702 unquotedIdentifier, suffix);
705 bool IsCommandGenerated {
707 return (_insertCommand != null || _updateCommand != null || _deleteCommand != null);
712 string GetQuoteCharacter (OdbcConnection conn)
714 return conn.GetInfo (OdbcInfo.IdentifierQuoteChar);
718 #endregion // Methods