(DISTFILES): Comment out a few missing files.
[mono-project.git] / mcs / class / System.Data / System.Data.SqlClient / SqlCommandBuilder.cs
blob9de0682e991a7c4f907e24034f7a5cc182649b80
1 //
2 // System.Data.SqlClient.SqlCommandBuilder.cs
3 //
4 // Author:
5 // Tim Coleman (tim@timcoleman.com)
6 //
7 // Copyright (C) Tim Coleman, 2002
8 //
11 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
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.
33 using System;
34 using System.Collections;
35 using System.ComponentModel;
36 using System.Data;
37 using System.Data.Common;
38 using System.Text;
40 namespace System.Data.SqlClient {
41 public sealed class SqlCommandBuilder : Component
43 #region Fields
45 bool disposed = false;
47 DataTable dbSchemaTable;
48 SqlDataAdapter adapter;
49 string quotePrefix;
50 string quoteSuffix;
51 string[] columnNames;
52 string tableName;
54 SqlCommand deleteCommand;
55 SqlCommand insertCommand;
56 SqlCommand updateCommand;
58 // Used to construct WHERE clauses
59 static readonly string clause1 = "({0} IS NULL AND {1} IS NULL)";
60 static readonly string clause2 = "({0} = {1})";
62 #endregion // Fields
64 #region Constructors
66 public SqlCommandBuilder ()
68 dbSchemaTable = null;
69 adapter = null;
70 quoteSuffix = String.Empty;
71 quotePrefix = String.Empty;
74 public SqlCommandBuilder (SqlDataAdapter adapter)
75 : this ()
77 DataAdapter = adapter;
80 #endregion // Constructors
82 #region Properties
84 [DataSysDescription ("The DataAdapter for which to automatically generate SqlCommands")]
85 [DefaultValue (null)]
86 public SqlDataAdapter DataAdapter {
87 get { return adapter; }
88 set {
89 adapter = value;
90 if (adapter != null)
91 adapter.RowUpdating += new SqlRowUpdatingEventHandler (RowUpdatingHandler);
95 private string QuotedTableName {
96 get { return GetQuotedString (tableName); }
99 [Browsable (false)]
100 [DataSysDescription ("The character used in a text command as the opening quote for quoting identifiers that contain special characters.")]
101 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
102 public string QuotePrefix {
103 get { return quotePrefix; }
104 set {
105 if (dbSchemaTable != null)
106 throw new InvalidOperationException ("The QuotePrefix and QuoteSuffix properties cannot be changed once an Insert, Update, or Delete command has been generated.");
107 quotePrefix = value;
111 [Browsable (false)]
112 [DataSysDescription ("The character used in a text command as the closing quote for quoting identifiers that contain special characters.")]
113 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
114 public string QuoteSuffix {
115 get { return quoteSuffix; }
116 set {
117 if (dbSchemaTable != null)
118 throw new InvalidOperationException ("The QuotePrefix and QuoteSuffix properties cannot be changed once an Insert, Update, or Delete command has been generated.");
119 quoteSuffix = value;
123 private SqlCommand SourceCommand {
124 get {
125 if (adapter != null)
126 return adapter.SelectCommand;
127 return null;
131 #endregion // Properties
133 #region Methods
135 private void BuildCache (bool closeConnection)
137 SqlCommand sourceCommand = SourceCommand;
138 if (sourceCommand == null)
139 throw new InvalidOperationException ("The DataAdapter.SelectCommand property needs to be initialized.");
140 SqlConnection connection = sourceCommand.Connection;
141 if (connection == null)
142 throw new InvalidOperationException ("The DataAdapter.SelectCommand.Connection property needs to be initialized.");
144 if (dbSchemaTable == null) {
145 if (connection.State == ConnectionState.Open)
146 closeConnection = false;
147 else
148 connection.Open ();
150 SqlDataReader reader = sourceCommand.ExecuteReader (CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo);
151 dbSchemaTable = reader.GetSchemaTable ();
152 reader.Close ();
153 if (closeConnection)
154 connection.Close ();
155 BuildInformation (dbSchemaTable);
159 private void BuildInformation (DataTable schemaTable)
161 tableName = String.Empty;
162 foreach (DataRow schemaRow in schemaTable.Rows) {
163 if (tableName == String.Empty)
164 tableName = schemaRow.IsNull ("BaseTableName") ? null : (string) schemaRow ["BaseTableName"];
165 else if (schemaRow.IsNull ("BaseTableName")) {
166 if (tableName != null)
167 throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
168 } else if (tableName != (string) schemaRow["BaseTableName"])
169 throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
171 dbSchemaTable = schemaTable;
174 private SqlCommand CreateDeleteCommand (DataRow row, DataTableMapping tableMapping)
176 // If no table was found, then we can't do an delete
177 if (QuotedTableName == String.Empty)
178 return null;
181 CreateNewCommand (ref deleteCommand);
183 string command = String.Format ("DELETE FROM {0} ", QuotedTableName);
184 StringBuilder columns = new StringBuilder ();
185 StringBuilder whereClause = new StringBuilder ();
186 string dsColumnName = String.Empty;
187 bool keyFound = false;
188 int parmIndex = 1;
190 foreach (DataRow schemaRow in dbSchemaTable.Rows) {
191 if (!IncludedInWhereClause (schemaRow))
192 continue;
194 if (whereClause.Length > 0)
195 whereClause.Append (" AND ");
197 bool isKey = (bool) schemaRow ["IsKey"];
198 SqlParameter parameter = null;
200 if (!isKey) {
201 parameter = deleteCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
203 dsColumnName = tableMapping.ColumnMappings [parameter.SourceColumn].DataSetColumn;
204 if (row != null)
205 parameter.Value = row [dsColumnName, DataRowVersion.Current];
206 whereClause.Append ("(");
207 whereClause.Append (String.Format (clause1, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
208 whereClause.Append (" OR ");
210 else
211 keyFound = true;
213 parameter = deleteCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
215 dsColumnName = tableMapping.ColumnMappings [parameter.SourceColumn].DataSetColumn;
216 if (row != null)
217 parameter.Value = row [dsColumnName, DataRowVersion.Current];
219 whereClause.Append (String.Format (clause2, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
221 if (!isKey)
222 whereClause.Append (")");
224 if (!keyFound)
225 throw new InvalidOperationException ("Dynamic SQL generation for the DeleteCommand is not supported against a SelectCommand that does not return any key column information.");
227 // We're all done, so bring it on home
228 string sql = String.Format ("{0} WHERE ( {1} )", command, whereClause.ToString ());
229 deleteCommand.CommandText = sql;
230 return deleteCommand;
233 private SqlCommand CreateInsertCommand (DataRow row, DataTableMapping tableMapping)
235 if (QuotedTableName == String.Empty)
236 return null;
238 CreateNewCommand (ref insertCommand);
240 string command = String.Format ("INSERT INTO {0}", QuotedTableName);
241 string sql;
242 StringBuilder columns = new StringBuilder ();
243 StringBuilder values = new StringBuilder ();
244 string dsColumnName = String.Empty;
246 int parmIndex = 1;
247 foreach (DataRow schemaRow in dbSchemaTable.Rows) {
248 if (!IncludedInInsert (schemaRow))
249 continue;
251 if (parmIndex > 1) {
252 columns.Append (" , ");
253 values.Append (" , ");
256 SqlParameter parameter = insertCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
258 dsColumnName = tableMapping.ColumnMappings [parameter.SourceColumn].DataSetColumn;
259 if (row != null)
260 parameter.Value = row [dsColumnName];
262 columns.Append (GetQuotedString (parameter.SourceColumn));
263 values.Append (parameter.ParameterName);
266 sql = String.Format ("{0}( {1} ) VALUES ( {2} )", command, columns.ToString (), values.ToString ());
267 insertCommand.CommandText = sql;
268 return insertCommand;
271 private void CreateNewCommand (ref SqlCommand command)
273 SqlCommand sourceCommand = SourceCommand;
274 if (command == null) {
275 command = sourceCommand.Connection.CreateCommand ();
276 command.CommandTimeout = sourceCommand.CommandTimeout;
277 command.Transaction = sourceCommand.Transaction;
279 command.CommandType = CommandType.Text;
280 command.UpdatedRowSource = UpdateRowSource.None;
283 private SqlCommand CreateUpdateCommand (DataRow row, DataTableMapping tableMapping)
285 // If no table was found, then we can't do an update
286 if (QuotedTableName == String.Empty)
287 return null;
289 CreateNewCommand (ref updateCommand);
291 string command = String.Format ("UPDATE {0} SET ", QuotedTableName);
292 StringBuilder columns = new StringBuilder ();
293 StringBuilder whereClause = new StringBuilder ();
294 int parmIndex = 1;
295 string dsColumnName = String.Empty;
296 bool keyFound = false;
298 // First, create the X=Y list for UPDATE
299 foreach (DataRow schemaRow in dbSchemaTable.Rows) {
300 if (columns.Length > 0)
301 columns.Append (" , ");
303 SqlParameter parameter = updateCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
305 dsColumnName = tableMapping.ColumnMappings [parameter.SourceColumn].DataSetColumn;
306 if (row != null)
307 parameter.Value = row [dsColumnName, DataRowVersion.Proposed];
309 columns.Append (String.Format ("{0} = {1}", GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
312 // Now, create the WHERE clause. This may be optimizable, but it would be ugly to incorporate
313 // into the loop above. "Premature optimization is the root of all evil." -- Knuth
314 foreach (DataRow schemaRow in dbSchemaTable.Rows) {
315 if (!IncludedInWhereClause (schemaRow))
316 continue;
318 if (whereClause.Length > 0)
319 whereClause.Append (" AND ");
321 bool isKey = (bool) schemaRow ["IsKey"];
322 SqlParameter parameter = null;
325 if (!isKey) {
326 parameter = updateCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
328 dsColumnName = tableMapping.ColumnMappings [parameter.SourceColumn].DataSetColumn;
329 if (row != null)
330 parameter.Value = row [dsColumnName];
332 whereClause.Append ("(");
333 whereClause.Append (String.Format (clause1, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
334 whereClause.Append (" OR ");
336 else
337 keyFound = true;
339 parameter = updateCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
341 dsColumnName = tableMapping.ColumnMappings [parameter.SourceColumn].DataSetColumn;
342 if (row != null)
343 parameter.Value = row [dsColumnName];
345 whereClause.Append (String.Format (clause2, GetQuotedString (parameter.SourceColumn), parameter.ParameterName));
347 if (!isKey)
348 whereClause.Append (")");
350 if (!keyFound)
351 throw new InvalidOperationException ("Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not return any key column information.");
353 // We're all done, so bring it on home
354 string sql = String.Format ("{0}{1} WHERE ( {2} )", command, columns.ToString (), whereClause.ToString ());
355 updateCommand.CommandText = sql;
356 return updateCommand;
359 private SqlParameter CreateParameter (int parmIndex, DataRow schemaRow)
361 string name = String.Format ("@p{0}", parmIndex);
362 string sourceColumn = (string) schemaRow ["BaseColumnName"];
363 SqlDbType sqlDbType = (SqlDbType) schemaRow ["ProviderType"];
364 int size = (int) schemaRow ["ColumnSize"];
366 return new SqlParameter (name, sqlDbType, size, sourceColumn);
369 public static void DeriveParameters (SqlCommand command)
371 command.DeriveParameters ();
374 protected override void Dispose (bool disposing)
376 if (!disposed) {
377 if (disposing) {
378 if (insertCommand != null)
379 insertCommand.Dispose ();
380 if (deleteCommand != null)
381 deleteCommand.Dispose ();
382 if (updateCommand != null)
383 updateCommand.Dispose ();
384 if (dbSchemaTable != null)
385 dbSchemaTable.Dispose ();
387 disposed = true;
391 public SqlCommand GetDeleteCommand ()
393 BuildCache (true);
394 return CreateDeleteCommand (null, null);
397 public SqlCommand GetInsertCommand ()
399 BuildCache (true);
400 return CreateInsertCommand (null, null);
403 private string GetQuotedString (string value)
405 if (value == String.Empty || value == null)
406 return value;
407 if (quotePrefix == String.Empty && quoteSuffix == String.Empty)
408 return value;
409 return String.Format ("{0}{1}{2}", quotePrefix, value, quoteSuffix);
412 public SqlCommand GetUpdateCommand ()
414 BuildCache (true);
415 return CreateUpdateCommand (null, null);
418 private bool IncludedInInsert (DataRow schemaRow)
420 // If the parameter has one of these properties, then we don't include it in the insert:
421 // AutoIncrement, Hidden, Expression, RowVersion, ReadOnly
423 if (!schemaRow.IsNull ("IsAutoIncrement") && (bool) schemaRow ["IsAutoIncrement"])
424 return false;
425 if (!schemaRow.IsNull ("IsHidden") && (bool) schemaRow ["IsHidden"])
426 return false;
427 if (!schemaRow.IsNull ("IsExpression") && (bool) schemaRow ["IsExpression"])
428 return false;
429 if (!schemaRow.IsNull ("IsRowVersion") && (bool) schemaRow ["IsRowVersion"])
430 return false;
431 if (!schemaRow.IsNull ("IsReadOnly") && (bool) schemaRow ["IsReadOnly"])
432 return false;
433 return true;
436 private bool IncludedInUpdate (DataRow schemaRow)
438 // If the parameter has one of these properties, then we don't include it in the insert:
439 // AutoIncrement, Hidden, RowVersion
441 if ((bool) schemaRow ["IsAutoIncrement"])
442 return false;
443 if ((bool) schemaRow ["IsHidden"])
444 return false;
445 if ((bool) schemaRow ["IsRowVersion"])
446 return false;
447 return true;
450 private bool IncludedInWhereClause (DataRow schemaRow)
452 if ((bool) schemaRow ["IsLong"])
453 return false;
454 return true;
457 [MonoTODO ("Figure out what else needs to be cleaned up when we refresh.")]
458 public void RefreshSchema ()
460 tableName = String.Empty;
461 dbSchemaTable = null;
464 #endregion // Methods
466 #region Event Handlers
468 private void RowUpdatingHandler (object sender, SqlRowUpdatingEventArgs e)
470 if (e.Status != UpdateStatus.Continue)
471 return;
473 switch (e.StatementType) {
474 case StatementType.Delete:
475 deleteCommand = e.Command;
476 break;
477 case StatementType.Insert:
478 insertCommand = e.Command;
479 break;
480 case StatementType.Update:
481 updateCommand = e.Command;
482 break;
483 default:
484 return;
487 try {
488 BuildCache (false);
490 switch (e.StatementType) {
491 case StatementType.Delete:
492 e.Command = CreateDeleteCommand (e.Row, e.TableMapping);
493 e.Status = UpdateStatus.Continue;
494 break;
495 case StatementType.Insert:
496 e.Command = CreateInsertCommand (e.Row, e.TableMapping);
497 e.Status = UpdateStatus.Continue;
498 break;
499 case StatementType.Update:
500 e.Command = CreateUpdateCommand (e.Row, e.TableMapping);
501 e.Status = UpdateStatus.Continue;
502 break;
505 if (e.Command != null && e.Row != null) {
506 e.Row.AcceptChanges ();
507 e.Status = UpdateStatus.SkipCurrentRow;
510 catch (Exception exception) {
511 e.Errors = exception;
512 e.Status = UpdateStatus.ErrorsOccurred;
516 #endregion // Event Handlers