2 // System.Data.OracleClient.OracleCommandBuilder.cs
4 // based on the SqlCommandBuilder in mcs/class/System.Data/System.Data.SqlClient
7 // Atsushi Enomoto <atsushi@ximian.com>
8 // Tim Coleman (tim@timcoleman.com)
9 // Daniel Morgan <danielmorgan@verizon.net>
11 // Copyright (C) Tim Coleman, 2002
12 // Copyright (C) Daniel Morgan, 2005
13 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 using System.Collections;
37 using System.ComponentModel;
39 using System.Data.Common;
42 namespace System.Data.OracleClient {
43 public sealed class OracleCommandBuilder : DbCommandBuilder
47 bool disposed = false;
49 DataTable dbSchemaTable;
50 OracleDataAdapter adapter;
53 //string[] columnNames;
56 OracleCommand deleteCommand;
57 OracleCommand insertCommand;
58 OracleCommand updateCommand;
60 // Used to construct WHERE clauses
61 static readonly string clause1 = "({0} IS NULL AND {1} IS NULL)";
62 static readonly string clause2 = "({0} = {1})";
68 public OracleCommandBuilder () {
71 quoteSuffix = String.Empty;
72 quotePrefix = String.Empty;
75 public OracleCommandBuilder (OracleDataAdapter adapter)
77 DataAdapter = adapter;
80 #endregion // Constructors
84 //[DataSysDescription ("The DataAdapter for which to automatically generate OracleCommands")]
88 OracleDataAdapter DataAdapter {
89 get { return adapter; }
92 adapter.RowUpdating -= new OracleRowUpdatingEventHandler (RowUpdatingHandler);
97 adapter.RowUpdating += new OracleRowUpdatingEventHandler (RowUpdatingHandler);
101 private string QuotedTableName {
102 get { return GetQuotedString (tableName); }
106 //[DataSysDescription ("The character used in a text command as the opening quote for quoting identifiers that contain special characters.")]
107 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
111 get { return quotePrefix; }
113 if (dbSchemaTable != null)
114 throw new InvalidOperationException ("The QuotePrefix and QuoteSuffix properties cannot be changed once an Insert, Update, or Delete command has been generated.");
120 //[DataSysDescription ("The character used in a text command as the closing quote for quoting identifiers that contain special characters.")]
121 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
125 get { return quoteSuffix; }
127 if (dbSchemaTable != null)
128 throw new InvalidOperationException ("The QuotePrefix and QuoteSuffix properties cannot be changed once an Insert, Update, or Delete command has been generated.");
133 private OracleCommand SourceCommand {
136 return adapter.SelectCommand;
141 #endregion // Properties
145 private void BuildCache (bool closeConnection)
147 OracleCommand sourceCommand = SourceCommand;
148 if (sourceCommand == null)
149 throw new InvalidOperationException ("The DataAdapter.SelectCommand property needs to be initialized.");
150 OracleConnection connection = sourceCommand.Connection;
151 if (connection == null)
152 throw new InvalidOperationException ("The DataAdapter.SelectCommand.Connection property needs to be initialized.");
154 if (dbSchemaTable == null) {
155 if (connection.State == ConnectionState.Open)
156 closeConnection = false;
160 OracleDataReader reader = sourceCommand.ExecuteReader (CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo);
161 dbSchemaTable = reader.GetSchemaTable ();
165 BuildInformation (dbSchemaTable);
169 private void BuildInformation (DataTable schemaTable)
171 tableName = String.Empty;
172 foreach (DataRow schemaRow in schemaTable.Rows) {
173 if (tableName == String.Empty)
174 tableName = schemaRow.IsNull ("BaseTableName") ? null : (string) schemaRow ["BaseTableName"];
175 else if (schemaRow.IsNull ("BaseTableName")) {
176 if (tableName != null)
177 throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
178 } else if (tableName != (string) schemaRow["BaseTableName"])
179 throw new InvalidOperationException ("Dynamic SQL generation is not supported against multiple base tables.");
181 dbSchemaTable = schemaTable;
184 private OracleCommand CreateDeleteCommand (DataRow row, DataTableMapping tableMapping)
186 // If no table was found, then we can't do an delete
187 if (QuotedTableName == String.Empty)
191 CreateNewCommand (ref deleteCommand);
193 string command = String.Format ("DELETE FROM {0} ", QuotedTableName);
194 StringBuilder whereClause = new StringBuilder ();
195 string dsColumnName = String.Empty;
196 bool keyFound = false;
199 foreach (DataRow schemaRow in dbSchemaTable.Rows) {
200 if (!IncludedInWhereClause (schemaRow))
203 if (whereClause.Length > 0)
204 whereClause.Append (" AND ");
206 bool isKey = (bool) schemaRow ["IsKey"];
207 OracleParameter parameter = null;
210 parameter = deleteCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
211 parameter.SourceVersion = DataRowVersion.Original;
213 dsColumnName = parameter.SourceColumn;
214 if (tableMapping != null
215 && tableMapping.ColumnMappings.Contains (parameter.SourceColumn))
216 dsColumnName = tableMapping.ColumnMappings [parameter.SourceColumn].DataSetColumn;
219 parameter.Value = row [dsColumnName, DataRowVersion.Original];
220 whereClause.Append ("(");
221 whereClause.Append (String.Format (clause1, GetQuotedString (parameter.SourceColumn), ":" + parameter.ParameterName));
222 whereClause.Append (" OR ");
227 parameter = deleteCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
228 parameter.SourceVersion = DataRowVersion.Original;
230 dsColumnName = parameter.SourceColumn;
231 if (tableMapping != null
232 && tableMapping.ColumnMappings.Contains (parameter.SourceColumn))
233 dsColumnName = tableMapping.ColumnMappings [parameter.SourceColumn].DataSetColumn;
236 parameter.Value = row [dsColumnName, DataRowVersion.Original];
238 whereClause.Append (String.Format (clause2, GetQuotedString (parameter.SourceColumn), ":" + parameter.ParameterName));
241 whereClause.Append (")");
244 throw new InvalidOperationException ("Dynamic SQL generation for the DeleteCommand is not supported against a SelectCommand that does not return any key column information.");
246 // We're all done, so bring it on home
247 string sql = String.Format ("{0} WHERE ( {1} )", command, whereClause.ToString ());
248 deleteCommand.CommandText = sql;
249 return deleteCommand;
252 private OracleCommand CreateInsertCommand (DataRow row, DataTableMapping tableMapping)
254 if (QuotedTableName == String.Empty)
257 CreateNewCommand (ref insertCommand);
259 string command = String.Format ("INSERT INTO {0}", QuotedTableName);
261 StringBuilder columns = new StringBuilder ();
262 StringBuilder values = new StringBuilder ();
263 string dsColumnName = String.Empty;
266 foreach (DataRow schemaRow in dbSchemaTable.Rows) {
267 if (!IncludedInInsert (schemaRow))
271 columns.Append (" , ");
272 values.Append (" , ");
275 OracleParameter parameter = insertCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
276 parameter.SourceVersion = DataRowVersion.Current;
278 dsColumnName = parameter.SourceColumn;
279 if (tableMapping != null
280 && tableMapping.ColumnMappings.Contains (parameter.SourceColumn))
281 dsColumnName = tableMapping.ColumnMappings [parameter.SourceColumn].DataSetColumn;
284 parameter.Value = row [dsColumnName, DataRowVersion.Current];
286 columns.Append (GetQuotedString (parameter.SourceColumn));
287 values.Append (":" + parameter.ParameterName);
290 sql = String.Format ("{0}( {1} ) VALUES ( {2} )", command, columns.ToString (), values.ToString ());
291 insertCommand.CommandText = sql;
292 return insertCommand;
295 private void CreateNewCommand (ref OracleCommand command) {
296 OracleCommand sourceCommand = SourceCommand;
297 if (command == null) {
298 command = sourceCommand.Connection.CreateCommand ();
299 command.Transaction = sourceCommand.Transaction;
301 command.CommandType = CommandType.Text;
302 command.UpdatedRowSource = UpdateRowSource.None;
305 private OracleCommand CreateUpdateCommand (DataRow row, DataTableMapping tableMapping)
307 // If no table was found, then we can't do an update
308 if (QuotedTableName == String.Empty)
311 CreateNewCommand (ref updateCommand);
313 string command = String.Format ("UPDATE {0} SET ", QuotedTableName);
314 StringBuilder columns = new StringBuilder ();
315 StringBuilder whereClause = new StringBuilder ();
317 string dsColumnName = String.Empty;
318 bool keyFound = false;
320 // First, create the X=Y list for UPDATE
321 foreach (DataRow schemaRow in dbSchemaTable.Rows) {
322 if (columns.Length > 0)
323 columns.Append (" , ");
325 OracleParameter parameter = updateCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
326 parameter.SourceVersion = DataRowVersion.Current;
328 dsColumnName = parameter.SourceColumn;
329 if (tableMapping != null
330 && tableMapping.ColumnMappings.Contains (parameter.SourceColumn))
331 dsColumnName = tableMapping.ColumnMappings [parameter.SourceColumn].DataSetColumn;
334 parameter.Value = row [dsColumnName, DataRowVersion.Current];
336 columns.Append (String.Format ("{0} = {1}", GetQuotedString (parameter.SourceColumn), ":" + parameter.ParameterName));
339 // Now, create the WHERE clause. This may be optimizable, but it would be ugly to incorporate
340 // into the loop above. "Premature optimization is the root of all evil." -- Knuth
341 foreach (DataRow schemaRow in dbSchemaTable.Rows) {
342 if (!IncludedInWhereClause (schemaRow))
345 if (whereClause.Length > 0)
346 whereClause.Append (" AND ");
348 bool isKey = (bool) schemaRow ["IsKey"];
349 OracleParameter parameter = null;
352 parameter = updateCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
353 parameter.SourceVersion = DataRowVersion.Original;
355 dsColumnName = parameter.SourceColumn;
356 if (tableMapping != null
357 && tableMapping.ColumnMappings.Contains (parameter.SourceColumn))
358 dsColumnName = tableMapping.ColumnMappings [parameter.SourceColumn].DataSetColumn;
361 parameter.Value = row [dsColumnName, DataRowVersion.Original];
363 whereClause.Append ("(");
364 whereClause.Append (String.Format (clause1, GetQuotedString (parameter.SourceColumn), ":" + parameter.ParameterName));
365 whereClause.Append (" OR ");
370 parameter = updateCommand.Parameters.Add (CreateParameter (parmIndex++, schemaRow));
371 parameter.SourceVersion = DataRowVersion.Original;
373 dsColumnName = parameter.SourceColumn;
374 if (tableMapping != null
375 && tableMapping.ColumnMappings.Contains (parameter.SourceColumn))
376 dsColumnName = tableMapping.ColumnMappings [parameter.SourceColumn].DataSetColumn;
379 parameter.Value = row [dsColumnName, DataRowVersion.Original];
381 whereClause.Append (String.Format (clause2, GetQuotedString (parameter.SourceColumn), ":" + parameter.ParameterName));
384 whereClause.Append (")");
387 throw new InvalidOperationException ("Dynamic SQL generation for the UpdateCommand is not supported against a SelectCommand that does not return any key column information.");
389 // We're all done, so bring it on home
390 string sql = String.Format ("{0}{1} WHERE ( {2} )", command, columns.ToString (), whereClause.ToString ());
391 updateCommand.CommandText = sql;
392 return updateCommand;
395 private OracleParameter CreateParameter (int parmIndex, DataRow schemaRow)
397 string name = String.Format ("p{0}", parmIndex);
399 string sourceColumn = (string) schemaRow ["BaseColumnName"];
400 int providerType = (int) schemaRow ["ProviderType"];
401 OracleType providerDbType = (OracleType) providerType;
402 int size = (int) schemaRow ["ColumnSize"];
404 return new OracleParameter (name, providerDbType, size, sourceColumn);
407 public static void DeriveParameters (OracleCommand command)
409 command.DeriveParameters ();
412 protected override void Dispose (bool disposing)
416 if (insertCommand != null)
417 insertCommand.Dispose ();
418 if (deleteCommand != null)
419 deleteCommand.Dispose ();
420 if (updateCommand != null)
421 updateCommand.Dispose ();
422 if (dbSchemaTable != null)
423 dbSchemaTable.Dispose ();
431 OracleCommand GetDeleteCommand ()
434 return CreateDeleteCommand (null, null);
439 OracleCommand GetInsertCommand ()
442 return CreateInsertCommand (null, null);
445 private string GetQuotedString (string value)
447 if (value == String.Empty || value == null)
449 if (quotePrefix == String.Empty && quoteSuffix == String.Empty)
451 return String.Format ("{0}{1}{2}", quotePrefix, value, quoteSuffix);
456 OracleCommand GetUpdateCommand ()
459 return CreateUpdateCommand (null, null);
462 private bool IncludedInInsert (DataRow schemaRow)
464 // If the parameter has one of these properties, then we don't include it in the insert:
465 if (!schemaRow.IsNull ("IsExpression") && (bool) schemaRow ["IsExpression"])
470 /*private bool IncludedInUpdate (DataRow schemaRow) {
471 // If the parameter has one of these properties, then we don't include it in the insert:
472 // AutoIncrement, Hidden, RowVersion
477 private bool IncludedInWhereClause (DataRow schemaRow) {
478 if ((bool) schemaRow ["IsLong"])
483 [MonoTODO ("Figure out what else needs to be cleaned up when we refresh.")]
486 void RefreshSchema ()
488 tableName = String.Empty;
489 dbSchemaTable = null;
493 protected override void ApplyParameterInfo (DbParameter dbParameter,
495 StatementType statementType,
498 throw new NotImplementedException ();
502 protected override string GetParameterName (int position)
504 throw new NotImplementedException ();
508 protected override string GetParameterName (string parameterName)
510 throw new NotImplementedException ();
514 protected override string GetParameterPlaceholder (int position)
516 throw new NotImplementedException ();
519 #endregion // Methods
521 #region Event Handlers
523 private void RowUpdatingHandler (object sender, OracleRowUpdatingEventArgs args)
525 if (args.Command != null)
528 switch (args.StatementType) {
529 case StatementType.Insert:
530 args.Command = GetInsertCommand ();
532 case StatementType.Update:
533 args.Command = GetUpdateCommand ();
535 case StatementType.Delete:
536 args.Command = GetDeleteCommand ();
539 } catch (Exception e) {
541 args.Status = UpdateStatus.ErrorsOccurred;
546 protected override void SetRowUpdatingHandler (DbDataAdapter adapter)
548 throw new NotImplementedException ();
551 #endregion // Event Handlers