2 * Firebird ADO.NET Data provider for .NET and Mono
4 * The contents of this file are subject to the Initial
5 * Developer's Public License Version 1.0 (the "License");
6 * you may not use this file except in compliance with the
7 * License. You may obtain a copy of the License at
8 * http://www.firebirdsql.org/index.php?op=doc&id=idpl
10 * Software distributed under the License is distributed on
11 * an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
12 * express or implied. See the License for the specific
13 * language governing rights and limitations under the License.
15 * Copyright (c) 2002, 2005 Carlos Guzman Alvarez
16 * All Rights Reserved.
20 using System.Collections;
21 using System.ComponentModel;
23 using System.Data.Common;
26 namespace FirebirdSql.Data.Firebird
28 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/overview/*'/>
31 [ToolboxBitmap(typeof(FbDataAdapter), "Resources.FbDataAdapter.bmp")]
32 [DefaultEvent("RowUpdated")]
33 [DesignerAttribute(typeof(Design.FbDataAdapterDesigner), typeof(System.ComponentModel.Design.IDesigner))]
35 public sealed class FbDataAdapter : DbDataAdapter, IDbDataAdapter
39 private static readonly object EventRowUpdated = new object();
40 private static readonly object EventRowUpdating = new object();
46 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/event[@name="RowUpdated"]/*'/>
47 public event FbRowUpdatedEventHandler RowUpdated
49 add { base.Events.AddHandler(EventRowUpdated, value); }
50 remove { base.Events.RemoveHandler(EventRowUpdated, value); }
53 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/event[@name="RowUpdating"]/*'/>
54 public event FbRowUpdatingEventHandler RowUpdating
58 base.Events.AddHandler(EventRowUpdating, value);
63 base.Events.RemoveHandler(EventRowUpdating, value);
71 private FbCommand selectCommand;
72 private FbCommand insertCommand;
73 private FbCommand updateCommand;
74 private FbCommand deleteCommand;
76 private bool disposed;
82 IDbCommand IDbDataAdapter.SelectCommand
84 get { return this.selectCommand; }
85 set { this.selectCommand = (FbCommand)value; }
88 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/property[@name="SelectCommand"]/*'/>
90 [Category("Fill"), DefaultValue(null)]
92 public FbCommand SelectCommand
94 get { return this.selectCommand; }
95 set { this.selectCommand = value; }
98 IDbCommand IDbDataAdapter.InsertCommand
100 get { return this.insertCommand; }
101 set { this.insertCommand = (FbCommand)value; }
104 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/property[@name="InsertCommand"]/*'/>
106 [Category("Update"), DefaultValue(null)]
108 public FbCommand InsertCommand
110 get { return this.insertCommand; }
111 set { this.insertCommand = value; }
114 IDbCommand IDbDataAdapter.UpdateCommand
116 get { return this.updateCommand; }
117 set { this.updateCommand = (FbCommand)value; }
120 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/property[@name="UpdateCommand"]/*'/>
122 [Category("Update"), DefaultValue(null)]
124 public FbCommand UpdateCommand
126 get { return this.updateCommand; }
127 set { this.updateCommand = value; }
130 IDbCommand IDbDataAdapter.DeleteCommand
132 get { return this.deleteCommand; }
133 set { this.deleteCommand = (FbCommand)value; }
136 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/property[@name="DeleteCommand"]/*'/>
138 [Category("Update"), DefaultValue(null)]
140 public FbCommand DeleteCommand
142 get { return this.deleteCommand; }
143 set { this.deleteCommand = value; }
150 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/constructor[@name="ctor"]/*'/>
151 public FbDataAdapter() : base()
155 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/constructor[@name="ctor(FbCommand)"]/*'/>
156 public FbDataAdapter(FbCommand selectCommand) : base()
158 this.SelectCommand = selectCommand;
161 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/constructor[@name="ctor(System.String,FbConnection)"]/*'/>
162 public FbDataAdapter(string selectCommandText, FbConnection selectConnection)
165 this.SelectCommand = new FbCommand(selectCommandText, selectConnection);
168 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/constructor[@name="ctor(System.String,System.String)"]/*'/>
169 public FbDataAdapter(string selectCommandText, string selectConnectionString)
172 FbConnection connection = new FbConnection(selectConnectionString);
173 this.SelectCommand = new FbCommand(selectCommandText, connection);
178 #region IDisposable Methods
180 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/method[@name="Dispose(System.Boolean)"]/*'/>
181 protected override void Dispose(bool disposing)
189 // Release any managed resources
192 if (this.SelectCommand != null)
194 this.SelectCommand.Dispose();
196 if (this.InsertCommand != null)
198 this.InsertCommand.Dispose();
200 if (this.UpdateCommand != null)
202 this.UpdateCommand.Dispose();
206 // release any unmanaged resources
208 this.disposed = true;
212 base.Dispose(disposing);
220 #region Protected Methods
222 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/method[@name="CreateRowUpdatingEvent(System.Data.DataRow,System.Data.IDbCommand,System.Data.StatementType,System.Data.Common.DataTableMapping)"]/*'/>
223 protected override RowUpdatingEventArgs CreateRowUpdatingEvent(
224 DataRow dataRow, IDbCommand command, StatementType statementType, DataTableMapping tableMapping)
226 return new FbRowUpdatingEventArgs(dataRow, command, statementType, tableMapping);
229 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/method[@name="CreateRowUpdatedEvent(System.Data.DataRow,System.Data.IDbCommand,System.Data.StatementType,System.Data.Common.DataTableMapping)"]/*'/>
230 protected override RowUpdatedEventArgs CreateRowUpdatedEvent(
231 DataRow dataRow, IDbCommand command, StatementType statementType, DataTableMapping tableMapping)
233 return new FbRowUpdatedEventArgs(dataRow, command, statementType, tableMapping);
236 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/method[@name="OnRowUpdating(System.Data.Common.RowUpdatingEventArgs)"]/*'/>
237 protected override void OnRowUpdating(RowUpdatingEventArgs value)
239 FbRowUpdatingEventHandler handler = null;
241 handler = (FbRowUpdatingEventHandler)base.Events[EventRowUpdating];
243 if ((null != handler) &&
244 (value is FbRowUpdatingEventArgs) &&
247 handler(this, (FbRowUpdatingEventArgs)value);
251 /// <include file='Doc/en_EN/FbDataAdapter.xml' path='doc/class[@name="FbDataAdapter"]/method[@name="OnRowUpdated(System.Data.Common.RowUpdatedEventArgs)"]/*'/>
252 protected override void OnRowUpdated(RowUpdatedEventArgs value)
254 FbRowUpdatedEventHandler handler = null;
256 handler = (FbRowUpdatedEventHandler)base.Events[EventRowUpdated];
258 if ((handler != null) &&
259 (value is FbRowUpdatedEventArgs) &&
262 handler(this, (FbRowUpdatedEventArgs)value);
268 #region Update DataRow Collection
271 /// Review .NET Framework documentation.
273 protected override int Update(DataRow[] dataRows, DataTableMapping tableMapping)
276 IDbCommand command = null;
277 StatementType statementType = StatementType.Insert;
278 ArrayList connections = new ArrayList();
279 RowUpdatingEventArgs updatingArgs = null;
280 Exception updateException = null;
282 foreach (DataRow row in dataRows)
284 if (row.RowState == DataRowState.Detached ||
285 row.RowState == DataRowState.Unchanged)
290 switch (row.RowState)
292 case DataRowState.Added:
293 command = this.insertCommand;
294 statementType = StatementType.Insert;
297 case DataRowState.Modified:
298 command = this.updateCommand;
299 statementType = StatementType.Update;
302 case DataRowState.Deleted:
303 command = this.deleteCommand;
304 statementType = StatementType.Delete;
308 /* The order of execution can be reviewed in the .NET 1.1 documentation
310 * 1. The values in the DataRow are moved to the parameter values.
311 * 2. The OnRowUpdating event is raised.
312 * 3. The command executes.
313 * 4. If the command is set to FirstReturnedRecord, then the first returned result is placed in the DataRow.
314 * 5. If there are output parameters, they are placed in the DataRow.
315 * 6. The OnRowUpdated event is raised.
316 * 7 AcceptChanges is called.
321 /* 1. Update Parameter values (It's very similar to what we
322 * are doing in the FbCommandBuilder class).
324 * Only input parameters should be updated.
326 if (command != null && command.Parameters.Count > 0)
328 this.UpdateParameterValues(command, statementType, row, tableMapping);
331 // 2. Raise RowUpdating event
332 updatingArgs = this.CreateRowUpdatingEvent(row, command, statementType, tableMapping);
333 this.OnRowUpdating(updatingArgs);
335 if (updatingArgs.Status == UpdateStatus.SkipAllRemainingRows)
339 else if (updatingArgs.Status == UpdateStatus.ErrorsOccurred)
341 if (updatingArgs.Errors == null)
343 throw new InvalidOperationException("RowUpdatingEvent: Errors occurred; no additional is information available.");
345 throw updatingArgs.Errors;
347 else if (updatingArgs.Status == UpdateStatus.SkipCurrentRow)
350 else if (updatingArgs.Status == UpdateStatus.Continue)
352 if (command != updatingArgs.Command)
354 command = updatingArgs.Command;
358 /* Samples of exceptions thrown by DbDataAdapter class
360 * Update requires a valid InsertCommand when passed DataRow collection with new rows
361 * Update requires a valid UpdateCommand when passed DataRow collection with modified rows.
362 * Update requires a valid DeleteCommand when passed DataRow collection with deleted rows.
364 string message = this.CreateExceptionMessage(statementType);
365 throw new InvalidOperationException(message);
368 /* Validate that the command has a connection */
369 if (command.Connection == null)
371 throw new InvalidOperationException("Update requires a command with a valid connection.");
374 // 3. Execute the command
375 if (command.Connection.State == ConnectionState.Closed)
377 command.Connection.Open();
378 // Track command connection
379 connections.Add(command.Connection);
382 int rowsAffected = command.ExecuteNonQuery();
383 if (rowsAffected == 0)
385 throw new DBConcurrencyException("An attempt to execute an INSERT, UPDATE, or DELETE statement resulted in zero records affected.");
390 /* 4. If the command is set to FirstReturnedRecord, then the
391 * first returned result is placed in the DataRow.
393 * We have nothing to do in this case as there are no
394 * support for batch commands.
397 /* 5. Check if we have output parameters and they should
400 * Only output paraneters should be updated
402 if (command.UpdatedRowSource == UpdateRowSource.OutputParameters ||
403 command.UpdatedRowSource == UpdateRowSource.Both)
405 // Process output parameters
406 foreach (IDataParameter parameter in command.Parameters)
408 if ((parameter.Direction == ParameterDirection.Output ||
409 parameter.Direction == ParameterDirection.ReturnValue ||
410 parameter.Direction == ParameterDirection.InputOutput) &&
411 parameter.SourceColumn != null &&
412 parameter.SourceColumn.Length > 0)
414 DataColumn column = null;
416 DataColumnMapping columnMapping = tableMapping.GetColumnMappingBySchemaAction(
417 parameter.SourceColumn,
418 this.MissingMappingAction);
420 if (columnMapping != null)
422 column = columnMapping.GetDataColumnBySchemaAction(
425 this.MissingSchemaAction);
429 row[column] = parameter.Value;
439 row.RowError = ex.Message;
440 updateException = ex;
443 if (updatingArgs.Status == UpdateStatus.Continue)
445 // 6. Raise RowUpdated event
446 RowUpdatedEventArgs updatedArgs = this.CreateRowUpdatedEvent(row, command, statementType, tableMapping);
447 this.OnRowUpdated(updatedArgs);
449 if (updatedArgs.Status == UpdateStatus.SkipAllRemainingRows)
453 else if (updatedArgs.Status == UpdateStatus.ErrorsOccurred)
455 if (updatingArgs.Errors == null)
457 throw new InvalidOperationException("RowUpdatedEvent: Errors occurred; no additional information available.");
459 throw updatedArgs.Errors;
461 else if (updatedArgs.Status == UpdateStatus.SkipCurrentRow)
464 else if (updatingArgs.Status == UpdateStatus.Continue)
466 // If the update result is an exception throw it
467 if (!this.ContinueUpdateOnError && updateException != null)
469 this.CloseConnections(connections);
470 throw updateException;
473 // 7. Call AcceptChanges
479 // If the update result is an exception throw it
480 if (!this.ContinueUpdateOnError && updateException != null)
482 this.CloseConnections(connections);
483 throw updateException;
487 updateException = null;
490 this.CloseConnections(connections);
497 #region Private Methods
499 private string CreateExceptionMessage(StatementType statementType)
501 System.Text.StringBuilder sb = new System.Text.StringBuilder();
503 sb.Append("Update requires a valid ");
504 sb.Append(statementType.ToString());
505 sb.Append("Command when passed DataRow collection with ");
507 switch (statementType)
509 case StatementType.Insert:
513 case StatementType.Update:
514 sb.Append("modified");
517 case StatementType.Delete:
518 sb.Append("deleted");
524 return sb.ToString();
527 private void UpdateParameterValues(
529 StatementType statementType,
531 DataTableMapping tableMapping)
533 foreach (IDataParameter parameter in command.Parameters)
535 // Process only input parameters
536 if (parameter.Direction != ParameterDirection.Input &&
537 parameter.Direction != ParameterDirection.InputOutput)
542 DataColumn column = null;
544 /* Get the DataColumnMapping that matches the given
547 DataColumnMapping columnMapping = tableMapping.GetColumnMappingBySchemaAction(
548 parameter.SourceColumn,
549 this.MissingMappingAction);
551 if (columnMapping != null)
553 column = columnMapping.GetDataColumnBySchemaAction(
556 this.MissingSchemaAction);
560 DataRowVersion dataRowVersion = DataRowVersion.Default;
562 if (statementType == StatementType.Insert)
564 dataRowVersion = DataRowVersion.Current;
566 else if (statementType == StatementType.Update)
568 dataRowVersion = parameter.SourceVersion;
570 else if (statementType == StatementType.Delete)
572 dataRowVersion = DataRowVersion.Original;
575 parameter.Value = row[column, dataRowVersion];
581 private void CloseConnections(ArrayList connections)
583 foreach (IDbConnection c in connections)