public static async Task UpdateRowExecuteAsync(IAdaSchemaMappingAdapter adapter, Boolean returnProviderSpecificTypes, RowUpdatedEventArgs rowUpdatedEvent, DbCommand dataCommand, StatementType cmdIndex, CancellationToken cancellationToken) { Debug.Assert(null != rowUpdatedEvent, "null rowUpdatedEvent"); Debug.Assert(null != dataCommand, "null dataCommand"); Debug.Assert(rowUpdatedEvent.Command == dataCommand, "dataCommand differs from rowUpdatedEvent"); bool insertAcceptChanges = true; UpdateRowSource updatedRowSource = dataCommand.UpdatedRowSource; if ((StatementType.Delete == cmdIndex) || (0 == (UpdateRowSource.FirstReturnedRecord & updatedRowSource))) { int recordsAffected = await dataCommand.ExecuteNonQueryAsync().ConfigureAwait(false); rowUpdatedEvent.AdapterInit_(recordsAffected); } else if ((StatementType.Insert == cmdIndex) || (StatementType.Update == cmdIndex)) { // we only care about the first row of the first result using (DbDataReader dataReader = await dataCommand.ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken).ConfigureAwait(false)) { AdaDataReaderContainer readerHandler = AdaDataReaderContainer.Create(dataReader, returnProviderSpecificTypes); try { bool getData = false; do { // advance to the first row returning result set // determined by actually having columns in the result set if (0 < readerHandler.FieldCount) { getData = true; break; } }while (await dataReader.NextResultAsync(cancellationToken).ConfigureAwait(false)); if (getData && (0 != dataReader.RecordsAffected)) { AdaSchemaMapping mapping = new AdaSchemaMapping(adapter, null, rowUpdatedEvent.Row.Table, readerHandler, false, SchemaType.Mapped, rowUpdatedEvent.TableMapping.SourceTable, true, null, null); if ((null != mapping.DataTable) && (null != mapping.DataValues)) { if (dataReader.Read()) { if ((StatementType.Insert == cmdIndex) && insertAcceptChanges) { // MDAC 64199 rowUpdatedEvent.Row.AcceptChanges(); insertAcceptChanges = false; } mapping.ApplyToDataRow(rowUpdatedEvent.Row); } } } } finally { // using Close which can optimize its { while(dataReader.NextResult()); } loop dataReader.Close(); // RecordsAffected is available after Close, but don't trust it after Dispose int recordsAffected = dataReader.RecordsAffected; rowUpdatedEvent.AdapterInit_(recordsAffected); } } } else { // StatementType.Select, StatementType.Batch Debug.Assert(false, "unexpected StatementType"); } // map the parameter results to the dataSet if ( ( (StatementType.Insert == cmdIndex) || (StatementType.Update == cmdIndex) ) && ( 0 != (UpdateRowSource.OutputParameters & updatedRowSource) ) && (0 != rowUpdatedEvent.RecordsAffected) ) { if ((StatementType.Insert == cmdIndex) && insertAcceptChanges) { rowUpdatedEvent.Row.AcceptChanges(); } ParameterMethods.ParameterOutput(adapter.MissingMappingAction, adapter.MissingSchemaAction, dataCommand.Parameters, rowUpdatedEvent.Row, rowUpdatedEvent.TableMapping); } // Only error if RecordsAffect == 0, not -1. A value of -1 means no count was received from server, // do not error in that situation (means 'set nocount on' was executed on server). switch (rowUpdatedEvent.Status) { case UpdateStatus.Continue: switch (cmdIndex) { case StatementType.Update: case StatementType.Delete: if (0 == rowUpdatedEvent.RecordsAffected) { // bug50526, an exception if no records affected and attempted an Update/Delete Debug.Assert(null == rowUpdatedEvent.Errors, "Continue - but contains an exception"); rowUpdatedEvent.Errors = ADP.UpdateConcurrencyViolation(cmdIndex, rowUpdatedEvent.RecordsAffected, 1, new DataRow[] { rowUpdatedEvent.Row }); // MDAC 55735 rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred; } break; } break; } }
public static async Task <int> UpdateAsync(ICanUpdateAsync self, DataRow[] dataRows, DataTableMapping tableMapping, CancellationToken cancellationToken) { Debug.Assert((null != dataRows) && (0 < dataRows.Length), "Update: bad dataRows"); Debug.Assert(null != tableMapping, "Update: bad DataTableMapping"); // If records were affected, increment row count by one - that is number of rows affected in dataset. int cumulativeDataRowsAffected = 0; DbConnection[] connections = new DbConnection[5]; // one for each statementtype ConnectionState[] connectionStates = new ConnectionState[5]; // closed by default (== 0) bool useSelectConnectionState = false; // MDAC 58710 DbCommand tmpcmd = self.SelectCommand; if (null != tmpcmd) { connections[0] = tmpcmd.Connection; if (null != connections[0]) { connectionStates[0] = connections[0].State; useSelectConnectionState = true; } } int maxBatchCommands = Math.Min(self.UpdateBatchSize, dataRows.Length); if (maxBatchCommands < 1) { // batch size of zero indicates one batch, no matter how large... maxBatchCommands = dataRows.Length; } BatchCommandInfo[] batchCommands = new BatchCommandInfo[maxBatchCommands]; DataRow[] rowBatch = new DataRow[maxBatchCommands]; int commandCount = 0; // the outer try/finally is for closing any connections we may have opened try { try { if (1 != maxBatchCommands) { self.InitializeBatching(); } StatementType statementType = StatementType.Select; DbCommand dataCommand = null; // for each row which is either insert, update, or delete foreach (DataRow dataRow in dataRows) { if (null == dataRow) { continue; // foreach DataRow } bool isCommandFromRowUpdating = false; // obtain the appropriate command switch (dataRow.RowState) { case DataRowState.Detached: case DataRowState.Unchanged: continue; // foreach DataRow case DataRowState.Added: statementType = StatementType.Insert; dataCommand = self.InsertCommand; break; case DataRowState.Deleted: statementType = StatementType.Delete; dataCommand = self.DeleteCommand; break; case DataRowState.Modified: statementType = StatementType.Update; dataCommand = self.UpdateCommand; break; default: Debug.Assert(false, "InvalidDataRowState"); throw ADP.InvalidDataRowState(dataRow.RowState); // out of Update without completing batch } // setup the event to be raised RowUpdatingEventArgs rowUpdatingEvent = self.CreateRowUpdatingEvent(dataRow, dataCommand, statementType, tableMapping); // self try/catch for any exceptions during the parameter initialization try { dataRow.RowError = null; // MDAC 67185 if (null != dataCommand) { // prepare the parameters for the user who then can modify them during OnRowUpdating ParameterMethods.ParameterInput(self.UpdateMappingAction, self.UpdateSchemaAction, dataCommand.Parameters, statementType, dataRow, tableMapping); } } catch (Exception e) { // if (!ADP.IsCatchableExceptionType(e)) { throw; } rowUpdatingEvent.Errors = e; rowUpdatingEvent.Status = UpdateStatus.ErrorsOccurred; } self.OnRowUpdating(rowUpdatingEvent); // user may throw out of Update without completing batch if (rowUpdatingEvent.Command is DbCommand tmpCommand) { isCommandFromRowUpdating = (dataCommand != tmpCommand); dataCommand = tmpCommand; } // handle the status from RowUpdating event UpdateStatus rowUpdatingStatus = rowUpdatingEvent.Status; if (UpdateStatus.Continue != rowUpdatingStatus) { if (UpdateStatus.ErrorsOccurred == rowUpdatingStatus) { self.UpdatingRowStatusErrors(rowUpdatingEvent, dataRow); continue; // foreach DataRow } else if (UpdateStatus.SkipCurrentRow == rowUpdatingStatus) { if (DataRowState.Unchanged == dataRow.RowState) { // MDAC 66286 cumulativeDataRowsAffected++; } continue; // foreach DataRow } else if (UpdateStatus.SkipAllRemainingRows == rowUpdatingStatus) { if (DataRowState.Unchanged == dataRow.RowState) { // MDAC 66286 cumulativeDataRowsAffected++; } break; // execute existing batch and return } else { throw ADP.InvalidUpdateStatus(rowUpdatingStatus); // out of Update } } // else onward to Append/ExecuteNonQuery/ExecuteReader rowUpdatingEvent = null; RowUpdatedEventArgs rowUpdatedEvent = null; if (1 == maxBatchCommands) { if (null != dataCommand) { batchCommands[0].CommandIdentifier = 0; batchCommands[0].ParameterCount = dataCommand.Parameters.Count; batchCommands[0].StatementType = statementType; batchCommands[0].UpdatedRowSource = dataCommand.UpdatedRowSource; } batchCommands[0].Row = dataRow; rowBatch[0] = dataRow; // not doing a batch update, just simplifying code... commandCount = 1; } else { Exception errors = null; try { if (null != dataCommand) { if (0 == (UpdateRowSource.FirstReturnedRecord & dataCommand.UpdatedRowSource)) { // append the command to the commandset. If an exception // occurs, then the user must append and continue batchCommands[commandCount].CommandIdentifier = self.AddToBatch(dataCommand); batchCommands[commandCount].ParameterCount = dataCommand.Parameters.Count; batchCommands[commandCount].Row = dataRow; batchCommands[commandCount].StatementType = statementType; batchCommands[commandCount].UpdatedRowSource = dataCommand.UpdatedRowSource; rowBatch[commandCount] = dataRow; commandCount++; if (commandCount < maxBatchCommands) { continue; // foreach DataRow } // else onward execute the batch } else { // do not allow the expectation that returned results will be used errors = new InvalidOperationException("When batching, the command's UpdatedRowSource property value of UpdateRowSource.FirstReturnedRecord or UpdateRowSource.Both is invalid."); } } else { // null Command will force RowUpdatedEvent with ErrorsOccured without completing batch errors = ADP.UpdateRequiresCommand(statementType, isCommandFromRowUpdating); } } catch (Exception e) { // try/catch for RowUpdatedEventArgs // if (!ADP.IsCatchableExceptionType(e)) { throw; } errors = e; } if (null != errors) { rowUpdatedEvent = self.CreateRowUpdatedEvent(dataRow, dataCommand, StatementType.Batch, tableMapping); rowUpdatedEvent.Errors = errors; rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred; self.OnRowUpdated(rowUpdatedEvent); // user may throw out of Update if (errors != rowUpdatedEvent.Errors) { // user set the error msg and we will use it for (int i = 0; i < batchCommands.Length; ++i) { batchCommands[i].Errors = null; } } cumulativeDataRowsAffected += self.UpdatedRowStatus(rowUpdatedEvent, batchCommands, commandCount); if (UpdateStatus.SkipAllRemainingRows == rowUpdatedEvent.Status) { break; } continue; // foreach datarow } } rowUpdatedEvent = self.CreateRowUpdatedEvent(dataRow, dataCommand, statementType, tableMapping); // self try/catch for any exceptions during the execution, population, output parameters try { if (1 != maxBatchCommands) { DbConnection connection = self.GetConnection(); ConnectionState state = await self.UpdateConnectionOpenAsync(connection, StatementType.Batch, connections, connectionStates, useSelectConnectionState, cancellationToken).ConfigureAwait(false); rowUpdatedEvent.AdapterInit_(rowBatch); if (ConnectionState.Open == state) { await AsyncDataReaderBatchExecuteMethods.UpdateBatchExecuteAsync(self, batchCommands, commandCount, rowUpdatedEvent, cancellationToken).ConfigureAwait(false); } else { // null Connection will force RowUpdatedEvent with ErrorsOccured without completing batch rowUpdatedEvent.Errors = ADP.UpdateOpenConnectionRequired(StatementType.Batch, false, state); rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred; } } else if (null != dataCommand) { DbConnection connection = dataCommand.Connection ?? throw new InvalidOperationException("DbCommand.Connection is null."); ConnectionState state = await self.UpdateConnectionOpenAsync(connection, statementType, connections, connectionStates, useSelectConnectionState, cancellationToken).ConfigureAwait(false); if (ConnectionState.Open == state) { await self.UpdateRowExecuteAsync(rowUpdatedEvent, dataCommand, statementType, cancellationToken).ConfigureAwait(false); batchCommands[0].RecordsAffected = rowUpdatedEvent.RecordsAffected; batchCommands[0].Errors = null; } else { // null Connection will force RowUpdatedEvent with ErrorsOccured without completing batch rowUpdatedEvent.Errors = ADP.UpdateOpenConnectionRequired(statementType, isCommandFromRowUpdating, state); rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred; } } else { // null Command will force RowUpdatedEvent with ErrorsOccured without completing batch rowUpdatedEvent.Errors = ADP.UpdateRequiresCommand(statementType, isCommandFromRowUpdating); rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred; } } catch (Exception e) { // try/catch for RowUpdatedEventArgs // if (!ADP.IsCatchableExceptionType(e)) { throw; } rowUpdatedEvent.Errors = e; rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred; } bool clearBatchOnSkipAll = (UpdateStatus.ErrorsOccurred == rowUpdatedEvent.Status); { Exception errors = rowUpdatedEvent.Errors; self.OnRowUpdated(rowUpdatedEvent); // user may throw out of Update // NOTE: the contents of rowBatch are now tainted... if (errors != rowUpdatedEvent.Errors) { // user set the error msg and we will use it for (int i = 0; i < batchCommands.Length; ++i) { batchCommands[i].Errors = null; } } } cumulativeDataRowsAffected += self.UpdatedRowStatus(rowUpdatedEvent, batchCommands, commandCount); if (UpdateStatus.SkipAllRemainingRows == rowUpdatedEvent.Status) { if (clearBatchOnSkipAll && 1 != maxBatchCommands) { self.ClearBatch(); commandCount = 0; } break; // from update } if (1 != maxBatchCommands) { self.ClearBatch(); commandCount = 0; } for (int i = 0; i < batchCommands.Length; ++i) { batchCommands[i] = default; } commandCount = 0; } // foreach DataRow // must handle the last batch if (1 != maxBatchCommands && 0 < commandCount) { RowUpdatedEventArgs rowUpdatedEvent = self.CreateRowUpdatedEvent(null, dataCommand, statementType, tableMapping); try { DbConnection connection = self.GetConnection(); ConnectionState state = await self.UpdateConnectionOpenAsync(connection, StatementType.Batch, connections, connectionStates, useSelectConnectionState, cancellationToken).ConfigureAwait(false); DataRow[] finalRowBatch = rowBatch; if (commandCount < rowBatch.Length) { finalRowBatch = new DataRow[commandCount]; Array.Copy(rowBatch, finalRowBatch, commandCount); } rowUpdatedEvent.AdapterInit_(finalRowBatch); if (ConnectionState.Open == state) { await AsyncDataReaderBatchExecuteMethods.UpdateBatchExecuteAsync(self, batchCommands, commandCount, rowUpdatedEvent, cancellationToken); } else { // null Connection will force RowUpdatedEvent with ErrorsOccured without completing batch rowUpdatedEvent.Errors = ADP.UpdateOpenConnectionRequired(StatementType.Batch, false, state); rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred; } } catch (Exception e) { // try/catch for RowUpdatedEventArgs // if (!ADP.IsCatchableExceptionType(e)) { throw; } rowUpdatedEvent.Errors = e; rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred; } Exception errors = rowUpdatedEvent.Errors; self.OnRowUpdated(rowUpdatedEvent); // user may throw out of Update // NOTE: the contents of rowBatch are now tainted... if (errors != rowUpdatedEvent.Errors) { // user set the error msg and we will use it for (int i = 0; i < batchCommands.Length; ++i) { batchCommands[i].Errors = null; } } cumulativeDataRowsAffected += self.UpdatedRowStatus(rowUpdatedEvent, batchCommands, commandCount); } } finally { if (1 != maxBatchCommands) { self.TerminateBatching(); } } } finally { // try/finally for connection cleanup for (int i = 0; i < connections.Length; ++i) { QuietClose(connections[i], connectionStates[i]); } } return(cumulativeDataRowsAffected); }
public static void AfterUpdateBatchExecute(IBatchingAdapter adapter, BatchCommandInfo[] batchCommands, int commandCount, RowUpdatedEventArgs rowUpdatedEvent) { MissingMappingAction missingMapping = adapter.UpdateMappingAction; MissingSchemaAction missingSchema = adapter.UpdateSchemaAction; int checkRecordsAffected = 0; bool hasConcurrencyViolation = false; List <DataRow> rows = null; // walk through the batch to build the sum of recordsAffected // determine possible indivdual messages per datarow // determine possible concurrency violations per datarow // map output parameters to the datarow for (int bc = 0; bc < commandCount; ++bc) { BatchCommandInfo batchCommand = batchCommands[bc]; StatementType statementType = batchCommand.StatementType; // default implementation always returns 1, derived classes must override // otherwise DbConcurrencyException will only be thrown if sum of all records in batch is 0 if (adapter.GetBatchedRecordsAffected(batchCommand.CommandIdentifier, out int rowAffected, error: out batchCommands[bc].Errors)) { batchCommands[bc].RecordsAffected = rowAffected; } if ((null == batchCommands[bc].Errors) && batchCommands[bc].RecordsAffected.HasValue) { // determine possible concurrency violations per datarow if ((StatementType.Update == statementType) || (StatementType.Delete == statementType)) { checkRecordsAffected++; if (0 == rowAffected) { if (null == rows) { rows = new List <DataRow>(); } batchCommands[bc].Errors = ADP.UpdateConcurrencyViolation(batchCommands[bc].StatementType, 0, 1, new DataRow[] { rowUpdatedEvent.GetRow_(bc) }); hasConcurrencyViolation = true; rows.Add(rowUpdatedEvent.GetRow_(bc)); } } // map output parameters to the datarow if (((StatementType.Insert == statementType) || (StatementType.Update == statementType)) && (0 != (UpdateRowSource.OutputParameters & batchCommand.UpdatedRowSource)) && (0 != rowAffected)) // MDAC 71174 { if (StatementType.Insert == statementType) { // AcceptChanges for 'added' rows so backend generated keys that are returned // propagte into the datatable correctly. rowUpdatedEvent.GetRow_(bc).AcceptChanges(); } for (int i = 0; i < batchCommand.ParameterCount; ++i) { IDataParameter parameter = adapter.GetBatchedParameter(batchCommand.CommandIdentifier, i); ParameterMethods.ParameterOutput(parameter, batchCommand.Row, rowUpdatedEvent.TableMapping, missingMapping, missingSchema); } } } } if (null == rowUpdatedEvent.Errors) { // Only error if RecordsAffect == 0, not -1. A value of -1 means no count was received from server, // do not error in that situation (means 'set nocount on' was executed on server). if (UpdateStatus.Continue == rowUpdatedEvent.Status) { if ((0 < checkRecordsAffected) && ((0 == rowUpdatedEvent.RecordsAffected) || hasConcurrencyViolation)) { // bug50526, an exception if no records affected and attempted an Update/Delete Debug.Assert(null == rowUpdatedEvent.Errors, "Continue - but contains an exception"); DataRow[] rowsInError = (null != rows) ? rows.ToArray() : rowUpdatedEvent.GetRows_(); rowUpdatedEvent.Errors = ADP.UpdateConcurrencyViolation(StatementType.Batch, commandCount - rowsInError.Length, commandCount, rowsInError); // MDAC 55735 rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred; } } } }