OdbcCommandBuilder.cs 17 KB

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