SqlCommandBuilder.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. //
  2. // System.Data.Common.CommandBuilder.cs
  3. //
  4. // Author:
  5. // Tim Coleman ([email protected])
  6. //
  7. // Copyright (C) Tim Coleman, 2002
  8. //
  9. using System;
  10. using System.Collections;
  11. using System.ComponentModel;
  12. using System.Data;
  13. using System.Data.Common;
  14. using System.Text;
  15. namespace System.Data.SqlClient {
  16. public sealed class SqlCommandBuilder : Component
  17. {
  18. #region Fields
  19. DataTable dbSchemaTable;
  20. SqlDataAdapter adapter;
  21. string quotePrefix;
  22. string quoteSuffix;
  23. string[] columnNames;
  24. string tableName;
  25. SqlCommand deleteCommand;
  26. SqlCommand insertCommand;
  27. SqlCommand updateCommand;
  28. // Used to construct WHERE clauses
  29. static readonly string clause1 = "({0} IS NULL AND {1} IS NULL)";
  30. static readonly string clause2 = "({0} = {1})";
  31. #endregion // Fields
  32. #region Constructors
  33. public SqlCommandBuilder ()
  34. {
  35. dbSchemaTable = null;
  36. adapter = null;
  37. quoteSuffix = String.Empty;
  38. quotePrefix = String.Empty;
  39. }
  40. public SqlCommandBuilder (SqlDataAdapter adapter)
  41. : this ()
  42. {
  43. DataAdapter = adapter;
  44. }
  45. #endregion // Constructors
  46. #region Properties
  47. public SqlDataAdapter DataAdapter {
  48. get { return adapter; }
  49. set {
  50. adapter = value;
  51. if (adapter != null)
  52. adapter.RowUpdating += new SqlRowUpdatingEventHandler (RowUpdatingHandler);
  53. }
  54. }
  55. private string QuotedTableName {
  56. get { return GetQuotedString (tableName); }
  57. }
  58. public string QuotePrefix {
  59. get { return quotePrefix; }
  60. set {
  61. if (dbSchemaTable != null)
  62. throw new InvalidOperationException ("The QuotePrefix and QuoteSuffix properties cannot be changed once an Insert, Update, or Delete command has been generated.");
  63. quotePrefix = value;
  64. }
  65. }
  66. public string QuoteSuffix {
  67. get { return quoteSuffix; }
  68. set {
  69. if (dbSchemaTable != null)
  70. throw new InvalidOperationException ("The QuotePrefix and QuoteSuffix properties cannot be changed once an Insert, Update, or Delete command has been generated.");
  71. quoteSuffix = value;
  72. }
  73. }
  74. private SqlCommand SourceCommand {
  75. get {
  76. if (adapter != null)
  77. return adapter.SelectCommand;
  78. return null;
  79. }
  80. }
  81. #endregion // Properties
  82. #region Methods
  83. private void BuildCache (bool closeConnection)
  84. {
  85. SqlCommand sourceCommand = SourceCommand;
  86. if (sourceCommand == null)
  87. throw new InvalidOperationException ("The DataAdapter.SelectCommand property needs to be initialized.");
  88. SqlConnection connection = sourceCommand.Connection;
  89. if (connection == null)
  90. throw new InvalidOperationException ("The DataAdapter.SelectCommand.Connection property needs to be initialized.");
  91. if (dbSchemaTable == null) {
  92. if (connection.State == ConnectionState.Open)
  93. closeConnection = false;
  94. else
  95. connection.Open ();
  96. SqlDataReader reader = sourceCommand.ExecuteReader (CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo);
  97. dbSchemaTable = reader.GetSchemaTable ();
  98. if (closeConnection)
  99. connection.Close ();
  100. BuildInformation (dbSchemaTable);
  101. }
  102. }
  103. private void BuildInformation (DataTable schemaTable)
  104. {
  105. tableName = String.Empty;
  106. foreach (DataRow schemaRow in schemaTable.Rows) {
  107. if (tableName == String.Empty)
  108. tableName = (string) schemaRow ["BaseTableName"];
  109. if (tableName != (string) schemaRow["BaseTableName"])
  110. throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
  111. }
  112. dbSchemaTable = schemaTable;
  113. }
  114. private SqlCommand CreateDeleteCommand (DataRow row, DataTableMapping tableMapping)
  115. {
  116. // If no table was found, then we can't do an delete
  117. if (QuotedTableName == String.Empty)
  118. return null;
  119. CreateNewCommand (ref deleteCommand);
  120. string command = String.Format ("DELETE FROM {0} ", QuotedTableName);
  121. StringBuilder columns = new StringBuilder ();
  122. StringBuilder where = new StringBuilder ();
  123. int parmIndex = 1;
  124. foreach (DataRow schemaRow in dbSchemaTable.Rows) {
  125. if (!IncludedInWhereClause (schemaRow))
  126. continue;
  127. if (where.Length > 0)
  128. where.Append (" AND ");
  129. bool isKey = (bool) schemaRow ["IsKey"];
  130. SqlParameter parameter = null;
  131. if (!isKey) {
  132. parameter = deleteCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
  133. where.Append ("(");
  134. where.Append (String.Format (clause1, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
  135. where.Append (" OR ");
  136. }
  137. parameter = deleteCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
  138. if (row != null)
  139. parameter.Value = row [parameter.SourceColumn, DataRowVersion.Current];
  140. where.Append (String.Format (clause2, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
  141. if (!isKey)
  142. where.Append (")");
  143. }
  144. // We're all done, so bring it on home
  145. string sql = String.Format ("{0} WHERE ( {1} )", command, where.ToString ());
  146. deleteCommand.CommandText = sql;
  147. return deleteCommand;
  148. }
  149. private SqlCommand CreateInsertCommand (DataRow row, DataTableMapping tableMapping)
  150. {
  151. if (QuotedTableName == String.Empty)
  152. return null;
  153. CreateNewCommand (ref insertCommand);
  154. string command = String.Format ("INSERT INTO {0}", QuotedTableName);
  155. string sql;
  156. StringBuilder columns = new StringBuilder ();
  157. StringBuilder values = new StringBuilder ();
  158. int parmIndex = 1;
  159. foreach (DataRow schemaRow in dbSchemaTable.Rows) {
  160. if (!IncludedInInsert (schemaRow))
  161. continue;
  162. if (parmIndex > 1) {
  163. columns.Append (" , ");
  164. values.Append (" , ");
  165. }
  166. SqlParameter parameter = insertCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
  167. if (row != null)
  168. parameter.Value = row [parameter.SourceColumn, DataRowVersion.Proposed];
  169. columns.Append (GetQuotedString (parameter.SourceColumn));
  170. values.Append (parameter.ParameterName);
  171. }
  172. sql = String.Format ("{0}( {1} ) VALUES ( {2} )", command, columns.ToString (), values.ToString ());
  173. insertCommand.CommandText = sql;
  174. return insertCommand;
  175. }
  176. private void CreateNewCommand (ref SqlCommand command)
  177. {
  178. SqlCommand sourceCommand = SourceCommand;
  179. if (command == null) {
  180. command = sourceCommand.Connection.CreateCommand ();
  181. command.CommandTimeout = sourceCommand.CommandTimeout;
  182. command.Transaction = sourceCommand.Transaction;
  183. }
  184. command.CommandType = CommandType.Text;
  185. command.UpdatedRowSource = UpdateRowSource.None;
  186. }
  187. private SqlCommand CreateUpdateCommand (DataRow row, DataTableMapping tableMapping)
  188. {
  189. // If no table was found, then we can't do an update
  190. if (QuotedTableName == String.Empty)
  191. return null;
  192. CreateNewCommand (ref updateCommand);
  193. string command = String.Format ("UPDATE {0} SET ", QuotedTableName);
  194. StringBuilder columns = new StringBuilder ();
  195. StringBuilder where = new StringBuilder ();
  196. int parmIndex = 1;
  197. // First, create the X=Y list for UPDATE
  198. foreach (DataRow schemaRow in dbSchemaTable.Rows) {
  199. if (columns.Length > 0)
  200. columns.Append (" , ");
  201. SqlParameter parameter = updateCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
  202. if (row != null)
  203. parameter.Value = row [parameter.SourceColumn, DataRowVersion.Proposed];
  204. columns.Append (String.Format ("{0} = {1}", GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
  205. }
  206. // Now, create the WHERE clause. This may be optimizable, but it would be ugly to incorporate
  207. // into the loop above. "Premature optimization is the root of all evil." -- Knuth
  208. foreach (DataRow schemaRow in dbSchemaTable.Rows) {
  209. if (!IncludedInWhereClause (schemaRow))
  210. continue;
  211. if (where.Length > 0)
  212. where.Append (" AND ");
  213. bool isKey = (bool) schemaRow ["IsKey"];
  214. SqlParameter parameter = null;
  215. if (!isKey) {
  216. parameter = updateCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
  217. where.Append ("(");
  218. where.Append (String.Format (clause1, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
  219. where.Append (" OR ");
  220. }
  221. parameter = updateCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
  222. if (row != null)
  223. parameter.Value = row [parameter.SourceColumn, DataRowVersion.Current];
  224. where.Append (String.Format (clause2, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
  225. if (!isKey)
  226. where.Append (")");
  227. }
  228. // We're all done, so bring it on home
  229. string sql = String.Format ("{0}{1} WHERE ( {2} )", command, columns.ToString (), where.ToString ());
  230. updateCommand.CommandText = sql;
  231. return updateCommand;
  232. }
  233. private SqlParameter CreateParameter (int parmIndex, DataRow schemaRow)
  234. {
  235. string name = String.Format ("@p{0}", parmIndex);
  236. string sourceColumn = (string) schemaRow ["BaseColumnName"];
  237. SqlDbType sqlDbType = (SqlDbType) schemaRow ["ProviderType"];
  238. int size = (int) schemaRow ["ColumnSize"];
  239. return new SqlParameter (name, sqlDbType, size, sourceColumn);
  240. }
  241. public static void DeriveParameters (SqlCommand command)
  242. {
  243. command.DeriveParameters ();
  244. }
  245. [MonoTODO]
  246. protected override void Dispose (bool disposing)
  247. {
  248. }
  249. public SqlCommand GetDeleteCommand ()
  250. {
  251. BuildCache (true);
  252. return CreateDeleteCommand (null, null);
  253. }
  254. public SqlCommand GetInsertCommand ()
  255. {
  256. BuildCache (true);
  257. return CreateInsertCommand (null, null);
  258. }
  259. private string GetQuotedString (string value)
  260. {
  261. if (value == String.Empty || value == null)
  262. return value;
  263. if (quotePrefix == String.Empty && quoteSuffix == String.Empty)
  264. return value;
  265. return String.Format ("{0}{1}{2}", quotePrefix, value, quoteSuffix);
  266. }
  267. public SqlCommand GetUpdateCommand ()
  268. {
  269. BuildCache (true);
  270. return CreateUpdateCommand (null, null);
  271. }
  272. private bool IncludedInInsert (DataRow schemaRow)
  273. {
  274. // If the parameter has one of these properties, then we don't include it in the insert:
  275. // AutoIncrement, Hidden, Expression, RowVersion, ReadOnly
  276. if ((bool) schemaRow ["IsAutoIncrement"])
  277. return false;
  278. if ((bool) schemaRow ["IsHidden"])
  279. return false;
  280. if ((bool) schemaRow ["IsExpression"])
  281. return false;
  282. if ((bool) schemaRow ["IsRowVersion"])
  283. return false;
  284. if ((bool) schemaRow ["IsReadOnly"])
  285. return false;
  286. return true;
  287. }
  288. private bool IncludedInUpdate (DataRow schemaRow)
  289. {
  290. // If the parameter has one of these properties, then we don't include it in the insert:
  291. // AutoIncrement, Hidden, RowVersion
  292. if ((bool) schemaRow ["IsAutoIncrement"])
  293. return false;
  294. if ((bool) schemaRow ["IsHidden"])
  295. return false;
  296. if ((bool) schemaRow ["IsRowVersion"])
  297. return false;
  298. return true;
  299. }
  300. private bool IncludedInWhereClause (DataRow schemaRow)
  301. {
  302. if ((bool) schemaRow ["IsLong"])
  303. return false;
  304. return true;
  305. }
  306. [MonoTODO]
  307. public void RefreshSchema ()
  308. {
  309. tableName = String.Empty;
  310. dbSchemaTable = null;
  311. }
  312. #endregion // Methods
  313. #region Event Handlers
  314. private void RowUpdatingHandler (object sender, SqlRowUpdatingEventArgs e)
  315. {
  316. if (e.Status != UpdateStatus.Continue)
  317. return;
  318. switch (e.StatementType) {
  319. case StatementType.Delete:
  320. deleteCommand = e.Command;
  321. break;
  322. case StatementType.Insert:
  323. insertCommand = e.Command;
  324. break;
  325. case StatementType.Update:
  326. updateCommand = e.Command;
  327. break;
  328. default:
  329. return;
  330. }
  331. BuildCache (false);
  332. switch (e.StatementType) {
  333. case StatementType.Delete:
  334. e.Command = CreateDeleteCommand (e.Row, e.TableMapping);
  335. e.Status = UpdateStatus.Continue;
  336. break;
  337. case StatementType.Insert:
  338. e.Command = CreateInsertCommand (e.Row, e.TableMapping);
  339. e.Status = UpdateStatus.Continue;
  340. break;
  341. case StatementType.Update:
  342. e.Command = CreateUpdateCommand (e.Row, e.TableMapping);
  343. e.Status = UpdateStatus.Continue;
  344. break;
  345. }
  346. if (e.Command != null && e.Row != null) {
  347. e.Row.AcceptChanges ();
  348. e.Status = UpdateStatus.SkipCurrentRow;
  349. }
  350. }
  351. #endregion // Event Handlers
  352. }
  353. }