OdbcCommandBuilder.cs 18 KB

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