1 //------------------------------------------------------------------------------
2 // <copyright file="DbDataAdapter.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
9 namespace System.Data.Common {
12 using System.ComponentModel;
13 using System.Collections;
14 using System.Collections.Generic;
16 using System.Data.ProviderBase;
17 using System.Diagnostics;
18 using System.Reflection;
19 using System.Threading;
21 public abstract class DbDataAdapter : DataAdapter, IDbDataAdapter, ICloneable { // V1.0.3300, MDAC 69629
22 public const string DefaultSourceTableName = "Table"; // V1.0.3300
24 internal static readonly object ParameterValueNonNullValue = 0;
25 internal static readonly object ParameterValueNullValue = 1;
27 private IDbCommand _deleteCommand, _insertCommand, _selectCommand, _updateCommand;
29 private CommandBehavior _fillCommandBehavior;
31 private struct BatchCommandInfo {
32 internal int CommandIdentifier; // whatever AddToBatch returns, so we can reference the command later in GetBatchedParameter
33 internal int ParameterCount; // number of parameters on the command, so we know how many to loop over when processing output parameters
34 internal DataRow Row; // the row that the command is intended to update
35 internal StatementType StatementType; // the statement type of the command, needed for accept changes
36 internal UpdateRowSource UpdatedRowSource; // the UpdatedRowSource value from the command, to know whether we need to look for output parameters or not
37 internal int? RecordsAffected;
38 internal Exception Errors;
41 protected DbDataAdapter() : base() { // V1.0.3300
44 protected DbDataAdapter(DbDataAdapter adapter) : base(adapter) { // V1.0.5000
48 private IDbDataAdapter _IDbDataAdapter {
50 return (IDbDataAdapter)this;
56 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
58 public DbCommand DeleteCommand { // V1.2.3300
60 return (DbCommand)(_IDbDataAdapter.DeleteCommand);
63 _IDbDataAdapter.DeleteCommand = value;
67 IDbCommand IDbDataAdapter.DeleteCommand { // V1.2.3300
69 return _deleteCommand;
72 _deleteCommand = value;
76 protected internal CommandBehavior FillCommandBehavior { // V1.2.3300, MDAC 87511
78 //Bid.Trace("<comm.DbDataAdapter.get_FillCommandBehavior|API> %d#\n", ObjectID);
79 return (_fillCommandBehavior | CommandBehavior.SequentialAccess);
82 // setting |= SchemaOnly; /* similar to FillSchema (which also uses KeyInfo) */
83 // setting |= KeyInfo; /* same as MissingSchemaAction.AddWithKey */
84 // setting |= SequentialAccess; /* required and always present */
85 // setting |= CloseConnection; /* close connection regardless of start condition */
86 _fillCommandBehavior = (value | CommandBehavior.SequentialAccess);
87 //Bid.Trace("<comm.DbDataAdapter.set_FillCommandBehavior|API> %d#, %d{ds.CommandBehavior}\n", (int)value);
93 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
95 public DbCommand InsertCommand { // V1.2.3300
97 return (DbCommand)(_IDbDataAdapter.InsertCommand);
100 _IDbDataAdapter.InsertCommand = value;
104 IDbCommand IDbDataAdapter.InsertCommand { // V1.2.3300
106 return _insertCommand;
109 _insertCommand = value;
115 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
117 public DbCommand SelectCommand { // V1.2.3300
119 return (DbCommand)(_IDbDataAdapter.SelectCommand);
122 _IDbDataAdapter.SelectCommand = value;
126 IDbCommand IDbDataAdapter.SelectCommand { // V1.2.3300
128 return _selectCommand;
131 _selectCommand = value;
137 ResCategoryAttribute(Res.DataCategory_Update),
138 ResDescriptionAttribute(Res.DbDataAdapter_UpdateBatchSize),
140 virtual public int UpdateBatchSize {
146 throw ADP.NotSupported();
153 DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
155 public DbCommand UpdateCommand { // V1.2.3300
157 return (DbCommand)(_IDbDataAdapter.UpdateCommand);
160 _IDbDataAdapter.UpdateCommand = value;
164 IDbCommand IDbDataAdapter.UpdateCommand { // V1.2.3300
166 return _updateCommand;
169 _updateCommand = value;
173 private System.Data.MissingMappingAction UpdateMappingAction {
175 if (System.Data.MissingMappingAction.Passthrough == MissingMappingAction) {
176 return System.Data.MissingMappingAction.Passthrough;
178 return System.Data.MissingMappingAction.Error;
182 private System.Data.MissingSchemaAction UpdateSchemaAction {
184 System.Data.MissingSchemaAction action = MissingSchemaAction;
185 if ((System.Data.MissingSchemaAction.Add == action) || (System.Data.MissingSchemaAction.AddWithKey == action)) {
186 return System.Data.MissingSchemaAction.Ignore;
188 return System.Data.MissingSchemaAction.Error;
192 protected virtual int AddToBatch(IDbCommand command) {
193 // Called to add a single command to the batch of commands that need
194 // to be executed as a batch, when batch updates are requested. It
195 // must return an identifier that can be used to identify the command
196 // to GetBatchedParameter later.
198 throw ADP.NotSupported();
201 virtual protected void ClearBatch() {
202 // Called when batch updates are requested to clear out the contents
203 // of the batch, whether or not it's been executed.
205 throw ADP.NotSupported();
208 object ICloneable.Clone() { // V1.0.3300, MDAC 69629
209 #pragma warning disable 618 // ignore obsolete warning about CloneInternals
210 DbDataAdapter clone = (DbDataAdapter)CloneInternals();
211 #pragma warning restore 618
212 clone.CloneFrom(this);
216 private void CloneFrom(DbDataAdapter from) {
217 IDbDataAdapter pfrom = from._IDbDataAdapter;
218 _IDbDataAdapter.SelectCommand = CloneCommand(pfrom.SelectCommand);
219 _IDbDataAdapter.InsertCommand = CloneCommand(pfrom.InsertCommand);
220 _IDbDataAdapter.UpdateCommand = CloneCommand(pfrom.UpdateCommand);
221 _IDbDataAdapter.DeleteCommand = CloneCommand(pfrom.DeleteCommand);
224 private IDbCommand CloneCommand(IDbCommand command) {
225 return (IDbCommand) ((command is ICloneable) ? ((ICloneable) command).Clone() : null);
228 virtual protected RowUpdatedEventArgs CreateRowUpdatedEvent(DataRow dataRow, IDbCommand command, StatementType statementType, DataTableMapping tableMapping) { // V1.0.3300
229 return new RowUpdatedEventArgs(dataRow, command, statementType, tableMapping);
232 virtual protected RowUpdatingEventArgs CreateRowUpdatingEvent(DataRow dataRow, IDbCommand command, StatementType statementType, DataTableMapping tableMapping) { // V1.0.3300
233 return new RowUpdatingEventArgs(dataRow, command, statementType, tableMapping);
236 override protected void Dispose(bool disposing) { // V1.0.3300, MDAC 69629
237 if (disposing) { // release mananged objects
238 IDbDataAdapter pthis = (IDbDataAdapter) this; // must cast to interface to obtain correct value
239 pthis.SelectCommand = null;
240 pthis.InsertCommand = null;
241 pthis.UpdateCommand = null;
242 pthis.DeleteCommand = null;
244 // release unmanaged objects
246 base.Dispose(disposing); // notify base classes
249 protected virtual int ExecuteBatch() {
250 // Called to execute the batched update command, returns the number
251 // of rows affected, just as ExecuteNonQuery would.
253 throw ADP.NotSupported();
256 public DataTable FillSchema(DataTable dataTable, SchemaType schemaType) { // V1.0.3300
258 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.FillSchema|API> %d#, dataTable, schemaType=%d{ds.SchemaType}\n", ObjectID, (int)schemaType);
260 IDbCommand selectCmd = _IDbDataAdapter.SelectCommand;
261 CommandBehavior cmdBehavior = FillCommandBehavior;
262 return FillSchema(dataTable, schemaType, selectCmd, cmdBehavior); // MDAC 67666
265 Bid.ScopeLeave(ref hscp);
269 override public DataTable[] FillSchema(DataSet dataSet, SchemaType schemaType) { // V1.0.3300
271 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.FillSchema|API> %d#, dataSet, schemaType=%d{ds.SchemaType}\n", ObjectID, (int)schemaType);
273 IDbCommand command = _IDbDataAdapter.SelectCommand;
274 if (DesignMode && ((null == command) || (null == command.Connection) || ADP.IsEmpty(command.CommandText))) {
275 return new DataTable[0]; // design-time support
277 CommandBehavior cmdBehavior = FillCommandBehavior;
278 return FillSchema(dataSet, schemaType, command, DbDataAdapter.DefaultSourceTableName, cmdBehavior);
281 Bid.ScopeLeave(ref hscp);
285 public DataTable[] FillSchema(DataSet dataSet, SchemaType schemaType, string srcTable) { // V1.0.3300
287 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.FillSchema|API> %d#, dataSet, schemaType=%d{ds.SchemaType}, srcTable=%ls%\n", ObjectID, (int)schemaType, srcTable);
289 IDbCommand selectCmd = _IDbDataAdapter.SelectCommand;
290 CommandBehavior cmdBehavior = FillCommandBehavior;
291 return FillSchema(dataSet, schemaType, selectCmd, srcTable, cmdBehavior);
294 Bid.ScopeLeave(ref hscp);
298 virtual protected DataTable[] FillSchema(DataSet dataSet, SchemaType schemaType, IDbCommand command, string srcTable, CommandBehavior behavior) { // V1.0.3300
300 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.FillSchema|API> %d#, dataSet, schemaType, command, srcTable, behavior=%d{ds.CommandBehavior}\n", ObjectID, (int)behavior);
302 if (null == dataSet) {
303 throw ADP.ArgumentNull("dataSet");
305 if ((SchemaType.Source != schemaType) && (SchemaType.Mapped != schemaType)) {
306 throw ADP.InvalidSchemaType(schemaType);
308 if (ADP.IsEmpty(srcTable)) {
309 throw ADP.FillSchemaRequiresSourceTableName("srcTable");
311 if (null == command) {
312 throw ADP.MissingSelectCommand(ADP.FillSchema);
314 return (DataTable[]) FillSchemaInternal(dataSet, null, schemaType, command, srcTable, behavior);
316 Bid.ScopeLeave(ref hscp);
320 virtual protected DataTable FillSchema(DataTable dataTable, SchemaType schemaType, IDbCommand command, CommandBehavior behavior) { // V1.0.3300
322 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.FillSchema|API> %d#, dataTable, schemaType, command, behavior=%d{ds.CommandBehavior}\n", ObjectID, (int)behavior);
324 if (null == dataTable) {
325 throw ADP.ArgumentNull("dataTable");
327 if ((SchemaType.Source != schemaType) && (SchemaType.Mapped != schemaType)) {
328 throw ADP.InvalidSchemaType(schemaType);
330 if (null == command) {
331 throw ADP.MissingSelectCommand(ADP.FillSchema);
333 string srcTableName = dataTable.TableName;
334 int index = IndexOfDataSetTable(srcTableName);
336 srcTableName = TableMappings[index].SourceTable;
338 return (DataTable) FillSchemaInternal(null, dataTable, schemaType, command, srcTableName, behavior | CommandBehavior.SingleResult);
340 Bid.ScopeLeave(ref hscp);
344 private object FillSchemaInternal(DataSet dataset, DataTable datatable, SchemaType schemaType, IDbCommand command, string srcTable, CommandBehavior behavior) {
345 object dataTables = null;
346 bool restoreNullConnection = (null == command.Connection);
348 IDbConnection activeConnection = DbDataAdapter.GetConnection3(this, command, ADP.FillSchema);
349 ConnectionState originalState = ConnectionState.Open;
352 QuietOpen(activeConnection, out originalState);
353 using(IDataReader dataReader = command.ExecuteReader(behavior | CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo)) {
354 if (null != datatable) { // delegate to next set of protected FillSchema methods
355 dataTables = FillSchema(datatable, schemaType, dataReader);
358 dataTables = FillSchema(dataset, schemaType, srcTable, dataReader);
363 QuietClose(activeConnection, originalState);
367 if (restoreNullConnection) {
368 command.Transaction = null;
369 command.Connection = null;
375 override public int Fill(DataSet dataSet) { // V1.0.3300
377 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> %d#, dataSet\n", ObjectID);
380 IDbCommand selectCmd = _IDbDataAdapter.SelectCommand;
381 CommandBehavior cmdBehavior = FillCommandBehavior;
382 return Fill(dataSet, 0, 0, DbDataAdapter.DefaultSourceTableName, selectCmd, cmdBehavior);
385 Bid.ScopeLeave(ref hscp);
389 public int Fill(DataSet dataSet, string srcTable) { // V1.0.3300
391 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> %d#, dataSet, srcTable='%ls'\n", ObjectID, srcTable);
394 IDbCommand selectCmd = _IDbDataAdapter.SelectCommand;
395 CommandBehavior cmdBehavior = FillCommandBehavior;
396 return Fill(dataSet, 0, 0, srcTable, selectCmd, cmdBehavior);
399 Bid.ScopeLeave(ref hscp);
403 public int Fill(DataSet dataSet, int startRecord, int maxRecords, string srcTable) { // V1.0.3300
405 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> %d#, dataSet, startRecord=%d, maxRecords=%d, srcTable='%ls'\n", ObjectID, startRecord, maxRecords, srcTable);
408 IDbCommand selectCmd = _IDbDataAdapter.SelectCommand;
409 CommandBehavior cmdBehavior = FillCommandBehavior;
410 return Fill(dataSet, startRecord, maxRecords, srcTable, selectCmd, cmdBehavior);
413 Bid.ScopeLeave(ref hscp);
417 virtual protected int Fill(DataSet dataSet, int startRecord, int maxRecords, string srcTable, IDbCommand command, CommandBehavior behavior) { // V1.0.3300
419 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> %d#, dataSet, startRecord, maxRecords, srcTable, command, behavior=%d{ds.CommandBehavior}\n", ObjectID, (int)behavior);
421 if (null == dataSet) {
422 throw ADP.FillRequires("dataSet");
424 if (startRecord < 0) {
425 throw ADP.InvalidStartRecord("startRecord", startRecord);
427 if (maxRecords < 0) {
428 throw ADP.InvalidMaxRecords("maxRecords", maxRecords);
430 if (ADP.IsEmpty(srcTable)) {
431 throw ADP.FillRequiresSourceTableName("srcTable");
433 if (null == command) {
434 throw ADP.MissingSelectCommand(ADP.Fill);
436 return FillInternal(dataSet, null, startRecord, maxRecords, srcTable, command, behavior);
439 Bid.ScopeLeave(ref hscp);
443 public int Fill(DataTable dataTable) { // V1.0.3300
445 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> %d#, dataTable\n", ObjectID);
448 DataTable[] dataTables = new DataTable[1] { dataTable};
449 IDbCommand selectCmd = _IDbDataAdapter.SelectCommand;
450 CommandBehavior cmdBehavior = FillCommandBehavior;
451 return Fill(dataTables, 0, 0, selectCmd, cmdBehavior);
454 Bid.ScopeLeave(ref hscp);
458 public int Fill(int startRecord, int maxRecords, params DataTable[] dataTables) { // V1.2.3300
460 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> %d#, startRecord=%d, maxRecords=%d, dataTable[]\n", ObjectID, startRecord, maxRecords);
463 IDbCommand selectCmd = _IDbDataAdapter.SelectCommand;
464 CommandBehavior cmdBehavior = FillCommandBehavior;
465 return Fill(dataTables, startRecord, maxRecords, selectCmd, cmdBehavior);
468 Bid.ScopeLeave(ref hscp);
472 virtual protected int Fill(DataTable dataTable, IDbCommand command, CommandBehavior behavior) { // V1.0.3300
474 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> dataTable, command, behavior=%d{ds.CommandBehavior}%d#\n", ObjectID, (int)behavior);
477 DataTable[] dataTables = new DataTable[1] { dataTable};
478 return Fill(dataTables, 0, 0, command, behavior);
481 Bid.ScopeLeave(ref hscp);
485 virtual protected int Fill(DataTable[] dataTables, int startRecord, int maxRecords, IDbCommand command, CommandBehavior behavior) { // V1.2.3300
487 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> %d#, dataTables[], startRecord, maxRecords, command, behavior=%d{ds.CommandBehavior}\n", ObjectID, (int)behavior);
489 if ((null == dataTables) || (0 == dataTables.Length) || (null == dataTables[0])) {
490 throw ADP.FillRequires("dataTable");
492 if (startRecord < 0) {
493 throw ADP.InvalidStartRecord("startRecord", startRecord);
495 if (maxRecords < 0) {
496 throw ADP.InvalidMaxRecords("maxRecords", maxRecords);
498 if ((1 < dataTables.Length) && ((0 != startRecord) || (0 != maxRecords))) {
499 throw ADP.OnlyOneTableForStartRecordOrMaxRecords();
501 if (null == command) {
502 throw ADP.MissingSelectCommand(ADP.Fill);
504 if (1 == dataTables.Length) {
505 behavior |= CommandBehavior.SingleResult;
507 return FillInternal(null, dataTables, startRecord, maxRecords, null, command, behavior);
510 Bid.ScopeLeave(ref hscp);
514 private int FillInternal(DataSet dataset, DataTable[] datatables, int startRecord, int maxRecords, string srcTable, IDbCommand command, CommandBehavior behavior) {
515 int rowsAddedToDataSet = 0;
516 bool restoreNullConnection = (null == command.Connection);
518 IDbConnection activeConnection = DbDataAdapter.GetConnection3(this, command, ADP.Fill);
519 ConnectionState originalState = ConnectionState.Open;
521 // the default is MissingSchemaAction.Add, the user must explicitly
522 // set MisingSchemaAction.AddWithKey to get key information back in the dataset
523 if (Data.MissingSchemaAction.AddWithKey == MissingSchemaAction) {
524 behavior |= CommandBehavior.KeyInfo;
528 QuietOpen(activeConnection, out originalState);
529 behavior |= CommandBehavior.SequentialAccess;
531 IDataReader dataReader = null;
533 dataReader = command.ExecuteReader(behavior);
535 if (null != datatables) { // delegate to next set of protected Fill methods
536 rowsAddedToDataSet = Fill(datatables, dataReader, startRecord, maxRecords);
539 rowsAddedToDataSet = Fill(dataset, srcTable, dataReader, startRecord, maxRecords);
543 if (null != dataReader) {
544 dataReader.Dispose();
549 QuietClose(activeConnection, originalState);
553 if (restoreNullConnection) {
554 command.Transaction = null;
555 command.Connection = null;
558 return rowsAddedToDataSet;
561 virtual protected IDataParameter GetBatchedParameter(int commandIdentifier, int parameterIndex) {
562 // Called to retrieve a parameter from a specific bached command, the
563 // first argument is the value that was returned by AddToBatch when it
564 // was called for the command.
566 throw ADP.NotSupported();
569 virtual protected bool GetBatchedRecordsAffected(int commandIdentifier, out int recordsAffected, out Exception error) { // SQLBU 412467
570 // Called to retrieve the records affected from a specific batched command,
571 // first argument is the value that was returned by AddToBatch when it
572 // was called for the command.
574 // default implementation always returns 1, derived classes override for otherwise
575 // otherwise DbConcurrencyException will only be thrown if sum of all records in batch is 0
577 // return 0 to cause Update to throw DbConcurrencyException
583 [ EditorBrowsableAttribute(EditorBrowsableState.Advanced) ] // MDAC 69508
584 override public IDataParameter[] GetFillParameters() { // V1.0.3300
585 IDataParameter[] value = null;
586 IDbCommand select = _IDbDataAdapter.SelectCommand;
587 if (null != select) {
588 IDataParameterCollection parameters = select.Parameters;
589 if (null != parameters) {
590 value = new IDataParameter[parameters.Count];
591 parameters.CopyTo(value, 0);
595 value = new IDataParameter[0];
600 internal DataTableMapping GetTableMapping(DataTable dataTable) {
601 DataTableMapping tableMapping = null;
602 int index = IndexOfDataSetTable(dataTable.TableName);
604 tableMapping = TableMappings[index];
606 if (null == tableMapping) {
607 if (System.Data.MissingMappingAction.Error == MissingMappingAction) {
608 throw ADP.MissingTableMappingDestination(dataTable.TableName);
610 tableMapping = new DataTableMapping(dataTable.TableName, dataTable.TableName);
615 virtual protected void InitializeBatching() {
616 // Called when batch updates are requested to prepare for processing
617 // of a batch of commands.
619 throw ADP.NotSupported();
622 virtual protected void OnRowUpdated(RowUpdatedEventArgs value) { // V1.0.3300
625 virtual protected void OnRowUpdating(RowUpdatingEventArgs value) { // V1.0.3300
628 private void ParameterInput(IDataParameterCollection parameters, StatementType typeIndex, DataRow row, DataTableMapping mappings) {
629 Data.MissingMappingAction missingMapping = UpdateMappingAction;
630 Data.MissingSchemaAction missingSchema = UpdateSchemaAction;
632 foreach(IDataParameter parameter in parameters) {
633 if ((null != parameter) && (0 != (ParameterDirection.Input & parameter.Direction))) {
635 string columnName = parameter.SourceColumn;
636 if (!ADP.IsEmpty(columnName)) {
638 DataColumn dataColumn = mappings.GetDataColumn(columnName, null, row.Table, missingMapping, missingSchema);
639 if (null != dataColumn) {
640 DataRowVersion version = DbDataAdapter.GetParameterSourceVersion(typeIndex, parameter);
641 parameter.Value = row[dataColumn, version];
644 parameter.Value = null;
647 DbParameter dbparameter = (parameter as DbParameter);
648 if ((null != dbparameter) && dbparameter.SourceColumnNullMapping) {
649 Debug.Assert(DbType.Int32 == parameter.DbType, "unexpected DbType");
650 parameter.Value = ADP.IsNull(parameter.Value) ? ParameterValueNullValue : ParameterValueNonNullValue;
657 private void ParameterOutput(IDataParameter parameter, DataRow row, DataTableMapping mappings, MissingMappingAction missingMapping, MissingSchemaAction missingSchema) {
658 if (0 != (ParameterDirection.Output & parameter.Direction)) {
660 object value = parameter.Value;
662 // null means default, meaning we leave the current DataRow value alone
663 string columnName = parameter.SourceColumn;
664 if (!ADP.IsEmpty(columnName)) {
666 DataColumn dataColumn = mappings.GetDataColumn(columnName, null, row.Table, missingMapping, missingSchema);
667 if (null != dataColumn) {
668 if (dataColumn.ReadOnly) {
670 dataColumn.ReadOnly = false;
671 row[dataColumn] = value;
674 dataColumn.ReadOnly = true;
678 row[dataColumn] = value;
686 private void ParameterOutput(IDataParameterCollection parameters, DataRow row, DataTableMapping mappings) {
687 Data.MissingMappingAction missingMapping = UpdateMappingAction;
688 Data.MissingSchemaAction missingSchema = UpdateSchemaAction;
690 foreach(IDataParameter parameter in parameters) {
691 if (null != parameter) {
692 ParameterOutput(parameter, row, mappings, missingMapping, missingSchema);
697 virtual protected void TerminateBatching() {
698 // Called when batch updates are requested to cleanup after a batch
699 // update has been completed.
701 throw ADP.NotSupported();
704 override public int Update(DataSet dataSet) { // V1.0.3300
705 //if (!TableMappings.Contains(DbDataAdapter.DefaultSourceTableName)) { // MDAC 59268
706 // throw ADP.UpdateRequiresSourceTable(DbDataAdapter.DefaultSourceTableName);
708 return Update(dataSet, DbDataAdapter.DefaultSourceTableName);
711 public int Update(DataRow[] dataRows) { // V1.0.3300
713 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Update|API> %d#, dataRows[]\n", ObjectID);
715 int rowsAffected = 0;
716 if (null == dataRows) {
717 throw ADP.ArgumentNull("dataRows");
719 else if (0 != dataRows.Length) {
720 DataTable dataTable = null;
721 for (int i = 0; i < dataRows.Length; ++i) {
722 if ((null != dataRows[i]) && (dataTable != dataRows[i].Table)) {
723 if (null != dataTable) {
724 throw ADP.UpdateMismatchRowTable(i);
726 dataTable = dataRows[i].Table;
729 if (null != dataTable) {
730 DataTableMapping tableMapping = GetTableMapping(dataTable);
731 rowsAffected = Update(dataRows, tableMapping);
737 Bid.ScopeLeave(ref hscp);
741 public int Update(DataTable dataTable) { // V1.0.3300
743 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Update|API> %d#, dataTable", ObjectID);
745 if (null == dataTable) {
746 throw ADP.UpdateRequiresDataTable("dataTable");
749 DataTableMapping tableMapping = null;
750 int index = IndexOfDataSetTable(dataTable.TableName);
752 tableMapping = TableMappings[index];
754 if (null == tableMapping) {
755 if (System.Data.MissingMappingAction.Error == MissingMappingAction) {
756 throw ADP.MissingTableMappingDestination(dataTable.TableName);
758 tableMapping = new DataTableMapping(DbDataAdapter.DefaultSourceTableName, dataTable.TableName);
760 return UpdateFromDataTable(dataTable, tableMapping);
762 Bid.ScopeLeave(ref hscp);
766 public int Update(DataSet dataSet, string srcTable) { // V1.0.3300
768 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Update|API> %d#, dataSet, srcTable='%ls'", ObjectID, srcTable);
770 if (null == dataSet) {
771 throw ADP.UpdateRequiresNonNullDataSet("dataSet");
773 if (ADP.IsEmpty(srcTable)) {
774 throw ADP.UpdateRequiresSourceTableName("srcTable");
777 //ADP.TraceDataSet("Update <" + srcTable + ">", dataSet);
780 int rowsAffected = 0;
782 System.Data.MissingMappingAction missingMapping = UpdateMappingAction;
783 DataTableMapping tableMapping = GetTableMappingBySchemaAction(srcTable, srcTable, UpdateMappingAction);
784 Debug.Assert(null != tableMapping, "null TableMapping when MissingMappingAction.Error");
786 // the ad-hoc scenario of no dataTable just returns
787 // ad-hoc scenario is defined as MissingSchemaAction.Add or MissingSchemaAction.Ignore
788 System.Data.MissingSchemaAction schemaAction = UpdateSchemaAction;
789 DataTable dataTable = tableMapping.GetDataTableBySchemaAction(dataSet, schemaAction);
790 if (null != dataTable) {
791 rowsAffected = UpdateFromDataTable(dataTable, tableMapping);
793 else if (!HasTableMappings() || (-1 == TableMappings.IndexOf(tableMapping))) {
794 //throw error since the user didn't explicitly map this tableName to Ignore.
795 throw ADP.UpdateRequiresSourceTable(srcTable); // MDAC 72681
800 Bid.ScopeLeave(ref hscp);
804 virtual protected int Update(DataRow[] dataRows, DataTableMapping tableMapping) { // V1.0.3300
806 Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Update|API> %d#, dataRows[], tableMapping", ObjectID);
808 Debug.Assert((null != dataRows) && (0 < dataRows.Length), "Update: bad dataRows");
809 Debug.Assert(null != tableMapping, "Update: bad DataTableMapping");
811 // If records were affected, increment row count by one - that is number of rows affected in dataset.
812 int cumulativeDataRowsAffected = 0;
814 IDbConnection[] connections = new IDbConnection[5]; // one for each statementtype
815 ConnectionState[] connectionStates = new ConnectionState[5]; // closed by default (== 0)
817 bool useSelectConnectionState = false; // MDAC 58710
818 IDbCommand tmpcmd = _IDbDataAdapter.SelectCommand;
819 if (null != tmpcmd) {
820 connections[0] = tmpcmd.Connection;
821 if (null != connections[0]) {
822 connectionStates[0] = connections[0].State;
823 useSelectConnectionState = true;
827 int maxBatchCommands = Math.Min(UpdateBatchSize, dataRows.Length);
829 if (maxBatchCommands < 1) { // batch size of zero indicates one batch, no matter how large...
830 maxBatchCommands = dataRows.Length;
833 BatchCommandInfo[] batchCommands = new BatchCommandInfo[maxBatchCommands];
834 DataRow[] rowBatch = new DataRow[maxBatchCommands];
835 int commandCount = 0;
837 // the outer try/finally is for closing any connections we may have opened
840 if (1 != maxBatchCommands) {
841 InitializeBatching();
843 StatementType statementType = StatementType.Select;
844 IDbCommand dataCommand = null;
846 // for each row which is either insert, update, or delete
847 foreach(DataRow dataRow in dataRows) {
848 if (null == dataRow) {
849 continue; // foreach DataRow
851 bool isCommandFromRowUpdating = false;
853 // obtain the appropriate command
854 switch (dataRow.RowState) {
855 case DataRowState.Detached:
856 case DataRowState.Unchanged:
857 continue; // foreach DataRow
858 case DataRowState.Added:
859 statementType = StatementType.Insert;
860 dataCommand = _IDbDataAdapter.InsertCommand;
862 case DataRowState.Deleted:
863 statementType = StatementType.Delete;
864 dataCommand = _IDbDataAdapter.DeleteCommand;
866 case DataRowState.Modified:
867 statementType = StatementType.Update;
868 dataCommand = _IDbDataAdapter.UpdateCommand;
871 Debug.Assert(false, "InvalidDataRowState");
872 throw ADP.InvalidDataRowState(dataRow.RowState); // out of Update without completing batch
875 // setup the event to be raised
876 RowUpdatingEventArgs rowUpdatingEvent = CreateRowUpdatingEvent(dataRow, dataCommand, statementType, tableMapping);
878 // this try/catch for any exceptions during the parameter initialization
880 dataRow.RowError = null; // MDAC 67185
881 if (null != dataCommand) {
882 // prepare the parameters for the user who then can modify them during OnRowUpdating
883 ParameterInput(dataCommand.Parameters, statementType, dataRow, tableMapping);
886 catch (Exception e) {
888 if (!ADP.IsCatchableExceptionType(e)) {
892 ADP.TraceExceptionForCapture(e);
894 rowUpdatingEvent.Errors = e;
895 rowUpdatingEvent.Status = UpdateStatus.ErrorsOccurred;
898 OnRowUpdating(rowUpdatingEvent); // user may throw out of Update without completing batch
900 IDbCommand tmpCommand = rowUpdatingEvent.Command;
901 isCommandFromRowUpdating = (dataCommand != tmpCommand);
902 dataCommand = tmpCommand;
905 // handle the status from RowUpdating event
906 UpdateStatus rowUpdatingStatus = rowUpdatingEvent.Status;
907 if (UpdateStatus.Continue != rowUpdatingStatus) {
908 if (UpdateStatus.ErrorsOccurred == rowUpdatingStatus) {
909 UpdatingRowStatusErrors(rowUpdatingEvent, dataRow);
910 continue; // foreach DataRow
912 else if (UpdateStatus.SkipCurrentRow == rowUpdatingStatus) {
913 if (DataRowState.Unchanged == dataRow.RowState) { // MDAC 66286
914 cumulativeDataRowsAffected++;
916 continue; // foreach DataRow
918 else if (UpdateStatus.SkipAllRemainingRows == rowUpdatingStatus) {
919 if (DataRowState.Unchanged == dataRow.RowState) { // MDAC 66286
920 cumulativeDataRowsAffected++;
922 break; // execute existing batch and return
925 throw ADP.InvalidUpdateStatus(rowUpdatingStatus); // out of Update
928 // else onward to Append/ExecuteNonQuery/ExecuteReader
930 rowUpdatingEvent = null;
931 RowUpdatedEventArgs rowUpdatedEvent = null;
933 if (1 == maxBatchCommands) {
934 if (null != dataCommand) {
935 batchCommands[0].CommandIdentifier = 0;
936 batchCommands[0].ParameterCount = dataCommand.Parameters.Count;
937 batchCommands[0].StatementType = statementType;
938 batchCommands[0].UpdatedRowSource = dataCommand.UpdatedRowSource;
940 batchCommands[0].Row = dataRow;
941 rowBatch[0] = dataRow; // not doing a batch update, just simplifying code...
945 Exception errors = null;
948 if (null != dataCommand) {
949 if (0 == (UpdateRowSource.FirstReturnedRecord & dataCommand.UpdatedRowSource)) {
950 // append the command to the commandset. If an exception
951 // occurs, then the user must append and continue
953 batchCommands[commandCount].CommandIdentifier = AddToBatch(dataCommand);
954 batchCommands[commandCount].ParameterCount = dataCommand.Parameters.Count;
955 batchCommands[commandCount].Row = dataRow;
956 batchCommands[commandCount].StatementType = statementType;
957 batchCommands[commandCount].UpdatedRowSource = dataCommand.UpdatedRowSource;
959 rowBatch[commandCount] = dataRow;
962 if (commandCount < maxBatchCommands) {
963 continue; // foreach DataRow
965 // else onward execute the batch
968 // do not allow the expectation that returned results will be used
969 errors = ADP.ResultsNotAllowedDuringBatch();
973 // null Command will force RowUpdatedEvent with ErrorsOccured without completing batch
974 errors = ADP.UpdateRequiresCommand(statementType, isCommandFromRowUpdating);
977 catch (Exception e) { // try/catch for RowUpdatedEventArgs
979 if (!ADP.IsCatchableExceptionType(e)) {
983 ADP.TraceExceptionForCapture(e);
987 if (null != errors) {
988 rowUpdatedEvent = CreateRowUpdatedEvent(dataRow, dataCommand, StatementType.Batch, tableMapping);
989 rowUpdatedEvent.Errors = errors;
990 rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
992 OnRowUpdated(rowUpdatedEvent); // user may throw out of Update
993 if (errors != rowUpdatedEvent.Errors) { // user set the error msg and we will use it
994 for(int i = 0; i < batchCommands.Length; ++i) {
995 batchCommands[i].Errors = null;
999 cumulativeDataRowsAffected += UpdatedRowStatus(rowUpdatedEvent, batchCommands, commandCount);
1000 if (UpdateStatus.SkipAllRemainingRows == rowUpdatedEvent.Status) {
1003 continue; // foreach datarow
1007 rowUpdatedEvent = CreateRowUpdatedEvent(dataRow, dataCommand, statementType, tableMapping);
1009 // this try/catch for any exceptions during the execution, population, output parameters
1011 if (1 != maxBatchCommands) {
1012 IDbConnection connection = DbDataAdapter.GetConnection1(this);
1014 ConnectionState state = UpdateConnectionOpen(connection, StatementType.Batch, connections, connectionStates, useSelectConnectionState);
1015 rowUpdatedEvent.AdapterInit(rowBatch);
1017 if (ConnectionState.Open == state) {
1018 UpdateBatchExecute(batchCommands, commandCount, rowUpdatedEvent);
1021 // null Connection will force RowUpdatedEvent with ErrorsOccured without completing batch
1022 rowUpdatedEvent.Errors = ADP.UpdateOpenConnectionRequired(StatementType.Batch, false, state);
1023 rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
1026 else if (null != dataCommand) {
1027 IDbConnection connection = DbDataAdapter.GetConnection4(this, dataCommand, statementType, isCommandFromRowUpdating);
1028 ConnectionState state = UpdateConnectionOpen(connection, statementType, connections, connectionStates, useSelectConnectionState);
1029 if (ConnectionState.Open == state) {
1030 UpdateRowExecute(rowUpdatedEvent, dataCommand, statementType);
1031 batchCommands[0].RecordsAffected = rowUpdatedEvent.RecordsAffected;
1032 batchCommands[0].Errors = null;
1035 // null Connection will force RowUpdatedEvent with ErrorsOccured without completing batch
1036 rowUpdatedEvent.Errors = ADP.UpdateOpenConnectionRequired(statementType, isCommandFromRowUpdating, state);
1037 rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
1041 // null Command will force RowUpdatedEvent with ErrorsOccured without completing batch
1042 rowUpdatedEvent.Errors = ADP.UpdateRequiresCommand(statementType, isCommandFromRowUpdating);
1043 rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
1046 catch (Exception e) { // try/catch for RowUpdatedEventArgs
1048 if (!ADP.IsCatchableExceptionType(e)) {
1052 ADP.TraceExceptionForCapture(e);
1054 rowUpdatedEvent.Errors = e;
1055 rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
1057 bool clearBatchOnSkipAll = (UpdateStatus.ErrorsOccurred == rowUpdatedEvent.Status);
1060 Exception errors = rowUpdatedEvent.Errors;
1061 OnRowUpdated(rowUpdatedEvent); // user may throw out of Update
1062 // NOTE: the contents of rowBatch are now tainted...
1063 if (errors != rowUpdatedEvent.Errors) { // user set the error msg and we will use it
1064 for(int i = 0; i < batchCommands.Length; ++i) {
1065 batchCommands[i].Errors = null;
1069 cumulativeDataRowsAffected += UpdatedRowStatus(rowUpdatedEvent, batchCommands, commandCount);
1071 if (UpdateStatus.SkipAllRemainingRows == rowUpdatedEvent.Status) {
1072 if (clearBatchOnSkipAll && 1 != maxBatchCommands) {
1076 break; // from update
1079 if (1 != maxBatchCommands) {
1083 for(int i = 0; i < batchCommands.Length; ++i) {
1084 batchCommands[i] = default(BatchCommandInfo);
1087 } // foreach DataRow
1089 // must handle the last batch
1090 if (1 != maxBatchCommands && 0 < commandCount) {
1091 RowUpdatedEventArgs rowUpdatedEvent = CreateRowUpdatedEvent(null, dataCommand, statementType, tableMapping);
1094 IDbConnection connection = DbDataAdapter.GetConnection1(this);
1096 ConnectionState state = UpdateConnectionOpen(connection, StatementType.Batch, connections, connectionStates, useSelectConnectionState);
1098 DataRow[] finalRowBatch = rowBatch;
1100 if (commandCount < rowBatch.Length) {
1101 finalRowBatch = new DataRow[commandCount];
1102 Array.Copy(rowBatch, finalRowBatch, commandCount);
1104 rowUpdatedEvent.AdapterInit(finalRowBatch);
1106 if (ConnectionState.Open == state) {
1107 UpdateBatchExecute(batchCommands, commandCount, rowUpdatedEvent);
1110 // null Connection will force RowUpdatedEvent with ErrorsOccured without completing batch
1111 rowUpdatedEvent.Errors = ADP.UpdateOpenConnectionRequired(StatementType.Batch, false, state);
1112 rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
1115 catch (Exception e) { // try/catch for RowUpdatedEventArgs
1117 if (!ADP.IsCatchableExceptionType(e)) {
1121 ADP.TraceExceptionForCapture(e);
1123 rowUpdatedEvent.Errors = e;
1124 rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
1126 Exception errors = rowUpdatedEvent.Errors;
1127 OnRowUpdated(rowUpdatedEvent); // user may throw out of Update
1128 // NOTE: the contents of rowBatch are now tainted...
1129 if (errors != rowUpdatedEvent.Errors) { // user set the error msg and we will use it
1130 for(int i = 0; i < batchCommands.Length; ++i) {
1131 batchCommands[i].Errors = null;
1135 cumulativeDataRowsAffected += UpdatedRowStatus(rowUpdatedEvent, batchCommands, commandCount);
1139 if (1 != maxBatchCommands) {
1140 TerminateBatching();
1144 finally { // try/finally for connection cleanup
1145 for(int i = 0; i < connections.Length; ++i) {
1146 QuietClose(connections[i], connectionStates[i]);
1149 return cumulativeDataRowsAffected;
1152 Bid.ScopeLeave(ref hscp);
1156 private void UpdateBatchExecute(BatchCommandInfo[] batchCommands, int commandCount, RowUpdatedEventArgs rowUpdatedEvent) {
1158 // the batch execution may succeed, partially succeed and throw an exception (or not), or totally fail
1159 int recordsAffected = ExecuteBatch();
1160 rowUpdatedEvent.AdapterInit(recordsAffected);
1162 catch(DbException e) {
1163 // an exception was thrown be but some part of the batch may have been succesfull
1164 ADP.TraceExceptionForCapture(e);
1165 rowUpdatedEvent.Errors = e;
1166 rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
1168 Data.MissingMappingAction missingMapping = UpdateMappingAction;
1169 Data.MissingSchemaAction missingSchema = UpdateSchemaAction;
1171 int checkRecordsAffected = 0;
1172 bool hasConcurrencyViolation = false;
1173 List<DataRow> rows = null;
1175 // walk through the batch to build the sum of recordsAffected
1176 // determine possible indivdual messages per datarow
1177 // determine possible concurrency violations per datarow
1178 // map output parameters to the datarow
1179 for(int bc = 0; bc < commandCount; ++bc) {
1180 BatchCommandInfo batchCommand = batchCommands[bc];
1181 StatementType statementType = batchCommand.StatementType;
1183 // default implementation always returns 1, derived classes must override
1184 // otherwise DbConcurrencyException will only be thrown if sum of all records in batch is 0
1186 if (GetBatchedRecordsAffected(batchCommand.CommandIdentifier, out rowAffected, out batchCommands[bc].Errors)) {
1187 batchCommands[bc].RecordsAffected = rowAffected;
1190 if ((null == batchCommands[bc].Errors) && batchCommands[bc].RecordsAffected.HasValue) {
1191 // determine possible concurrency violations per datarow
1192 if ((StatementType.Update == statementType) || (StatementType.Delete == statementType)) {
1193 checkRecordsAffected++;
1194 if (0 == rowAffected) {
1196 rows = new List<DataRow>();
1198 batchCommands[bc].Errors = ADP.UpdateConcurrencyViolation(batchCommands[bc].StatementType, 0, 1, new DataRow[] { rowUpdatedEvent.Rows[bc] });
1199 hasConcurrencyViolation = true;
1200 rows.Add(rowUpdatedEvent.Rows[bc]);
1204 // map output parameters to the datarow
1205 if (((StatementType.Insert == statementType) || (StatementType.Update == statementType))
1206 && (0 != (UpdateRowSource.OutputParameters & batchCommand.UpdatedRowSource)) && (0 != rowAffected)) // MDAC 71174
1208 if (StatementType.Insert == statementType) { // MDAC 64199
1209 // AcceptChanges for 'added' rows so backend generated keys that are returned
1210 // propagte into the datatable correctly.
1211 rowUpdatedEvent.Rows[bc].AcceptChanges();
1214 for(int i = 0; i < batchCommand.ParameterCount; ++i) {
1215 IDataParameter parameter = GetBatchedParameter(batchCommand.CommandIdentifier, i);
1216 ParameterOutput(parameter, batchCommand.Row, rowUpdatedEvent.TableMapping, missingMapping, missingSchema);
1222 if (null == rowUpdatedEvent.Errors) {
1223 // Only error if RecordsAffect == 0, not -1. A value of -1 means no count was received from server,
1224 // do not error in that situation (means 'set nocount on' was executed on server).
1225 if (UpdateStatus.Continue == rowUpdatedEvent.Status) {
1226 if ((0 < checkRecordsAffected) && ((0 == rowUpdatedEvent.RecordsAffected) || hasConcurrencyViolation)) {
1227 // bug50526, an exception if no records affected and attempted an Update/Delete
1228 Debug.Assert(null == rowUpdatedEvent.Errors, "Continue - but contains an exception");
1229 DataRow[] rowsInError = (null != rows) ? rows.ToArray() : rowUpdatedEvent.Rows;
1230 rowUpdatedEvent.Errors = ADP.UpdateConcurrencyViolation(StatementType.Batch, commandCount - rowsInError.Length, commandCount, rowsInError); // MDAC 55735
1231 rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
1237 private ConnectionState UpdateConnectionOpen(IDbConnection connection, StatementType statementType, IDbConnection[] connections, ConnectionState[] connectionStates, bool useSelectConnectionState) {
1238 Debug.Assert(null != connection, "unexpected null connection");
1239 Debug.Assert(null != connection, "unexpected null connection");
1240 int index = (int)statementType;
1241 if (connection != connections[index]) {
1242 // if the user has changed the connection on the command object
1243 // and we had opened that connection, close that connection
1244 QuietClose(connections[index], connectionStates[index]);
1246 connections[index] = connection;
1247 connectionStates[index] = ConnectionState.Closed; // required, open may throw
1249 QuietOpen(connection, out connectionStates[index]);
1250 if (useSelectConnectionState && (connections[0] == connection)) {
1251 connectionStates[index] = connections[0].State;
1254 return connection.State;
1257 private int UpdateFromDataTable(DataTable dataTable, DataTableMapping tableMapping) {
1258 int rowsAffected = 0;
1259 DataRow[] dataRows = ADP.SelectAdapterRows(dataTable, false);
1260 if ((null != dataRows) && (0 < dataRows.Length)) {
1261 rowsAffected = Update(dataRows, tableMapping);
1263 return rowsAffected;
1266 private void UpdateRowExecute(RowUpdatedEventArgs rowUpdatedEvent, IDbCommand dataCommand, StatementType cmdIndex) {
1267 Debug.Assert(null != rowUpdatedEvent, "null rowUpdatedEvent");
1268 Debug.Assert(null != dataCommand, "null dataCommand");
1269 Debug.Assert(rowUpdatedEvent.Command == dataCommand, "dataCommand differs from rowUpdatedEvent");
1271 bool insertAcceptChanges = true;
1272 UpdateRowSource updatedRowSource = dataCommand.UpdatedRowSource;
1273 if ((StatementType.Delete == cmdIndex) || (0 == (UpdateRowSource.FirstReturnedRecord & updatedRowSource))) {
1274 int recordsAffected = dataCommand.ExecuteNonQuery(); // MDAC 88441
1275 rowUpdatedEvent.AdapterInit(recordsAffected);
1277 else if ((StatementType.Insert == cmdIndex) || (StatementType.Update == cmdIndex)) {
1278 // we only care about the first row of the first result
1279 using(IDataReader dataReader = dataCommand.ExecuteReader(CommandBehavior.SequentialAccess)) {
1280 DataReaderContainer readerHandler = DataReaderContainer.Create(dataReader, ReturnProviderSpecificTypes);
1282 bool getData = false;
1284 // advance to the first row returning result set
1285 // determined by actually having columns in the result set
1286 if (0 < readerHandler.FieldCount) {
1290 } while (dataReader.NextResult());
1292 if (getData && (0 != dataReader.RecordsAffected)) { // MDAC 71174
1293 SchemaMapping mapping = new SchemaMapping(this, null, rowUpdatedEvent.Row.Table, readerHandler, false, SchemaType.Mapped, rowUpdatedEvent.TableMapping.SourceTable, true, null, null);
1295 if ((null != mapping.DataTable) && (null != mapping.DataValues)) {
1296 if (dataReader.Read()) {
1297 if ((StatementType.Insert == cmdIndex) && insertAcceptChanges) { // MDAC 64199
1298 rowUpdatedEvent.Row.AcceptChanges();
1299 insertAcceptChanges = false;
1301 mapping.ApplyToDataRow(rowUpdatedEvent.Row);
1307 // using Close which can optimize its { while(dataReader.NextResult()); } loop
1310 // RecordsAffected is available after Close, but don't trust it after Dispose
1311 int recordsAffected = dataReader.RecordsAffected;
1312 rowUpdatedEvent.AdapterInit(recordsAffected);
1317 // StatementType.Select, StatementType.Batch
1318 Debug.Assert(false, "unexpected StatementType");
1321 // map the parameter results to the dataSet
1322 if (((StatementType.Insert == cmdIndex) || (StatementType.Update == cmdIndex))
1323 && (0 != (UpdateRowSource.OutputParameters & updatedRowSource)) && (0 != rowUpdatedEvent.RecordsAffected)) { // MDAC 71174
1325 if ((StatementType.Insert == cmdIndex) && insertAcceptChanges) { // MDAC 64199
1326 rowUpdatedEvent.Row.AcceptChanges();
1328 ParameterOutput(dataCommand.Parameters, rowUpdatedEvent.Row, rowUpdatedEvent.TableMapping);
1331 // Only error if RecordsAffect == 0, not -1. A value of -1 means no count was received from server,
1332 // do not error in that situation (means 'set nocount on' was executed on server).
1333 switch(rowUpdatedEvent.Status) {
1334 case UpdateStatus.Continue:
1336 case StatementType.Update:
1337 case StatementType.Delete:
1338 if (0 == rowUpdatedEvent.RecordsAffected) {
1339 // bug50526, an exception if no records affected and attempted an Update/Delete
1340 Debug.Assert(null == rowUpdatedEvent.Errors, "Continue - but contains an exception");
1341 rowUpdatedEvent.Errors = ADP.UpdateConcurrencyViolation(cmdIndex, rowUpdatedEvent.RecordsAffected, 1, new DataRow[] { rowUpdatedEvent.Row }); // MDAC 55735
1342 rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
1350 private int UpdatedRowStatus(RowUpdatedEventArgs rowUpdatedEvent, BatchCommandInfo[] batchCommands, int commandCount) {
1351 Debug.Assert(null != rowUpdatedEvent, "null rowUpdatedEvent");
1352 int cumulativeDataRowsAffected = 0;
1353 switch (rowUpdatedEvent.Status) {
1354 case UpdateStatus.Continue:
1355 cumulativeDataRowsAffected = UpdatedRowStatusContinue(rowUpdatedEvent, batchCommands, commandCount);
1356 break; // return to foreach DataRow
1357 case UpdateStatus.ErrorsOccurred:
1358 cumulativeDataRowsAffected = UpdatedRowStatusErrors(rowUpdatedEvent, batchCommands, commandCount);
1359 break; // no datarow affected if ErrorsOccured
1360 case UpdateStatus.SkipCurrentRow:
1361 case UpdateStatus.SkipAllRemainingRows: // cancel the Update method
1362 cumulativeDataRowsAffected = UpdatedRowStatusSkip(batchCommands, commandCount);
1363 break; // foreach DataRow without accepting changes on this row (but user may haved accepted chagnes for us)
1365 throw ADP.InvalidUpdateStatus(rowUpdatedEvent.Status);
1366 } // switch RowUpdatedEventArgs.Status
1367 return cumulativeDataRowsAffected;
1370 private int UpdatedRowStatusContinue(RowUpdatedEventArgs rowUpdatedEvent, BatchCommandInfo[] batchCommands, int commandCount) {
1371 Debug.Assert(null != batchCommands, "null batchCommands?");
1372 int cumulativeDataRowsAffected = 0;
1373 // 1. We delay accepting the changes until after we fire RowUpdatedEvent
1374 // so the user has a chance to call RejectChanges for any given reason
1375 // 2. If the DataSource return 0 records affected, its an indication that
1376 // the command didn't take so we don't want to automatically
1378 // With 'set nocount on' the count will be -1, accept changes in that case too.
1379 // 3. Don't accept changes if no rows were affected, the user needs
1380 // to know that there is a concurrency violation
1382 // Only accept changes if the row is not already accepted, ie detached.
1383 bool acdu = AcceptChangesDuringUpdate;
1384 for (int i = 0; i < commandCount; i++) {
1385 DataRow row = batchCommands[i].Row;
1386 if ((null == batchCommands[i].Errors) && batchCommands[i].RecordsAffected.HasValue && (0 != batchCommands[i].RecordsAffected.Value)) {
1387 Debug.Assert(null != row, "null dataRow?");
1389 if (0 != ((DataRowState.Added|DataRowState.Deleted|DataRowState.Modified)&row.RowState)) {
1390 row.AcceptChanges();
1393 cumulativeDataRowsAffected++;
1396 return cumulativeDataRowsAffected;
1399 private int UpdatedRowStatusErrors(RowUpdatedEventArgs rowUpdatedEvent, BatchCommandInfo[] batchCommands, int commandCount) {
1400 Debug.Assert(null != batchCommands, "null batchCommands?");
1401 Exception errors = rowUpdatedEvent.Errors;
1402 if (null == errors) {
1403 // user changed status to ErrorsOccured without supplying an exception message
1404 errors = ADP.RowUpdatedErrors();
1405 rowUpdatedEvent.Errors = errors;
1410 string message = errors.Message;
1412 for (int i = 0; i < commandCount; i++) {
1413 DataRow row = batchCommands[i].Row;
1414 Debug.Assert(null != row, "null dataRow?");
1416 if (null != batchCommands[i].Errors) { // will exist if 0 == RecordsAffected
1417 string rowMsg = batchCommands[i].Errors.Message;
1418 if (String.IsNullOrEmpty(rowMsg)) {
1421 row.RowError += rowMsg;
1425 if (!done) { // all rows are in 'error'
1426 for (int i = 0; i < commandCount; i++) {
1427 DataRow row = batchCommands[i].Row;
1428 // its possible a DBConcurrencyException exists and all rows have records affected
1429 // via not overriding GetBatchedRecordsAffected or user setting the exception
1430 row.RowError += message; // MDAC 65808
1434 affected = UpdatedRowStatusContinue(rowUpdatedEvent, batchCommands, commandCount);
1436 if (!ContinueUpdateOnError) { // MDAC 66900
1437 throw errors; // out of Update
1439 return affected; // return the count of successful rows within the batch failure
1442 private int UpdatedRowStatusSkip(BatchCommandInfo[] batchCommands, int commandCount){
1443 Debug.Assert(null != batchCommands, "null batchCommands?");
1445 int cumulativeDataRowsAffected = 0;
1447 for (int i = 0; i < commandCount; i++) {
1448 DataRow row = batchCommands[i].Row;
1449 Debug.Assert(null != row, "null dataRow?");
1450 if (0 != ((DataRowState.Detached|DataRowState.Unchanged) & row.RowState)) {
1451 cumulativeDataRowsAffected++; // MDAC 66286
1454 return cumulativeDataRowsAffected;
1457 private void UpdatingRowStatusErrors(RowUpdatingEventArgs rowUpdatedEvent, DataRow dataRow) {
1458 Debug.Assert(null != dataRow, "null dataRow");
1459 Exception errors = rowUpdatedEvent.Errors;
1461 if (null == errors) {
1462 // user changed status to ErrorsOccured without supplying an exception message
1463 errors = ADP.RowUpdatingErrors();
1464 rowUpdatedEvent.Errors = errors;
1466 string message = errors.Message;
1467 dataRow.RowError += message; // MDAC 65808
1469 if (!ContinueUpdateOnError) { // MDAC 66900
1470 throw errors; // out of Update
1474 static private IDbConnection GetConnection1(DbDataAdapter adapter) {
1475 IDbCommand command = adapter._IDbDataAdapter.SelectCommand;
1476 if (null == command) {
1477 command = adapter._IDbDataAdapter.InsertCommand;
1478 if (null == command) {
1479 command = adapter._IDbDataAdapter.UpdateCommand;
1480 if (null == command) {
1481 command = adapter._IDbDataAdapter.DeleteCommand;
1485 IDbConnection connection = null;
1486 if (null != command) {
1487 connection = command.Connection;
1489 if (null == connection) {
1490 throw ADP.UpdateConnectionRequired(StatementType.Batch, false);
1495 static private IDbConnection GetConnection3(DbDataAdapter adapter, IDbCommand command, string method) {
1496 Debug.Assert(null != command, "GetConnection3: null command");
1497 Debug.Assert(!ADP.IsEmpty(method), "missing method name");
1498 IDbConnection connection = command.Connection;
1499 if (null == connection) {
1500 throw ADP.ConnectionRequired_Res(method);
1505 static private IDbConnection GetConnection4(DbDataAdapter adapter, IDbCommand command, StatementType statementType, bool isCommandFromRowUpdating) {
1506 Debug.Assert(null != command, "GetConnection4: null command");
1507 IDbConnection connection = command.Connection;
1508 if (null == connection) {
1509 throw ADP.UpdateConnectionRequired(statementType, isCommandFromRowUpdating);
1513 static private DataRowVersion GetParameterSourceVersion(StatementType statementType, IDataParameter parameter) {
1514 switch (statementType) {
1515 case StatementType.Insert: return DataRowVersion.Current; // ignores parameter.SourceVersion
1516 case StatementType.Update: return parameter.SourceVersion;
1517 case StatementType.Delete: return DataRowVersion.Original; // ignores parameter.SourceVersion
1518 case StatementType.Select:
1519 case StatementType.Batch:
1520 throw ADP.UnwantedStatementType(statementType);
1522 throw ADP.InvalidStatementType(statementType);
1526 static private void QuietClose(IDbConnection connection, ConnectionState originalState) {
1527 // close the connection if:
1528 // * it was closed on first use and adapter has opened it, AND
1529 // * provider's implementation did not ask to keep this connection open
1530 if ((null != connection) && (ConnectionState.Closed == originalState)) {
1531 // we don't have to check the current connection state because
1532 // it is supposed to be safe to call Close multiple times
1537 // QuietOpen needs to appear in the try {} finally { QuietClose } block
1538 // otherwise a possibility exists that an exception may be thrown, i.e. ThreadAbortException
1539 // where we would Open the connection and not close it
1540 static private void QuietOpen(IDbConnection connection, out ConnectionState originalState) {
1541 Debug.Assert(null != connection, "QuietOpen: null connection");
1542 originalState = connection.State;
1543 if (ConnectionState.Closed == originalState) {