OdbcCommandBuilder.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. //
  2. // System.Data.Odbc.OdbcCommandBuilder
  3. //
  4. // Author:
  5. // Umadevi S ([email protected])
  6. // Sureshkumar T ([email protected])
  7. //
  8. // Copyright (C) Novell Inc, 2004
  9. //
  10. //
  11. // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
  12. //
  13. // Permission is hereby granted, free of charge, to any person obtaining
  14. // a copy of this software and associated documentation files (the
  15. // "Software"), to deal in the Software without restriction, including
  16. // without limitation the rights to use, copy, modify, merge, publish,
  17. // distribute, sublicense, and/or sell copies of the Software, and to
  18. // permit persons to whom the Software is furnished to do so, subject to
  19. // the following conditions:
  20. //
  21. // The above copyright notice and this permission notice shall be
  22. // included in all copies or substantial portions of the Software.
  23. //
  24. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  25. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  26. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  27. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  28. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  29. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  30. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  31. //
  32. using System.Text;
  33. using System.Data;
  34. using System.Data.Common;
  35. using System.ComponentModel;
  36. namespace System.Data.Odbc
  37. {
  38. /// <summary>
  39. /// 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.
  40. /// </summary>
  41. #if NET_2_0
  42. public sealed class OdbcCommandBuilder : DbCommandBuilder
  43. #else // 1_1
  44. public sealed class OdbcCommandBuilder : Component
  45. #endif // NET_2_0
  46. {
  47. #region Fields
  48. private OdbcDataAdapter _adapter;
  49. private string _quotePrefix;
  50. private string _quoteSuffix;
  51. private DataTable _schema;
  52. private string _tableName;
  53. private OdbcCommand _insertCommand;
  54. private OdbcCommand _updateCommand;
  55. private OdbcCommand _deleteCommand;
  56. bool _disposed;
  57. private OdbcRowUpdatingEventHandler rowUpdatingHandler;
  58. #endregion // Fields
  59. #region Constructors
  60. public OdbcCommandBuilder ()
  61. {
  62. _adapter = null;
  63. _quotePrefix = String.Empty;
  64. _quoteSuffix = String.Empty;
  65. rowUpdatingHandler = null;
  66. }
  67. public OdbcCommandBuilder (OdbcDataAdapter adapter)
  68. : this ()
  69. {
  70. DataAdapter = adapter;
  71. }
  72. #endregion // Constructors
  73. #region Properties
  74. [OdbcDescriptionAttribute ("The DataAdapter for which to automatically generate OdbcCommands")]
  75. [DefaultValue (null)]
  76. public
  77. #if NET_2_0
  78. new
  79. #endif // NET_2_0
  80. OdbcDataAdapter DataAdapter {
  81. get {
  82. return _adapter;
  83. }
  84. set {
  85. if (_adapter == value)
  86. return;
  87. if (rowUpdatingHandler != null)
  88. rowUpdatingHandler = new OdbcRowUpdatingEventHandler (OnRowUpdating);
  89. if (_adapter != null)
  90. _adapter.RowUpdating -= rowUpdatingHandler;
  91. _adapter = value;
  92. if (_adapter != null)
  93. _adapter.RowUpdating += rowUpdatingHandler;
  94. }
  95. }
  96. private OdbcCommand SelectCommand
  97. {
  98. get {
  99. if (DataAdapter == null)
  100. return null;
  101. return DataAdapter.SelectCommand;
  102. }
  103. }
  104. private DataTable Schema
  105. {
  106. get {
  107. if (_schema == null)
  108. RefreshSchema ();
  109. return _schema;
  110. }
  111. }
  112. private string TableName
  113. {
  114. get {
  115. if (_tableName != String.Empty)
  116. return _tableName;
  117. DataRow [] schemaRows = Schema.Select ("BaseTableName is not null and BaseTableName <> ''");
  118. if (schemaRows.Length > 1) {
  119. string tableName = (string) schemaRows [0] ["BaseTableName"];
  120. foreach (DataRow schemaRow in schemaRows) {
  121. if ( (string) schemaRow ["BaseTableName"] != tableName)
  122. throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
  123. }
  124. }
  125. if (schemaRows.Length == 0)
  126. throw new InvalidOperationException ("Cannot determine the base table name. Cannot proceed");
  127. _tableName = schemaRows [0] ["BaseTableName"].ToString ();
  128. return _tableName;
  129. }
  130. }
  131. [BrowsableAttribute (false)]
  132. [OdbcDescriptionAttribute ("The prefix string wrapped around sql objects")]
  133. [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
  134. #if ONLY_1_1
  135. public
  136. #else
  137. new
  138. #endif
  139. string QuotePrefix {
  140. get {
  141. return _quotePrefix;
  142. }
  143. set {
  144. _quotePrefix = value;
  145. }
  146. }
  147. [BrowsableAttribute (false)]
  148. [OdbcDescriptionAttribute ("The suffix string wrapped around sql objects")]
  149. [DesignerSerializationVisibilityAttribute (DesignerSerializationVisibility.Hidden)]
  150. #if ONLY_1_1
  151. public
  152. #else
  153. new
  154. #endif // NET_2_0
  155. string QuoteSuffix {
  156. get {
  157. return _quoteSuffix;
  158. }
  159. set {
  160. _quoteSuffix = value;
  161. }
  162. }
  163. #endregion // Properties
  164. #region Methods
  165. [MonoTODO]
  166. public static void DeriveParameters (OdbcCommand command)
  167. {
  168. throw new NotImplementedException ();
  169. }
  170. #if ONLY_1_1
  171. protected override
  172. #else
  173. new
  174. #endif
  175. void Dispose (bool disposing)
  176. {
  177. if (_disposed)
  178. return;
  179. if (disposing) {
  180. // dispose managed resource
  181. if (_insertCommand != null) _insertCommand.Dispose ();
  182. if (_updateCommand != null) _updateCommand.Dispose ();
  183. if (_deleteCommand != null) _deleteCommand.Dispose ();
  184. if (_schema != null) _insertCommand.Dispose ();
  185. _insertCommand = null;
  186. _updateCommand = null;
  187. _deleteCommand = null;
  188. _schema = null;
  189. }
  190. _disposed = true;
  191. }
  192. private bool IsUpdatable (DataRow schemaRow)
  193. {
  194. if ( (! schemaRow.IsNull ("IsAutoIncrement") && (bool) schemaRow ["IsAutoIncrement"])
  195. || (! schemaRow.IsNull ("IsHidden") && (bool) schemaRow ["IsHidden"])
  196. || (! schemaRow.IsNull ("IsExpression") && (bool) schemaRow ["IsExpression"])
  197. || (! schemaRow.IsNull ("IsRowVersion") && (bool) schemaRow ["IsRowVersion"])
  198. || (! schemaRow.IsNull ("IsReadOnly") && (bool) schemaRow ["IsReadOnly"])
  199. )
  200. return false;
  201. return true;
  202. }
  203. private string GetColumnName (DataRow schemaRow)
  204. {
  205. string columnName = schemaRow.IsNull ("BaseColumnName") ? String.Empty : (string) schemaRow ["BaseColumnName"];
  206. if (columnName == String.Empty)
  207. columnName = schemaRow.IsNull ("ColumnName") ? String.Empty : (string) schemaRow ["ColumnName"];
  208. return columnName;
  209. }
  210. private OdbcParameter AddParameter (OdbcCommand cmd, string paramName, OdbcType odbcType,
  211. int length, string sourceColumnName, DataRowVersion rowVersion)
  212. {
  213. OdbcParameter param;
  214. if (length >= 0 && sourceColumnName != String.Empty)
  215. param = cmd.Parameters.Add (paramName, odbcType, length, sourceColumnName);
  216. else
  217. param = cmd.Parameters.Add (paramName, odbcType);
  218. param.SourceVersion = rowVersion;
  219. return param;
  220. }
  221. /*
  222. * creates where clause for optimistic concurrency
  223. */
  224. private string CreateOptWhereClause (OdbcCommand command, bool option)
  225. {
  226. string [] whereClause = new string [Schema.Rows.Count];
  227. int count = 0;
  228. foreach (DataRow schemaRow in Schema.Rows) {
  229. // exclude non updatable columns
  230. if (! IsUpdatable (schemaRow))
  231. continue;
  232. string columnName = null;
  233. if (option)
  234. columnName = GetColumnName (schemaRow);
  235. else
  236. columnName = String.Format ("@p{0}", count);
  237. if (columnName == String.Empty)
  238. throw new InvalidOperationException ("Cannot form delete command. Column name is missing!");
  239. bool allowNull = schemaRow.IsNull ("AllowDBNull") || (bool) schemaRow ["AllowDBNull"];
  240. OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
  241. int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
  242. if (allowNull) {
  243. whereClause [count] = String.Format ("((? = 1 AND {0} IS NULL) OR ({0} = ?))",
  244. columnName);
  245. AddParameter (command, columnName, sqlDbType, length, columnName, DataRowVersion.Original);
  246. AddParameter (command, columnName, sqlDbType, length, columnName, DataRowVersion.Original);
  247. } else {
  248. whereClause [count] = String.Format ( "({0} = ?)", columnName);
  249. AddParameter (command, columnName, sqlDbType, length, columnName, DataRowVersion.Original);
  250. }
  251. count++;
  252. }
  253. return String.Join (" AND ", whereClause, 0, count);
  254. }
  255. private void CreateNewCommand (ref OdbcCommand command)
  256. {
  257. OdbcCommand sourceCommand = SelectCommand;
  258. if (command == null) {
  259. command = new OdbcCommand ();
  260. command.Connection = sourceCommand.Connection;
  261. command.CommandTimeout = sourceCommand.CommandTimeout;
  262. command.Transaction = sourceCommand.Transaction;
  263. }
  264. command.CommandType = CommandType.Text;
  265. command.UpdatedRowSource = UpdateRowSource.None;
  266. command.Parameters.Clear ();
  267. }
  268. private OdbcCommand CreateInsertCommand (bool option)
  269. {
  270. CreateNewCommand (ref _insertCommand);
  271. string query = String.Format ("INSERT INTO {0}", QuoteIdentifier (TableName));
  272. string [] columns = new string [Schema.Rows.Count];
  273. string [] values = new string [Schema.Rows.Count];
  274. int count = 0;
  275. foreach (DataRow schemaRow in Schema.Rows) {
  276. // exclude non updatable columns
  277. if (! IsUpdatable (schemaRow))
  278. continue;
  279. string columnName = null;
  280. if (option)
  281. columnName = GetColumnName (schemaRow);
  282. else
  283. columnName = String.Format ("@p{0}", count);
  284. if (columnName == String.Empty)
  285. throw new InvalidOperationException ("Cannot form insert command. Column name is missing!");
  286. // create column string & value string
  287. columns [count] = QuoteIdentifier(columnName);
  288. values [count++] = "?";
  289. // create parameter and add
  290. OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
  291. int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
  292. AddParameter (_insertCommand, columnName, sqlDbType, length, columnName, DataRowVersion.Current);
  293. }
  294. query = String.Format ("{0} ({1}) VALUES ({2})",
  295. query,
  296. String.Join (", ", columns, 0, count),
  297. String.Join (", ", values, 0, count) );
  298. _insertCommand.CommandText = query;
  299. return _insertCommand;
  300. }
  301. public
  302. #if NET_2_0
  303. new
  304. #endif // NET_2_0
  305. OdbcCommand GetInsertCommand ()
  306. {
  307. // FIXME: check validity of adapter
  308. if (_insertCommand != null)
  309. return _insertCommand;
  310. if (_schema == null)
  311. RefreshSchema ();
  312. return CreateInsertCommand (false);
  313. }
  314. #if NET_2_0
  315. public new OdbcCommand GetInsertCommand (bool option)
  316. {
  317. // FIXME: check validity of adapter
  318. if (_insertCommand != null)
  319. return _insertCommand;
  320. if (_schema == null)
  321. RefreshSchema ();
  322. return CreateInsertCommand (option);
  323. }
  324. #endif // NET_2_0
  325. private OdbcCommand CreateUpdateCommand (bool option)
  326. {
  327. CreateNewCommand (ref _updateCommand);
  328. string query = String.Format ("UPDATE {0} SET", QuoteIdentifier (TableName));
  329. string [] setClause = new string [Schema.Rows.Count];
  330. int count = 0;
  331. foreach (DataRow schemaRow in Schema.Rows) {
  332. // exclude non updatable columns
  333. if (! IsUpdatable (schemaRow))
  334. continue;
  335. string columnName = null;
  336. if (option)
  337. columnName = GetColumnName (schemaRow);
  338. else
  339. columnName = String.Format ("@p{0}", count);
  340. if (columnName == String.Empty)
  341. throw new InvalidOperationException ("Cannot form update command. Column name is missing!");
  342. OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
  343. int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
  344. // create column = value string
  345. setClause [count] = String.Format ("{0} = ?", QuoteIdentifier(columnName));
  346. AddParameter (_updateCommand, columnName, sqlDbType, length, columnName, DataRowVersion.Current);
  347. count++;
  348. }
  349. // create where clause. odbc uses positional parameters. so where class
  350. // is created seperate from the above loop.
  351. string whereClause = CreateOptWhereClause (_updateCommand, option);
  352. query = String.Format ("{0} {1} WHERE ({2})",
  353. query,
  354. String.Join (", ", setClause, 0, count),
  355. whereClause);
  356. _updateCommand.CommandText = query;
  357. return _updateCommand;
  358. }
  359. public
  360. #if NET_2_0
  361. new
  362. #endif // NET_2_0
  363. OdbcCommand GetUpdateCommand ()
  364. {
  365. // FIXME: check validity of adapter
  366. if (_updateCommand != null)
  367. return _updateCommand;
  368. if (_schema == null)
  369. RefreshSchema ();
  370. return CreateUpdateCommand (false);
  371. }
  372. #if NET_2_0
  373. public new OdbcCommand GetUpdateCommand (bool option)
  374. {
  375. // FIXME: check validity of adapter
  376. if (_updateCommand != null)
  377. return _updateCommand;
  378. if (_schema == null)
  379. RefreshSchema ();
  380. return CreateUpdateCommand (option);
  381. }
  382. #endif // NET_2_0
  383. private OdbcCommand CreateDeleteCommand (bool option)
  384. {
  385. CreateNewCommand (ref _deleteCommand);
  386. string query = String.Format ("DELETE FROM {0}", QuoteIdentifier (TableName));
  387. string whereClause = CreateOptWhereClause (_deleteCommand, option);
  388. query = String.Format ("{0} WHERE ({1})", query, whereClause);
  389. _deleteCommand.CommandText = query;
  390. return _deleteCommand;
  391. }
  392. public
  393. #if NET_2_0
  394. new
  395. #endif // NET_2_0
  396. OdbcCommand GetDeleteCommand ()
  397. {
  398. // FIXME: check validity of adapter
  399. if (_deleteCommand != null)
  400. return _deleteCommand;
  401. if (_schema == null)
  402. RefreshSchema ();
  403. return CreateDeleteCommand (false);
  404. }
  405. #if NET_2_0
  406. public new OdbcCommand GetDeleteCommand (bool option)
  407. {
  408. // FIXME: check validity of adapter
  409. if (_deleteCommand != null)
  410. return _deleteCommand;
  411. if (_schema == null)
  412. RefreshSchema ();
  413. return CreateDeleteCommand (option);
  414. }
  415. #endif // NET_2_0
  416. #if ONLY_1_1
  417. public
  418. #else
  419. new
  420. #endif // NET_2_0
  421. void RefreshSchema ()
  422. {
  423. // creates metadata
  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");
  428. CommandBehavior behavior = CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo;
  429. if (SelectCommand.Connection.State != ConnectionState.Open) {
  430. SelectCommand.Connection.Open ();
  431. behavior |= CommandBehavior.CloseConnection;
  432. }
  433. OdbcDataReader reader = SelectCommand.ExecuteReader (behavior);
  434. _schema = reader.GetSchemaTable ();
  435. reader.Close ();
  436. // force creation of commands
  437. _insertCommand = null;
  438. _updateCommand = null;
  439. _deleteCommand = null;
  440. _tableName = String.Empty;
  441. }
  442. #if NET_2_0
  443. protected override void ApplyParameterInfo (DbParameter dbParameter,
  444. DataRow row,
  445. StatementType statementType,
  446. bool whereClause)
  447. {
  448. OdbcParameter parameter = (OdbcParameter) dbParameter;
  449. parameter.Size = int.Parse (row ["ColumnSize"].ToString ());
  450. if (row ["NumericPrecision"] != DBNull.Value) {
  451. parameter.Precision = byte.Parse (row ["NumericPrecision"].ToString ());
  452. }
  453. if (row ["NumericScale"] != DBNull.Value) {
  454. parameter.Scale = byte.Parse (row ["NumericScale"].ToString ());
  455. }
  456. parameter.DbType = (DbType) row ["ProviderType"];
  457. }
  458. protected override string GetParameterName (int position)
  459. {
  460. return String.Format("@p{0}", position);
  461. }
  462. protected override string GetParameterName (string parameterName)
  463. {
  464. return String.Format("@{0}", parameterName);
  465. }
  466. protected override string GetParameterPlaceholder (int position)
  467. {
  468. return GetParameterName (position);
  469. }
  470. // FIXME: According to MSDN - "if this method is called again with
  471. // the same DbDataAdapter, the DbCommandBuilder is unregistered for
  472. // that DbDataAdapter's RowUpdating event" - this behaviour is yet
  473. // to be verified
  474. protected override void SetRowUpdatingHandler (DbDataAdapter adapter)
  475. {
  476. if (!(adapter is OdbcDataAdapter)) {
  477. throw new InvalidOperationException ("Adapter needs to be a SqlDataAdapter");
  478. }
  479. if (rowUpdatingHandler == null)
  480. rowUpdatingHandler = new OdbcRowUpdatingEventHandler (OnRowUpdating);
  481. ((OdbcDataAdapter) adapter).RowUpdating += rowUpdatingHandler;
  482. }
  483. #endif // NET_2_0
  484. #if NET_2_0
  485. public override
  486. #else
  487. private
  488. #endif
  489. string QuoteIdentifier (string unquotedIdentifier)
  490. {
  491. if (unquotedIdentifier == null || unquotedIdentifier == String.Empty)
  492. return unquotedIdentifier;
  493. return String.Format ("{0}{1}{2}", QuotePrefix,
  494. unquotedIdentifier, QuoteSuffix);
  495. }
  496. #if NET_2_0
  497. // FIXME: Not sure what the extra "connection" param does!
  498. public string QuoteIdentifier (string unquotedIdentifier, OdbcConnection connection)
  499. {
  500. return QuoteIdentifier (unquotedIdentifier);
  501. }
  502. public string UnquoteIdentifier (string quotedIdentifier, OdbcConnection connection)
  503. {
  504. return UnquoteIdentifier (quotedIdentifier);
  505. }
  506. #endif
  507. #if NET_2_0
  508. public override
  509. #else
  510. private
  511. #endif
  512. string UnquoteIdentifier (string quotedIdentifier)
  513. {
  514. if (quotedIdentifier == null || quotedIdentifier == String.Empty)
  515. return quotedIdentifier;
  516. StringBuilder sb = new StringBuilder (quotedIdentifier.Length);
  517. sb.Append (quotedIdentifier);
  518. if (quotedIdentifier.StartsWith (QuotePrefix))
  519. sb.Remove (0,QuotePrefix.Length);
  520. if (quotedIdentifier.EndsWith (QuoteSuffix))
  521. sb.Remove (sb.Length - QuoteSuffix.Length, QuoteSuffix.Length );
  522. return sb.ToString ();
  523. }
  524. private void OnRowUpdating (object sender, OdbcRowUpdatingEventArgs args)
  525. {
  526. if (args.Command != null)
  527. return;
  528. Console.WriteLine (Environment.StackTrace);
  529. try {
  530. switch (args.StatementType) {
  531. case StatementType.Insert:
  532. args.Command = GetInsertCommand ();
  533. break;
  534. case StatementType.Update:
  535. args.Command = GetUpdateCommand ();
  536. break;
  537. case StatementType.Delete:
  538. args.Command = GetDeleteCommand ();
  539. break;
  540. }
  541. } catch (Exception e) {
  542. args.Errors = e;
  543. args.Status = UpdateStatus.ErrorsOccurred;
  544. }
  545. }
  546. #endregion // Methods
  547. }
  548. }