OdbcCommandBuilder.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  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. public
  283. #if NET_2_0
  284. new
  285. #endif // NET_2_0
  286. OdbcCommand GetUpdateCommand ()
  287. {
  288. // FIXME: check validity of adapter
  289. if (_updateCommand != null)
  290. return _updateCommand;
  291. if (_schema == null)
  292. RefreshSchema ();
  293. _updateCommand = new OdbcCommand ();
  294. _updateCommand.Connection = DataAdapter.SelectCommand.Connection;
  295. _updateCommand.Transaction = DataAdapter.SelectCommand.Transaction;
  296. _updateCommand.CommandType = CommandType.Text;
  297. _updateCommand.UpdatedRowSource = UpdateRowSource.None;
  298. string query = String.Format ("UPDATE {0} SET", QuoteIdentifier (TableName));
  299. string [] setClause = new string [Schema.Rows.Count];
  300. int count = 0;
  301. foreach (DataRow schemaRow in Schema.Rows) {
  302. // exclude non updatable columns
  303. if (! IsUpdatable (schemaRow))
  304. continue;
  305. string columnName = GetColumnName (schemaRow);
  306. if (columnName == String.Empty)
  307. throw new InvalidOperationException ("Cannot form update command. Column name is missing!");
  308. OdbcType sqlDbType = schemaRow.IsNull ("ProviderType") ? OdbcType.VarChar : (OdbcType) schemaRow ["ProviderType"];
  309. int length = schemaRow.IsNull ("ColumnSize") ? -1 : (int) schemaRow ["ColumnSize"];
  310. // create column = value string
  311. setClause [count] = String.Format ("{0} = ?", QuoteIdentifier(columnName));
  312. AddParameter (_updateCommand, columnName, sqlDbType, length, columnName, DataRowVersion.Current);
  313. count++;
  314. }
  315. // create where clause. odbc uses positional parameters. so where class
  316. // is created seperate from the above loop.
  317. string whereClause = CreateOptWhereClause (_updateCommand);
  318. query = String.Format ("{0} {1} WHERE ({2})",
  319. query,
  320. String.Join (", ", setClause, 0, count),
  321. whereClause);
  322. _updateCommand.CommandText = query;
  323. return _updateCommand;
  324. }
  325. public
  326. #if NET_2_0
  327. new
  328. #endif // NET_2_0
  329. OdbcCommand GetDeleteCommand ()
  330. {
  331. // FIXME: check validity of adapter
  332. if (_deleteCommand != null)
  333. return _deleteCommand;
  334. if (_schema == null)
  335. RefreshSchema ();
  336. _deleteCommand = new OdbcCommand ();
  337. _deleteCommand.Connection = DataAdapter.SelectCommand.Connection;
  338. _deleteCommand.Transaction = DataAdapter.SelectCommand.Transaction;
  339. _deleteCommand.CommandType = CommandType.Text;
  340. _deleteCommand.UpdatedRowSource = UpdateRowSource.None;
  341. string query = String.Format ("DELETE FROM {0}", QuoteIdentifier (TableName));
  342. string whereClause = CreateOptWhereClause (_deleteCommand);
  343. query = String.Format ("{0} WHERE ({1})", query, whereClause);
  344. _deleteCommand.CommandText = query;
  345. return _deleteCommand;
  346. }
  347. public
  348. #if NET_2_0
  349. override
  350. #endif // NET_2_0
  351. void RefreshSchema ()
  352. {
  353. // creates metadata
  354. if (SelectCommand == null)
  355. throw new InvalidOperationException ("SelectCommand should be valid");
  356. if (SelectCommand.Connection == null)
  357. throw new InvalidOperationException ("SelectCommand's Connection should be valid");
  358. CommandBehavior behavior = CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo;
  359. if (SelectCommand.Connection.State != ConnectionState.Open) {
  360. SelectCommand.Connection.Open ();
  361. behavior |= CommandBehavior.CloseConnection;
  362. }
  363. OdbcDataReader reader = SelectCommand.ExecuteReader (behavior);
  364. _schema = reader.GetSchemaTable ();
  365. reader.Close ();
  366. // force creation of commands
  367. _insertCommand = null;
  368. _updateCommand = null;
  369. _deleteCommand = null;
  370. _tableName = String.Empty;
  371. }
  372. #if NET_2_0
  373. [MonoTODO]
  374. protected override void ApplyParameterInfo (IDbDataParameter dbParameter, DataRow row)
  375. {
  376. throw new NotImplementedException ();
  377. }
  378. [MonoTODO]
  379. protected override string GetParameterName (int position)
  380. {
  381. throw new NotImplementedException ();
  382. }
  383. [MonoTODO]
  384. protected override string GetParameterPlaceholder (int position)
  385. {
  386. throw new NotImplementedException ();
  387. }
  388. [MonoTODO]
  389. protected override DbProviderFactory ProviderFactory
  390. {
  391. get {throw new NotImplementedException ();}
  392. }
  393. [MonoTODO]
  394. protected override void SetRowUpdatingHandler (DbDataAdapter adapter)
  395. {
  396. throw new NotImplementedException ();
  397. }
  398. #endif // NET_2_0
  399. #if NET_2_0
  400. [MonoTODO]
  401. public override
  402. #else
  403. private
  404. #endif
  405. string QuoteIdentifier (string unquotedIdentifier)
  406. {
  407. #if NET_2_0
  408. throw new NotImplementedException ();
  409. #else
  410. if (unquotedIdentifier == null || unquotedIdentifier == String.Empty)
  411. return unquotedIdentifier;
  412. return String.Format ("{0}{1}{2}", QuotePrefix,
  413. unquotedIdentifier, QuoteSuffix);
  414. #endif
  415. }
  416. #if NET_2_0
  417. [MonoTODO]
  418. public override
  419. #else
  420. private
  421. #endif
  422. string UnquoteIdentifier (string quotedIdentifier)
  423. {
  424. #if NET_2_0
  425. throw new NotImplementedException ();
  426. #else
  427. if (quotedIdentifier == null || quotedIdentifier == String.Empty)
  428. return quotedIdentifier;
  429. StringBuilder sb = new StringBuilder (quotedIdentifier.Length);
  430. sb.Append (quotedIdentifier);
  431. if (quotedIdentifier.StartsWith (QuotePrefix))
  432. sb.Remove (0,QuotePrefix.Length);
  433. if (quotedIdentifier.EndsWith (QuoteSuffix))
  434. sb.Remove (sb.Length - QuoteSuffix.Length, QuoteSuffix.Length );
  435. return sb.ToString ();
  436. #endif
  437. }
  438. private void OnRowUpdating (object sender, OdbcRowUpdatingEventArgs args)
  439. {
  440. if (args.Command != null)
  441. return;
  442. try {
  443. switch (args.StatementType) {
  444. case StatementType.Insert:
  445. args.Command = GetInsertCommand ();
  446. break;
  447. case StatementType.Update:
  448. args.Command = GetUpdateCommand ();
  449. break;
  450. case StatementType.Delete:
  451. args.Command = GetDeleteCommand ();
  452. break;
  453. }
  454. } catch (Exception e) {
  455. args.Errors = e;
  456. args.Status = UpdateStatus.ErrorsOccurred;
  457. }
  458. }
  459. #endregion // Methods
  460. }
  461. }