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.
45 public sealed class OdbcCommandBuilder : DbCommandBuilder
49 private OdbcDataAdapter _adapter;
51 private DataTable _schema;
52 private string _tableName;
53 private OdbcCommand _insertCommand;
54 private OdbcCommand _updateCommand;
55 private OdbcCommand _deleteCommand;
59 private OdbcRowUpdatingEventHandler rowUpdatingHandler;
65 public OdbcCommandBuilder ()
69 public OdbcCommandBuilder (OdbcDataAdapter adapter)
72 DataAdapter = adapter;
75 #endregion // Constructors
79 [OdbcDescriptionAttribute ("The DataAdapter for which to automatically generate OdbcCommands")]
83 OdbcDataAdapter DataAdapter {
88 if (_adapter == value)
91 if (rowUpdatingHandler != null)
92 rowUpdatingHandler = new OdbcRowUpdatingEventHandler (OnRowUpdating);
95 _adapter.RowUpdating -= rowUpdatingHandler;
98 _adapter.RowUpdating += rowUpdatingHandler;
102 private OdbcCommand SelectCommand {
104 if (DataAdapter == null)
106 return DataAdapter.SelectCommand;
110 private DataTable Schema {
118 private string TableName {
120 if (_tableName != string.Empty)
123 DataRow [] schemaRows = Schema.Select ("BaseTableName is not null and BaseTableName <> ''");
124 if (schemaRows.Length > 1) {
125 string tableName = (string) schemaRows [0] ["BaseTableName"];
126 foreach (DataRow schemaRow in schemaRows) {
127 if ( (string) schemaRow ["BaseTableName"] != tableName)
128 throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
131 if (schemaRows.Length == 0)
132 throw new InvalidOperationException ("Cannot determine the base table name. Cannot proceed");
133 _tableName = schemaRows [0] ["BaseTableName"].ToString ();
139 #endregion // Properties
144 public static void DeriveParameters (OdbcCommand command)
146 throw new NotImplementedException ();
150 void Dispose (bool disposing)
156 // dispose managed resource
157 if (_insertCommand != null)
158 _insertCommand.Dispose ();
159 if (_updateCommand != null)
160 _updateCommand.Dispose ();
161 if (_deleteCommand != null)
162 _deleteCommand.Dispose ();
166 _insertCommand = null;
167 _updateCommand = null;
168 _deleteCommand = null;
174 private bool IsUpdatable (DataRow schemaRow)
176 if ( (! schemaRow.IsNull ("IsAutoIncrement") && (bool) schemaRow ["IsAutoIncrement"])
177 || (! schemaRow.IsNull ("IsRowVersion") && (bool) schemaRow ["IsRowVersion"])
178 || (! schemaRow.IsNull ("IsReadOnly") && (bool) schemaRow ["IsReadOnly"])
179 || (schemaRow.IsNull ("BaseTableName") || ((string) schemaRow ["BaseTableName"]).Length == 0)
185 private string GetColumnName (DataRow schemaRow)
187 string columnName = schemaRow.IsNull ("BaseColumnName") ? String.Empty : (string) schemaRow ["BaseColumnName"];
188 if (columnName == String.Empty)
189 columnName = schemaRow.IsNull ("ColumnName") ? String.Empty : (string) schemaRow ["ColumnName"];
193 private OdbcParameter AddParameter (OdbcCommand cmd, string paramName, OdbcType odbcType,
194 int length, string sourceColumnName, DataRowVersion rowVersion)
197 if (length >= 0 && sourceColumnName != String.Empty)
198 param = cmd.Parameters.Add (paramName, odbcType, length, sourceColumnName);
200 param = cmd.Parameters.Add (paramName, odbcType);
201 param.SourceVersion = rowVersion;
206 * creates where clause for optimistic concurrency
208 private string CreateOptWhereClause (OdbcCommand command, int paramCount)
210 string [] whereClause = new string [Schema.Rows.Count];
214 foreach (DataRow schemaRow in Schema.Rows) {
215 // exclude non updatable columns
216 if (! IsUpdatable (schemaRow))
219 string columnName = GetColumnName (schemaRow);
220 if (columnName == String.Empty)
221 throw new InvalidOperationException ("Cannot form delete command. Column name is missing!");
223 bool allowNull = schemaRow.IsNull ("AllowDBNull") || (bool) schemaRow ["AllowDBNull"];
224 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
225 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
228 whereClause [partCount++] = String.Format ("((? = 1 AND {0} IS NULL) OR ({0} = ?))",
229 GetQuotedString (columnName));
230 OdbcParameter nullParam = AddParameter (
232 GetParameterName (++paramCount),
236 DataRowVersion.Original);
238 AddParameter (command, GetParameterName (++paramCount),
239 sqlDbType, length, columnName,
240 DataRowVersion.Original);
242 whereClause [partCount++] = String.Format ("({0} = ?)",
243 GetQuotedString (columnName));
244 AddParameter (command, GetParameterName (++paramCount),
245 sqlDbType, length, columnName,
246 DataRowVersion.Original);
250 return String.Join (" AND ", whereClause, 0, partCount);
253 private void CreateNewCommand (ref OdbcCommand command)
255 OdbcCommand sourceCommand = SelectCommand;
256 if (command == null) {
257 command = new OdbcCommand ();
258 command.Connection = sourceCommand.Connection;
259 command.CommandTimeout = sourceCommand.CommandTimeout;
260 command.Transaction = sourceCommand.Transaction;
262 command.CommandType = CommandType.Text;
263 command.UpdatedRowSource = UpdateRowSource.None;
264 command.Parameters.Clear ();
267 private OdbcCommand CreateInsertCommand (bool option)
269 CreateNewCommand (ref _insertCommand);
271 string query = String.Format ("INSERT INTO {0}", GetQuotedString (TableName));
272 string [] columns = new string [Schema.Rows.Count];
273 string [] values = new string [Schema.Rows.Count];
277 foreach (DataRow schemaRow in Schema.Rows) {
278 // exclude non updatable columns
279 if (! IsUpdatable (schemaRow))
282 string columnName = GetColumnName (schemaRow);
283 if (columnName == String.Empty)
284 throw new InvalidOperationException ("Cannot form insert command. Column name is missing!");
286 // create column string & value string
287 columns [count] = GetQuotedString (columnName);
288 values [count++] = "?";
290 // create parameter and add
291 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
292 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
294 AddParameter (_insertCommand, GetParameterName (count),
295 sqlDbType, length, columnName, DataRowVersion.Current);
298 query = String.Format (
299 "{0} ({1}) VALUES ({2})",
301 String.Join (", ", columns, 0, count),
302 String.Join (", ", values, 0, count));
303 _insertCommand.CommandText = query;
304 return _insertCommand;
309 OdbcCommand GetInsertCommand ()
311 // FIXME: check validity of adapter
312 if (_insertCommand != null)
313 return _insertCommand;
318 return CreateInsertCommand (false);
321 public new OdbcCommand GetInsertCommand (bool useColumnsForParameterNames)
323 // FIXME: check validity of adapter
324 if (_insertCommand != null)
325 return _insertCommand;
330 return CreateInsertCommand (useColumnsForParameterNames);
333 private OdbcCommand CreateUpdateCommand (bool option)
335 CreateNewCommand (ref _updateCommand);
337 string query = String.Format ("UPDATE {0} SET", GetQuotedString (TableName));
338 string [] setClause = new string [Schema.Rows.Count];
342 foreach (DataRow schemaRow in Schema.Rows) {
343 // exclude non updatable columns
344 if (! IsUpdatable (schemaRow))
347 string columnName = GetColumnName (schemaRow);
348 if (columnName == String.Empty)
349 throw new InvalidOperationException ("Cannot form update command. Column name is missing!");
351 OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
352 int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
354 // create column = value string
355 setClause [count++] = String.Format ("{0} = ?", GetQuotedString (columnName));
356 AddParameter (_updateCommand, GetParameterName (count),
357 sqlDbType, length, columnName, DataRowVersion.Current);
360 // create where clause. odbc uses positional parameters. so where class
361 // is created seperate from the above loop.
362 string whereClause = CreateOptWhereClause (_updateCommand, count);
364 query = String.Format (
365 "{0} {1} WHERE ({2})",
367 String.Join (", ", setClause, 0, count),
369 _updateCommand.CommandText = query;
370 return _updateCommand;
375 OdbcCommand GetUpdateCommand ()
377 // FIXME: check validity of adapter
378 if (_updateCommand != null)
379 return _updateCommand;
384 return CreateUpdateCommand (false);
387 public new OdbcCommand GetUpdateCommand (bool useColumnsForParameterNames)
389 // FIXME: check validity of adapter
390 if (_updateCommand != null)
391 return _updateCommand;
396 return CreateUpdateCommand (useColumnsForParameterNames);
399 private OdbcCommand CreateDeleteCommand (bool option)
401 CreateNewCommand (ref _deleteCommand);
403 string query = String.Format (
405 GetQuotedString (TableName));
406 string whereClause = CreateOptWhereClause (_deleteCommand, 0);
408 query = String.Format (
412 _deleteCommand.CommandText = query;
413 return _deleteCommand;
418 OdbcCommand GetDeleteCommand ()
420 // FIXME: check validity of adapter
421 if (_deleteCommand != null)
422 return _deleteCommand;
427 return CreateDeleteCommand (false);
430 public new OdbcCommand GetDeleteCommand (bool useColumnsForParameterNames)
432 // FIXME: check validity of adapter
433 if (_deleteCommand != null)
434 return _deleteCommand;
439 return CreateDeleteCommand (useColumnsForParameterNames);
443 void RefreshSchema ()
446 if (SelectCommand == null)
447 throw new InvalidOperationException ("SelectCommand should be valid");
448 if (SelectCommand.Connection == null)
449 throw new InvalidOperationException ("SelectCommand's Connection should be valid");
451 CommandBehavior behavior = CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo;
452 if (SelectCommand.Connection.State != ConnectionState.Open) {
453 SelectCommand.Connection.Open ();
454 behavior |= CommandBehavior.CloseConnection;
457 OdbcDataReader reader = SelectCommand.ExecuteReader (behavior);
458 _schema = reader.GetSchemaTable ();
461 // force creation of commands
462 _insertCommand = null;
463 _updateCommand = null;
464 _deleteCommand = null;
465 _tableName = String.Empty;
469 string GetParameterName (int parameterOrdinal)
471 return String.Format ("p{0}", parameterOrdinal);
475 protected override void ApplyParameterInfo (DbParameter parameter,
477 StatementType statementType,
480 OdbcParameter odbcParam = (OdbcParameter) parameter;
481 odbcParam.Size = int.Parse (datarow ["ColumnSize"].ToString ());
482 if (datarow ["NumericPrecision"] != DBNull.Value)
483 odbcParam.Precision = byte.Parse (datarow ["NumericPrecision"].ToString ());
484 if (datarow ["NumericScale"] != DBNull.Value)
485 odbcParam.Scale = byte.Parse (datarow ["NumericScale"].ToString ());
486 odbcParam.DbType = (DbType) datarow ["ProviderType"];
489 protected override string GetParameterName (string parameterName)
491 return String.Format("@{0}", parameterName);
494 protected override string GetParameterPlaceholder (int parameterOrdinal)
496 return GetParameterName (parameterOrdinal);
499 // FIXME: According to MSDN - "if this method is called again with
500 // the same DbDataAdapter, the DbCommandBuilder is unregistered for
501 // that DbDataAdapter's RowUpdating event" - this behaviour is yet
503 protected override void SetRowUpdatingHandler (DbDataAdapter adapter)
505 if (!(adapter is OdbcDataAdapter))
506 throw new InvalidOperationException ("Adapter needs to be a SqlDataAdapter");
507 if (rowUpdatingHandler == null)
508 rowUpdatingHandler = new OdbcRowUpdatingEventHandler (OnRowUpdating);
510 ((OdbcDataAdapter) adapter).RowUpdating += rowUpdatingHandler;
513 public override string QuoteIdentifier (string unquotedIdentifier)
515 return QuoteIdentifier (unquotedIdentifier, null);
518 public string QuoteIdentifier (string unquotedIdentifier, OdbcConnection connection)
520 if (unquotedIdentifier == null)
521 throw new ArgumentNullException ("unquotedIdentifier");
523 string prefix = QuotePrefix;
524 string suffix = QuoteSuffix;
526 if (QuotePrefix.Length == 0) {
527 if (connection == null)
528 throw new InvalidOperationException (
529 "An open connection is required if "
530 + "QuotePrefix is not set.");
531 prefix = suffix = GetQuoteCharacter (connection);
534 if (prefix.Length > 0 && prefix != " ") {
536 if (suffix.Length > 0)
537 escaped = unquotedIdentifier.Replace (
538 suffix, suffix + suffix);
540 escaped = unquotedIdentifier;
541 return string.Concat (prefix, escaped, suffix);
543 return unquotedIdentifier;
546 public string UnquoteIdentifier (string quotedIdentifier, OdbcConnection connection)
548 return UnquoteIdentifier (quotedIdentifier);
551 public override string UnquoteIdentifier (string quotedIdentifier)
553 if (quotedIdentifier == null || quotedIdentifier.Length == 0)
554 return quotedIdentifier;
556 StringBuilder sb = new StringBuilder (quotedIdentifier.Length);
557 sb.Append (quotedIdentifier);
558 if (quotedIdentifier.StartsWith (QuotePrefix))
559 sb.Remove (0,QuotePrefix.Length);
560 if (quotedIdentifier.EndsWith (QuoteSuffix))
561 sb.Remove (sb.Length - QuoteSuffix.Length, QuoteSuffix.Length );
562 return sb.ToString ();
565 private void OnRowUpdating (object sender, OdbcRowUpdatingEventArgs args)
567 if (args.Command != null)
570 switch (args.StatementType) {
571 case StatementType.Insert:
572 args.Command = GetInsertCommand ();
574 case StatementType.Update:
575 args.Command = GetUpdateCommand ();
577 case StatementType.Delete:
578 args.Command = GetDeleteCommand ();
581 } catch (Exception e) {
583 args.Status = UpdateStatus.ErrorsOccurred;
587 string GetQuotedString (string unquotedIdentifier)
589 string prefix = QuotePrefix;
590 string suffix = QuoteSuffix;
592 if (prefix.Length == 0 && suffix.Length == 0)
593 return unquotedIdentifier;
595 return String.Format ("{0}{1}{2}", prefix,
596 unquotedIdentifier, suffix);
599 bool IsCommandGenerated {
601 return (_insertCommand != null || _updateCommand != null || _deleteCommand != null);
605 string GetQuoteCharacter (OdbcConnection conn)
607 return conn.GetInfo (OdbcInfo.IdentifierQuoteChar);
610 #endregion // Methods