0950ab8f2c365b83f352f27b7436a8f5a0423b9a
[mono.git] / mcs / class / referencesource / System.Data / System / Data / Common / DbDataAdapter.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="DbDataAdapter.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
8
9 namespace System.Data.Common {
10
11     using System;
12     using System.ComponentModel;
13     using System.Collections;
14     using System.Collections.Generic;
15     using System.Data;
16     using System.Data.ProviderBase;
17     using System.Diagnostics;
18     using System.Reflection;
19     using System.Threading;
20
21     public abstract class DbDataAdapter : DataAdapter, IDbDataAdapter, ICloneable { // V1.0.3300, MDAC 69629
22         public const string DefaultSourceTableName = "Table"; // V1.0.3300
23
24         internal static readonly object ParameterValueNonNullValue = 0;
25         internal static readonly object ParameterValueNullValue = 1;
26
27         private IDbCommand      _deleteCommand, _insertCommand, _selectCommand, _updateCommand;
28
29         private CommandBehavior _fillCommandBehavior;
30
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;
39         }
40
41         protected DbDataAdapter() : base() { // V1.0.3300
42         }
43
44         protected DbDataAdapter(DbDataAdapter adapter) : base(adapter) { // V1.0.5000
45             CloneFrom(adapter);
46         }
47
48         private IDbDataAdapter _IDbDataAdapter {
49             get {
50                 return (IDbDataAdapter)this;
51             }
52         }
53
54         [
55         Browsable(false),
56         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
57         ]
58         public DbCommand DeleteCommand { // V1.2.3300
59             get {
60                 return (DbCommand)(_IDbDataAdapter.DeleteCommand);
61             }
62             set {
63                 _IDbDataAdapter.DeleteCommand = value;
64             }
65         }
66
67         IDbCommand IDbDataAdapter.DeleteCommand { // V1.2.3300
68             get {
69                 return _deleteCommand;
70             }
71             set {
72                 _deleteCommand = value;
73             }
74         }
75
76         protected internal CommandBehavior FillCommandBehavior { // V1.2.3300, MDAC 87511
77             get {
78                 //Bid.Trace("<comm.DbDataAdapter.get_FillCommandBehavior|API> %d#\n", ObjectID);
79                 return (_fillCommandBehavior | CommandBehavior.SequentialAccess);
80             }
81             set {
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);
88             }
89         }
90
91         [
92         Browsable(false),
93         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
94         ]
95         public DbCommand InsertCommand { // V1.2.3300
96             get {
97                 return (DbCommand)(_IDbDataAdapter.InsertCommand);
98             }
99             set {
100                 _IDbDataAdapter.InsertCommand = value;
101             }
102         }
103
104         IDbCommand IDbDataAdapter.InsertCommand { // V1.2.3300
105             get {
106                 return _insertCommand;
107             }
108             set {
109                 _insertCommand = value;
110             }
111         }
112
113         [
114         Browsable(false),
115         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
116         ]
117         public DbCommand SelectCommand { // V1.2.3300
118             get {
119                 return (DbCommand)(_IDbDataAdapter.SelectCommand);
120             }
121             set {
122                 _IDbDataAdapter.SelectCommand = value;
123             }
124         }
125
126         IDbCommand IDbDataAdapter.SelectCommand { // V1.2.3300
127             get {
128                 return _selectCommand;
129             }
130             set {
131                 _selectCommand = value;
132             }
133         }
134
135         [
136         DefaultValue(1),
137         ResCategoryAttribute(Res.DataCategory_Update),
138         ResDescriptionAttribute(Res.DbDataAdapter_UpdateBatchSize),
139         ]
140         virtual public int UpdateBatchSize {
141             get {
142                 return 1;
143             }
144             set {
145                 if (1 != value) {
146                     throw ADP.NotSupported();
147                 }
148             }
149         }
150
151         [
152         Browsable(false),
153         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
154         ]
155         public DbCommand UpdateCommand { // V1.2.3300
156             get {
157                 return (DbCommand)(_IDbDataAdapter.UpdateCommand);
158             }
159             set {
160                 _IDbDataAdapter.UpdateCommand = value;
161             }
162         }
163
164         IDbCommand IDbDataAdapter.UpdateCommand { // V1.2.3300
165             get {
166                 return _updateCommand;
167             }
168             set {
169                 _updateCommand = value;
170             }
171         }
172
173         private System.Data.MissingMappingAction UpdateMappingAction {
174             get {
175                 if (System.Data.MissingMappingAction.Passthrough == MissingMappingAction) {
176                     return System.Data.MissingMappingAction.Passthrough;
177                 }
178                 return System.Data.MissingMappingAction.Error;
179             }
180         }
181
182         private System.Data.MissingSchemaAction UpdateSchemaAction {
183             get {
184                 System.Data.MissingSchemaAction action = MissingSchemaAction;
185                 if ((System.Data.MissingSchemaAction.Add == action) || (System.Data.MissingSchemaAction.AddWithKey == action)) {
186                     return System.Data.MissingSchemaAction.Ignore;
187                 }
188                 return System.Data.MissingSchemaAction.Error;
189             }
190         }
191
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.
197
198             throw ADP.NotSupported();
199         }
200
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.
204
205             throw ADP.NotSupported();
206         }
207
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);
213             return clone;
214         }
215
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);
222         }
223
224         private IDbCommand CloneCommand(IDbCommand command) {
225             return (IDbCommand) ((command is ICloneable) ? ((ICloneable) command).Clone() : null);
226         }
227
228         virtual protected RowUpdatedEventArgs CreateRowUpdatedEvent(DataRow dataRow, IDbCommand command, StatementType statementType, DataTableMapping tableMapping) { // V1.0.3300
229             return new RowUpdatedEventArgs(dataRow, command, statementType, tableMapping);
230         }
231
232         virtual protected RowUpdatingEventArgs CreateRowUpdatingEvent(DataRow dataRow, IDbCommand command, StatementType statementType, DataTableMapping tableMapping) { // V1.0.3300
233             return new RowUpdatingEventArgs(dataRow, command, statementType, tableMapping);
234         }
235
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;
243             }
244             // release unmanaged objects
245
246             base.Dispose(disposing); // notify base classes
247         }
248
249         protected virtual int ExecuteBatch() {
250             // Called to execute the batched update command, returns the number
251             // of rows affected, just as ExecuteNonQuery would.
252
253             throw ADP.NotSupported();
254         }
255
256         public DataTable FillSchema(DataTable dataTable, SchemaType schemaType) { // V1.0.3300
257             IntPtr hscp;
258             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.FillSchema|API> %d#, dataTable, schemaType=%d{ds.SchemaType}\n", ObjectID, (int)schemaType);
259             try {
260                 IDbCommand selectCmd = _IDbDataAdapter.SelectCommand;
261                 CommandBehavior cmdBehavior = FillCommandBehavior;
262                 return FillSchema(dataTable, schemaType, selectCmd, cmdBehavior); // MDAC 67666
263             }
264             finally {
265                 Bid.ScopeLeave(ref hscp);
266             }
267         }
268
269         override public DataTable[] FillSchema(DataSet dataSet, SchemaType schemaType) { // V1.0.3300
270             IntPtr hscp;
271             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.FillSchema|API> %d#, dataSet, schemaType=%d{ds.SchemaType}\n", ObjectID, (int)schemaType);
272             try {
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
276                 }
277                 CommandBehavior cmdBehavior = FillCommandBehavior;
278                 return FillSchema(dataSet, schemaType, command, DbDataAdapter.DefaultSourceTableName, cmdBehavior);
279             }
280             finally {
281                 Bid.ScopeLeave(ref hscp);
282             }
283         }
284
285         public DataTable[] FillSchema(DataSet dataSet, SchemaType schemaType, string srcTable) { // V1.0.3300
286             IntPtr hscp;
287             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.FillSchema|API> %d#, dataSet, schemaType=%d{ds.SchemaType}, srcTable=%ls%\n", ObjectID, (int)schemaType, srcTable);
288             try {
289                 IDbCommand selectCmd = _IDbDataAdapter.SelectCommand;
290                 CommandBehavior cmdBehavior = FillCommandBehavior;
291                 return FillSchema(dataSet, schemaType, selectCmd, srcTable, cmdBehavior);
292             }
293             finally {
294                 Bid.ScopeLeave(ref hscp);
295             }
296         }
297
298         virtual protected DataTable[] FillSchema(DataSet dataSet, SchemaType schemaType, IDbCommand command, string srcTable, CommandBehavior behavior) { // V1.0.3300
299             IntPtr hscp;
300             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.FillSchema|API> %d#, dataSet, schemaType, command, srcTable, behavior=%d{ds.CommandBehavior}\n", ObjectID, (int)behavior);
301             try {
302                 if (null == dataSet) {
303                     throw ADP.ArgumentNull("dataSet");
304                 }
305                 if ((SchemaType.Source != schemaType) && (SchemaType.Mapped != schemaType)) {
306                     throw ADP.InvalidSchemaType(schemaType);
307                 }
308                 if (ADP.IsEmpty(srcTable)) {
309                     throw ADP.FillSchemaRequiresSourceTableName("srcTable");
310                 }
311                 if (null == command) {
312                     throw ADP.MissingSelectCommand(ADP.FillSchema);
313                 }
314                 return (DataTable[]) FillSchemaInternal(dataSet, null, schemaType, command, srcTable, behavior);
315             } finally {
316                 Bid.ScopeLeave(ref hscp);
317             }
318         }
319
320         virtual protected DataTable FillSchema(DataTable dataTable, SchemaType schemaType, IDbCommand command, CommandBehavior behavior) { // V1.0.3300
321             IntPtr hscp;
322             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.FillSchema|API> %d#, dataTable, schemaType, command, behavior=%d{ds.CommandBehavior}\n", ObjectID, (int)behavior);
323             try {
324                 if (null == dataTable) {
325                     throw ADP.ArgumentNull("dataTable");
326                 }
327                 if ((SchemaType.Source != schemaType) && (SchemaType.Mapped != schemaType)) {
328                     throw ADP.InvalidSchemaType(schemaType);
329                 }
330                 if (null == command) {
331                     throw ADP.MissingSelectCommand(ADP.FillSchema);
332                 }
333                 string srcTableName = dataTable.TableName;
334                 int index = IndexOfDataSetTable(srcTableName);
335                 if (-1 != index) {
336                    srcTableName = TableMappings[index].SourceTable;
337                 }
338                 return (DataTable) FillSchemaInternal(null, dataTable, schemaType, command, srcTableName, behavior | CommandBehavior.SingleResult);
339             } finally {
340                 Bid.ScopeLeave(ref hscp);
341             }
342         }
343
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);
347             try {
348                 IDbConnection activeConnection = DbDataAdapter.GetConnection3(this, command, ADP.FillSchema);
349                 ConnectionState originalState = ConnectionState.Open;
350
351                 try {
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);
356                         }
357                         else {
358                             dataTables = FillSchema(dataset, schemaType, srcTable, dataReader);
359                         }
360                     }
361                 }
362                 finally {
363                     QuietClose(activeConnection, originalState);
364                 }
365             }
366             finally {
367                 if (restoreNullConnection) {
368                     command.Transaction = null;
369                     command.Connection = null;
370                 }
371             }
372             return dataTables;
373         }
374
375         override public int Fill(DataSet dataSet) { // V1.0.3300
376             IntPtr hscp;
377             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> %d#, dataSet\n", ObjectID);
378             try {
379                 // delegate to Fill4
380                 IDbCommand selectCmd = _IDbDataAdapter.SelectCommand;
381                 CommandBehavior cmdBehavior = FillCommandBehavior;
382                 return Fill(dataSet, 0, 0, DbDataAdapter.DefaultSourceTableName, selectCmd, cmdBehavior);
383             }
384             finally {
385                 Bid.ScopeLeave(ref hscp);
386             }
387         }
388
389         public int Fill(DataSet dataSet, string srcTable) { // V1.0.3300
390             IntPtr hscp;
391             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> %d#, dataSet, srcTable='%ls'\n", ObjectID, srcTable);
392             try {
393                 // delegate to Fill4
394                 IDbCommand selectCmd = _IDbDataAdapter.SelectCommand;
395                 CommandBehavior cmdBehavior = FillCommandBehavior;
396                 return Fill(dataSet, 0, 0, srcTable, selectCmd, cmdBehavior);
397             }
398             finally {
399                 Bid.ScopeLeave(ref hscp);
400             }
401         }
402
403         public int Fill(DataSet dataSet, int startRecord, int maxRecords, string srcTable) { // V1.0.3300
404             IntPtr hscp;
405             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> %d#, dataSet, startRecord=%d, maxRecords=%d, srcTable='%ls'\n", ObjectID, startRecord, maxRecords, srcTable);
406             try {
407                 // delegate to Fill4
408                 IDbCommand selectCmd = _IDbDataAdapter.SelectCommand;
409                 CommandBehavior cmdBehavior = FillCommandBehavior;
410                 return Fill(dataSet, startRecord, maxRecords, srcTable, selectCmd, cmdBehavior);
411             }
412             finally {
413                 Bid.ScopeLeave(ref hscp);
414             }
415         }
416
417         virtual protected int Fill(DataSet dataSet, int startRecord, int maxRecords, string srcTable, IDbCommand command, CommandBehavior behavior) { // V1.0.3300
418             IntPtr hscp;
419             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> %d#, dataSet, startRecord, maxRecords, srcTable, command, behavior=%d{ds.CommandBehavior}\n", ObjectID, (int)behavior);
420             try {
421                 if (null == dataSet) {
422                     throw ADP.FillRequires("dataSet");
423                 }
424                 if (startRecord < 0) {
425                     throw ADP.InvalidStartRecord("startRecord", startRecord);
426                 }
427                 if (maxRecords < 0) {
428                     throw ADP.InvalidMaxRecords("maxRecords", maxRecords);
429                 }
430                 if (ADP.IsEmpty(srcTable)) {
431                     throw ADP.FillRequiresSourceTableName("srcTable");
432                 }
433                 if (null == command) {
434                     throw ADP.MissingSelectCommand(ADP.Fill);
435                 }
436                 return FillInternal(dataSet, null, startRecord, maxRecords, srcTable, command, behavior);
437             }finally {
438
439             Bid.ScopeLeave(ref hscp);
440             }
441         }
442
443         public int Fill(DataTable dataTable) { // V1.0.3300
444             IntPtr hscp;
445             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> %d#, dataTable\n", ObjectID);
446             try {
447                 // delegate to Fill8
448                 DataTable[] dataTables = new DataTable[1] { dataTable};
449                 IDbCommand selectCmd = _IDbDataAdapter.SelectCommand;
450                 CommandBehavior cmdBehavior = FillCommandBehavior;
451                 return Fill(dataTables, 0, 0, selectCmd, cmdBehavior);
452             }
453             finally {
454                 Bid.ScopeLeave(ref hscp);
455             }
456         }
457
458         public int Fill(int startRecord, int maxRecords, params DataTable[] dataTables) { // V1.2.3300
459             IntPtr hscp;
460             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> %d#, startRecord=%d, maxRecords=%d, dataTable[]\n", ObjectID, startRecord, maxRecords);
461             try {
462                 // delegate to Fill8
463                 IDbCommand selectCmd = _IDbDataAdapter.SelectCommand;
464                 CommandBehavior cmdBehavior = FillCommandBehavior;
465                 return Fill(dataTables, startRecord, maxRecords, selectCmd, cmdBehavior);
466             }
467             finally {
468                 Bid.ScopeLeave(ref hscp);
469             }
470         }
471
472         virtual protected int Fill(DataTable dataTable, IDbCommand command, CommandBehavior behavior) { // V1.0.3300
473             IntPtr hscp;
474             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> dataTable, command, behavior=%d{ds.CommandBehavior}%d#\n", ObjectID, (int)behavior);
475             try {
476                 // delegate to Fill8
477                 DataTable[] dataTables = new DataTable[1] { dataTable};
478                 return Fill(dataTables, 0, 0, command, behavior);
479             }
480             finally {
481                 Bid.ScopeLeave(ref hscp);
482             }
483         }
484
485         virtual protected int Fill(DataTable[] dataTables, int startRecord, int maxRecords, IDbCommand command, CommandBehavior behavior) { // V1.2.3300
486             IntPtr hscp;
487             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Fill|API> %d#, dataTables[], startRecord, maxRecords, command, behavior=%d{ds.CommandBehavior}\n", ObjectID, (int)behavior);
488             try {
489                 if ((null == dataTables) || (0 == dataTables.Length) || (null == dataTables[0])) {
490                     throw ADP.FillRequires("dataTable");
491                 }
492                 if (startRecord < 0) {
493                     throw ADP.InvalidStartRecord("startRecord", startRecord);
494                 }
495                 if (maxRecords < 0) {
496                     throw ADP.InvalidMaxRecords("maxRecords", maxRecords);
497                 }
498                 if ((1 < dataTables.Length) && ((0 != startRecord) || (0 != maxRecords))) {
499                     throw ADP.OnlyOneTableForStartRecordOrMaxRecords();
500                 }
501                 if (null == command) {
502                     throw ADP.MissingSelectCommand(ADP.Fill);
503                 }
504                 if (1 == dataTables.Length) {
505                     behavior |= CommandBehavior.SingleResult;
506                 }
507                 return FillInternal(null, dataTables, startRecord, maxRecords, null, command, behavior);
508             }
509             finally {
510                 Bid.ScopeLeave(ref hscp);
511             }
512         }
513
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);
517             try {
518                 IDbConnection activeConnection = DbDataAdapter.GetConnection3(this, command, ADP.Fill);
519                 ConnectionState originalState = ConnectionState.Open;
520
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;
525                 }
526
527                 try {
528                     QuietOpen(activeConnection, out originalState);
529                     behavior |= CommandBehavior.SequentialAccess;
530
531                     IDataReader dataReader = null;
532                     try {
533                         dataReader = command.ExecuteReader(behavior);
534
535                         if (null != datatables) { // delegate to next set of protected Fill methods
536                             rowsAddedToDataSet = Fill(datatables, dataReader, startRecord, maxRecords);
537                         }
538                         else {
539                             rowsAddedToDataSet = Fill(dataset, srcTable, dataReader, startRecord, maxRecords);
540                         }
541                     }
542                     finally {
543                         if (null != dataReader) {
544                             dataReader.Dispose();
545                         }
546                     }
547                 }
548                 finally {
549                     QuietClose(activeConnection, originalState);
550                 }
551             }
552             finally {
553                 if (restoreNullConnection) {
554                     command.Transaction = null;
555                     command.Connection = null;
556                 }
557             }
558             return rowsAddedToDataSet;
559         }
560
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.
565
566             throw ADP.NotSupported();
567         }
568
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.
573
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
576
577             // return 0 to cause Update to throw DbConcurrencyException
578             recordsAffected = 1;
579             error = null;
580             return true;
581         }
582
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);
592                 }
593             }
594             if (null == value) {
595                 value = new IDataParameter[0];
596             }
597             return value;
598         }
599
600         internal DataTableMapping GetTableMapping(DataTable dataTable) {
601             DataTableMapping tableMapping = null;
602             int index = IndexOfDataSetTable(dataTable.TableName);
603             if (-1 != index) {
604                 tableMapping = TableMappings[index];
605             }
606             if (null == tableMapping) {
607                 if (System.Data.MissingMappingAction.Error == MissingMappingAction) {
608                     throw ADP.MissingTableMappingDestination(dataTable.TableName);
609                 }
610                 tableMapping = new DataTableMapping(dataTable.TableName, dataTable.TableName);
611             }
612             return tableMapping;
613         }
614
615         virtual protected void InitializeBatching() {
616             // Called when batch updates are requested to prepare for processing
617             // of a batch of commands.
618
619             throw ADP.NotSupported();
620         }
621
622         virtual protected void OnRowUpdated(RowUpdatedEventArgs value) { // V1.0.3300
623         }
624
625         virtual protected void OnRowUpdating(RowUpdatingEventArgs value) { // V1.0.3300
626         }
627
628         private void ParameterInput(IDataParameterCollection parameters, StatementType typeIndex, DataRow row, DataTableMapping mappings) {
629             Data.MissingMappingAction missingMapping = UpdateMappingAction;
630             Data.MissingSchemaAction missingSchema = UpdateSchemaAction;
631
632             foreach(IDataParameter parameter in parameters) {
633                 if ((null != parameter) && (0 != (ParameterDirection.Input & parameter.Direction))) {
634
635                     string columnName = parameter.SourceColumn;
636                     if (!ADP.IsEmpty(columnName)) {
637
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];
642                         }
643                         else {
644                             parameter.Value = null;
645                         }
646
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;
651                         }
652                     }
653                 }
654             }
655         }
656
657         private void ParameterOutput(IDataParameter parameter, DataRow row, DataTableMapping mappings, MissingMappingAction missingMapping, MissingSchemaAction missingSchema) {
658             if (0 != (ParameterDirection.Output & parameter.Direction)) {
659
660                 object value = parameter.Value;
661                 if (null != value) {
662                     // null means default, meaning we leave the current DataRow value alone
663                     string columnName = parameter.SourceColumn;
664                     if (!ADP.IsEmpty(columnName)) {
665
666                         DataColumn dataColumn = mappings.GetDataColumn(columnName, null, row.Table, missingMapping, missingSchema);
667                         if (null != dataColumn) {
668                             if (dataColumn.ReadOnly) {
669                                 try {
670                                     dataColumn.ReadOnly = false;
671                                     row[dataColumn] = value;
672                                 }
673                                 finally {
674                                     dataColumn.ReadOnly = true;
675                                 }
676                             }
677                             else {
678                                 row[dataColumn] = value;
679                             }
680                         }
681                     }
682                 }
683             }
684         }
685
686         private void ParameterOutput(IDataParameterCollection parameters, DataRow row, DataTableMapping mappings) {
687             Data.MissingMappingAction missingMapping = UpdateMappingAction;
688             Data.MissingSchemaAction missingSchema = UpdateSchemaAction;
689
690             foreach(IDataParameter parameter in parameters) {
691                 if (null != parameter) {
692                     ParameterOutput(parameter, row, mappings, missingMapping, missingSchema);
693                 }
694             }
695          }
696
697         virtual protected void TerminateBatching() {
698             // Called when batch updates are requested to cleanup after a batch
699             // update has been completed.
700
701             throw ADP.NotSupported();
702         }
703
704         override public int Update(DataSet dataSet) { // V1.0.3300
705             //if (!TableMappings.Contains(DbDataAdapter.DefaultSourceTableName)) { // MDAC 59268
706             //    throw ADP.UpdateRequiresSourceTable(DbDataAdapter.DefaultSourceTableName);
707             //}
708             return Update(dataSet, DbDataAdapter.DefaultSourceTableName);
709         }
710
711         public int Update(DataRow[] dataRows) { // V1.0.3300
712             IntPtr hscp;
713             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Update|API> %d#, dataRows[]\n", ObjectID);
714             try {
715                 int rowsAffected = 0;
716                 if (null == dataRows) {
717                     throw ADP.ArgumentNull("dataRows");
718                 }
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);
725                             }
726                             dataTable = dataRows[i].Table;
727                         }
728                     }
729                     if (null != dataTable) {
730                         DataTableMapping tableMapping = GetTableMapping(dataTable);
731                         rowsAffected = Update(dataRows, tableMapping);
732                     }
733                 }
734                 return rowsAffected;
735             }
736             finally {
737                 Bid.ScopeLeave(ref hscp);
738             }
739         }
740
741         public int Update(DataTable dataTable) { // V1.0.3300
742             IntPtr hscp;
743             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Update|API> %d#, dataTable", ObjectID);
744             try {
745                 if (null == dataTable) {
746                     throw ADP.UpdateRequiresDataTable("dataTable");
747                 }
748
749                 DataTableMapping tableMapping = null;
750                 int index = IndexOfDataSetTable(dataTable.TableName);
751                 if (-1 != index) {
752                     tableMapping = TableMappings[index];
753                 }
754                 if (null == tableMapping) {
755                     if (System.Data.MissingMappingAction.Error == MissingMappingAction) {
756                         throw ADP.MissingTableMappingDestination(dataTable.TableName);
757                     }
758                     tableMapping = new DataTableMapping(DbDataAdapter.DefaultSourceTableName, dataTable.TableName);
759                 }
760                 return UpdateFromDataTable(dataTable, tableMapping);
761             } finally {
762                 Bid.ScopeLeave(ref hscp);
763             }
764         }
765
766         public int Update(DataSet dataSet, string srcTable) { // V1.0.3300
767             IntPtr hscp;
768             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Update|API> %d#, dataSet, srcTable='%ls'", ObjectID, srcTable);
769             try {
770                 if (null == dataSet) {
771                     throw ADP.UpdateRequiresNonNullDataSet("dataSet");
772                 }
773                 if (ADP.IsEmpty(srcTable)) {
774                     throw ADP.UpdateRequiresSourceTableName("srcTable");
775                 }
776 #if DEBUG
777                 //ADP.TraceDataSet("Update <" + srcTable + ">", dataSet);
778 #endif
779
780                 int rowsAffected = 0;
781
782                 System.Data.MissingMappingAction missingMapping = UpdateMappingAction;
783                 DataTableMapping tableMapping = GetTableMappingBySchemaAction(srcTable, srcTable, UpdateMappingAction);
784                 Debug.Assert(null != tableMapping, "null TableMapping when MissingMappingAction.Error");
785
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);
792                 }
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
796                 }
797                 return rowsAffected;
798             }
799             finally {
800                 Bid.ScopeLeave(ref hscp);
801             }
802         }
803
804         virtual protected int Update(DataRow[] dataRows, DataTableMapping tableMapping) { // V1.0.3300
805             IntPtr hscp;
806             Bid.ScopeEnter(out hscp, "<comm.DbDataAdapter.Update|API> %d#, dataRows[], tableMapping", ObjectID);
807             try {
808                 Debug.Assert((null != dataRows) && (0 < dataRows.Length), "Update: bad dataRows");
809                 Debug.Assert(null != tableMapping, "Update: bad DataTableMapping");
810
811                 // If records were affected, increment row count by one - that is number of rows affected in dataset.
812                 int cumulativeDataRowsAffected = 0;
813
814                 IDbConnection[] connections = new IDbConnection[5]; // one for each statementtype
815                 ConnectionState[] connectionStates = new ConnectionState[5]; // closed by default (== 0)
816
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;
824                     }
825                 }
826
827                 int maxBatchCommands = Math.Min(UpdateBatchSize, dataRows.Length);
828
829                 if (maxBatchCommands < 1) {  // batch size of zero indicates one batch, no matter how large...
830                     maxBatchCommands = dataRows.Length;
831                 }
832
833                 BatchCommandInfo[] batchCommands = new BatchCommandInfo[maxBatchCommands];
834                 DataRow[] rowBatch = new DataRow[maxBatchCommands];
835                 int commandCount = 0;
836
837                 // the outer try/finally is for closing any connections we may have opened
838                 try {
839                     try {
840                         if (1 != maxBatchCommands) {
841                             InitializeBatching();
842                         }
843                         StatementType statementType = StatementType.Select;
844                         IDbCommand dataCommand = null;
845
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
850                             }
851                             bool isCommandFromRowUpdating = false;
852
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;
861                                 break;
862                             case DataRowState.Deleted:
863                                 statementType = StatementType.Delete;
864                                 dataCommand = _IDbDataAdapter.DeleteCommand;
865                                 break;
866                             case DataRowState.Modified:
867                                 statementType = StatementType.Update;
868                                 dataCommand = _IDbDataAdapter.UpdateCommand;
869                                 break;
870                             default:
871                                 Debug.Assert(false, "InvalidDataRowState");
872                                 throw ADP.InvalidDataRowState(dataRow.RowState); // out of Update without completing batch
873                             }
874
875                             // setup the event to be raised
876                             RowUpdatingEventArgs rowUpdatingEvent = CreateRowUpdatingEvent(dataRow, dataCommand, statementType, tableMapping);
877
878                             // this try/catch for any exceptions during the parameter initialization
879                             try {
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);
884                                 }
885                             }
886                             catch (Exception e) {
887                                 // 
888                                 if (!ADP.IsCatchableExceptionType(e)) {
889                                     throw;
890                                 }
891
892                                 ADP.TraceExceptionForCapture(e);
893
894                                 rowUpdatingEvent.Errors = e;
895                                 rowUpdatingEvent.Status = UpdateStatus.ErrorsOccurred;
896                             }
897
898                             OnRowUpdating(rowUpdatingEvent); // user may throw out of Update without completing batch
899
900                             IDbCommand tmpCommand = rowUpdatingEvent.Command;
901                             isCommandFromRowUpdating = (dataCommand != tmpCommand);
902                             dataCommand = tmpCommand;
903                             tmpCommand = null;
904
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
911                                 }
912                                 else if (UpdateStatus.SkipCurrentRow == rowUpdatingStatus) {
913                                     if (DataRowState.Unchanged == dataRow.RowState) { // MDAC 66286
914                                         cumulativeDataRowsAffected++;
915                                     }
916                                     continue; // foreach DataRow
917                                 }
918                                 else if (UpdateStatus.SkipAllRemainingRows == rowUpdatingStatus) {
919                                     if (DataRowState.Unchanged == dataRow.RowState) { // MDAC 66286
920                                         cumulativeDataRowsAffected++;
921                                     }
922                                     break; // execute existing batch and return
923                                 }
924                                 else {
925                                     throw ADP.InvalidUpdateStatus(rowUpdatingStatus);  // out of Update
926                                 }
927                             }
928                             // else onward to Append/ExecuteNonQuery/ExecuteReader
929
930                             rowUpdatingEvent = null;
931                             RowUpdatedEventArgs rowUpdatedEvent = null;
932
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;
939                                 }
940                                 batchCommands[0].Row = dataRow;
941                                 rowBatch[0] = dataRow; // not doing a batch update, just simplifying code...
942                                 commandCount = 1;
943                             }
944                             else {
945                                 Exception errors = null;
946
947                                 try {
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
952
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;
958
959                                             rowBatch[commandCount] = dataRow;
960                                             commandCount++;
961
962                                             if (commandCount < maxBatchCommands) {
963                                                 continue; // foreach DataRow
964                                             }
965                                             // else onward execute the batch
966                                          }
967                                         else {
968                                             // do not allow the expectation that returned results will be used
969                                             errors = ADP.ResultsNotAllowedDuringBatch();
970                                         }
971                                     }
972                                     else {
973                                         // null Command will force RowUpdatedEvent with ErrorsOccured without completing batch
974                                         errors = ADP.UpdateRequiresCommand(statementType, isCommandFromRowUpdating);
975                                     }
976                                 }
977                                 catch (Exception e) { // try/catch for RowUpdatedEventArgs
978                                     // 
979                                     if (!ADP.IsCatchableExceptionType(e)) {
980                                         throw;
981                                     }
982
983                                     ADP.TraceExceptionForCapture(e);
984                                     errors = e;
985                                 }
986
987                                 if (null != errors) {
988                                     rowUpdatedEvent = CreateRowUpdatedEvent(dataRow, dataCommand, StatementType.Batch, tableMapping);
989                                     rowUpdatedEvent.Errors = errors;
990                                     rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
991
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;
996                                         }
997                                     }
998
999                                     cumulativeDataRowsAffected += UpdatedRowStatus(rowUpdatedEvent, batchCommands, commandCount);
1000                                     if (UpdateStatus.SkipAllRemainingRows == rowUpdatedEvent.Status) {
1001                                         break;
1002                                     }
1003                                     continue; // foreach datarow
1004                                 }
1005                             }
1006
1007                             rowUpdatedEvent = CreateRowUpdatedEvent(dataRow, dataCommand, statementType, tableMapping);
1008
1009                             // this try/catch for any exceptions during the execution, population, output parameters
1010                             try {
1011                                 if (1 != maxBatchCommands) {
1012                                     IDbConnection connection = DbDataAdapter.GetConnection1(this);
1013
1014                                     ConnectionState state = UpdateConnectionOpen(connection, StatementType.Batch, connections, connectionStates, useSelectConnectionState);
1015                                     rowUpdatedEvent.AdapterInit(rowBatch);
1016
1017                                     if (ConnectionState.Open == state) {
1018                                         UpdateBatchExecute(batchCommands, commandCount, rowUpdatedEvent);
1019                                     }
1020                                     else {
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;
1024                                     }
1025                                 }
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;
1033                                     }
1034                                     else {
1035                                         // null Connection will force RowUpdatedEvent with ErrorsOccured without completing batch
1036                                         rowUpdatedEvent.Errors = ADP.UpdateOpenConnectionRequired(statementType, isCommandFromRowUpdating, state);
1037                                         rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
1038                                     }
1039                                 }
1040                                 else {
1041                                     // null Command will force RowUpdatedEvent with ErrorsOccured without completing batch
1042                                     rowUpdatedEvent.Errors = ADP.UpdateRequiresCommand(statementType, isCommandFromRowUpdating);
1043                                     rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
1044                                 }
1045                             }
1046                             catch (Exception e) { // try/catch for RowUpdatedEventArgs
1047                                 // 
1048                                 if (!ADP.IsCatchableExceptionType(e)) {
1049                                     throw;
1050                                 }
1051
1052                                 ADP.TraceExceptionForCapture(e);
1053
1054                                 rowUpdatedEvent.Errors = e;
1055                                 rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
1056                             }
1057                             bool clearBatchOnSkipAll = (UpdateStatus.ErrorsOccurred == rowUpdatedEvent.Status);
1058
1059                             {
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;
1066                                     }
1067                                 }
1068                             }
1069                             cumulativeDataRowsAffected += UpdatedRowStatus(rowUpdatedEvent, batchCommands, commandCount);
1070
1071                             if (UpdateStatus.SkipAllRemainingRows == rowUpdatedEvent.Status) {
1072                                 if (clearBatchOnSkipAll && 1 != maxBatchCommands) {
1073                                     ClearBatch();
1074                                     commandCount = 0;
1075                                 }
1076                                 break; // from update
1077                             }
1078
1079                             if (1 != maxBatchCommands) {
1080                                 ClearBatch();
1081                                 commandCount = 0;
1082                             }
1083                             for(int i = 0; i < batchCommands.Length; ++i) {
1084                                 batchCommands[i] = default(BatchCommandInfo);
1085                             }
1086                             commandCount = 0;
1087                         } // foreach DataRow
1088
1089                         // must handle the last batch
1090                         if (1 != maxBatchCommands && 0 < commandCount) {
1091                             RowUpdatedEventArgs rowUpdatedEvent = CreateRowUpdatedEvent(null, dataCommand, statementType, tableMapping);
1092
1093                             try {
1094                                 IDbConnection connection = DbDataAdapter.GetConnection1(this);
1095
1096                                 ConnectionState state = UpdateConnectionOpen(connection, StatementType.Batch, connections, connectionStates, useSelectConnectionState);
1097
1098                                 DataRow[] finalRowBatch = rowBatch;
1099
1100                                 if (commandCount < rowBatch.Length) {
1101                                     finalRowBatch = new DataRow[commandCount];
1102                                     Array.Copy(rowBatch, finalRowBatch, commandCount);
1103                                 }
1104                                 rowUpdatedEvent.AdapterInit(finalRowBatch);
1105
1106                                 if (ConnectionState.Open == state) {
1107                                     UpdateBatchExecute(batchCommands, commandCount, rowUpdatedEvent);
1108                                 }
1109                                 else {
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;
1113                                 }
1114                             }
1115                             catch (Exception e) { // try/catch for RowUpdatedEventArgs
1116                                 // 
1117                                 if (!ADP.IsCatchableExceptionType(e)) {
1118                                     throw;
1119                                 }
1120
1121                                 ADP.TraceExceptionForCapture(e);
1122
1123                                 rowUpdatedEvent.Errors = e;
1124                                 rowUpdatedEvent.Status = UpdateStatus.ErrorsOccurred;
1125                             }
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;
1132                                 }
1133                             }
1134
1135                             cumulativeDataRowsAffected += UpdatedRowStatus(rowUpdatedEvent, batchCommands, commandCount);
1136                         }
1137                     }
1138                     finally {
1139                         if (1 != maxBatchCommands) {
1140                             TerminateBatching();
1141                         }
1142                     }
1143                 }
1144                 finally { // try/finally for connection cleanup
1145                     for(int i = 0; i < connections.Length; ++i) {
1146                         QuietClose(connections[i], connectionStates[i]);
1147                     }
1148                 }
1149                 return cumulativeDataRowsAffected;
1150             }
1151             finally {
1152                 Bid.ScopeLeave(ref hscp);
1153             }
1154         }
1155
1156         private void UpdateBatchExecute(BatchCommandInfo[] batchCommands, int commandCount, RowUpdatedEventArgs rowUpdatedEvent) {
1157             try {
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);
1161             }
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;
1167             }
1168             Data.MissingMappingAction missingMapping = UpdateMappingAction;
1169             Data.MissingSchemaAction  missingSchema  = UpdateSchemaAction;
1170
1171             int checkRecordsAffected = 0;
1172             bool hasConcurrencyViolation = false;
1173             List<DataRow> rows = null;
1174
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;
1182
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
1185                 int rowAffected;
1186                 if (GetBatchedRecordsAffected(batchCommand.CommandIdentifier, out rowAffected, out batchCommands[bc].Errors)) {
1187                     batchCommands[bc].RecordsAffected = rowAffected;
1188                 }
1189
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) {
1195                             if (null == rows) {
1196                                 rows = new List<DataRow>();
1197                             }
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]);
1201                         }
1202                     }
1203
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
1207                     {
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();
1212                         }
1213
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);
1217                         }
1218                     }
1219                 }
1220             }
1221
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;
1232                     }
1233                 }
1234             }
1235         }
1236
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]);
1245
1246                 connections[index] = connection;
1247                 connectionStates[index] = ConnectionState.Closed; // required, open may throw
1248
1249                 QuietOpen(connection, out connectionStates[index]);
1250                 if (useSelectConnectionState && (connections[0] == connection)) {
1251                     connectionStates[index] = connections[0].State;
1252                 }
1253             }
1254             return connection.State;
1255         }
1256
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);
1262             }
1263             return rowsAffected;
1264         }
1265
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");
1270
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);
1276             }
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);
1281                     try {
1282                         bool getData = false;
1283                         do {
1284                             // advance to the first row returning result set
1285                             // determined by actually having columns in the result set
1286                             if (0 < readerHandler.FieldCount) {
1287                                 getData = true;
1288                                 break;
1289                             }
1290                         } while (dataReader.NextResult());
1291
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);
1294
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;
1300                                     }
1301                                     mapping.ApplyToDataRow(rowUpdatedEvent.Row);
1302                                 }
1303                             }
1304                         }
1305                     }
1306                     finally {
1307                         // using Close which can optimize its { while(dataReader.NextResult()); } loop
1308                         dataReader.Close();
1309
1310                         // RecordsAffected is available after Close, but don't trust it after Dispose
1311                         int recordsAffected = dataReader.RecordsAffected;
1312                         rowUpdatedEvent.AdapterInit(recordsAffected);
1313                     }
1314                 }
1315             }
1316             else {
1317                 // StatementType.Select, StatementType.Batch
1318                 Debug.Assert(false, "unexpected StatementType");
1319             }
1320
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
1324
1325                 if ((StatementType.Insert == cmdIndex) && insertAcceptChanges) { // MDAC 64199
1326                     rowUpdatedEvent.Row.AcceptChanges();
1327                 }
1328                 ParameterOutput(dataCommand.Parameters, rowUpdatedEvent.Row, rowUpdatedEvent.TableMapping);
1329             }
1330
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:
1335                 switch(cmdIndex) {
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;
1343                     }
1344                     break;
1345                 }
1346                 break;
1347             }
1348         }
1349
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)
1364             default:
1365                 throw ADP.InvalidUpdateStatus(rowUpdatedEvent.Status);
1366             } // switch RowUpdatedEventArgs.Status
1367             return cumulativeDataRowsAffected;
1368         }
1369
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
1377             //    AcceptChanges.
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
1381
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?");
1388                     if (acdu) {
1389                         if (0 != ((DataRowState.Added|DataRowState.Deleted|DataRowState.Modified)&row.RowState)) {
1390                             row.AcceptChanges();
1391                         }
1392                     }
1393                     cumulativeDataRowsAffected++;
1394                 }
1395             }
1396             return cumulativeDataRowsAffected;
1397         }
1398
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;
1406             }
1407
1408             int affected = 0;
1409             bool done = false;
1410             string message = errors.Message;
1411
1412             for (int i = 0; i < commandCount; i++) {
1413                 DataRow row = batchCommands[i].Row;
1414                 Debug.Assert(null != row, "null dataRow?");
1415
1416                 if (null != batchCommands[i].Errors) { // will exist if 0 == RecordsAffected
1417                     string rowMsg = batchCommands[i].Errors.Message;
1418                     if (String.IsNullOrEmpty(rowMsg)) {
1419                         rowMsg = message;
1420                     }
1421                     row.RowError += rowMsg;
1422                     done = true;
1423                 }
1424             }
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
1431                 }
1432             }
1433             else {
1434                 affected = UpdatedRowStatusContinue(rowUpdatedEvent, batchCommands, commandCount);
1435             }
1436             if (!ContinueUpdateOnError) { // MDAC 66900
1437                 throw errors; // out of Update
1438             }
1439             return affected; // return the count of successful rows within the batch failure
1440         }
1441
1442         private int UpdatedRowStatusSkip(BatchCommandInfo[] batchCommands, int commandCount){
1443             Debug.Assert(null != batchCommands, "null batchCommands?");
1444
1445             int cumulativeDataRowsAffected = 0;
1446
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
1452                 }
1453             }
1454             return cumulativeDataRowsAffected;
1455         }
1456
1457         private void UpdatingRowStatusErrors(RowUpdatingEventArgs rowUpdatedEvent, DataRow dataRow) {
1458             Debug.Assert(null != dataRow, "null dataRow");
1459             Exception errors = rowUpdatedEvent.Errors;
1460
1461             if (null == errors) {
1462                 // user changed status to ErrorsOccured without supplying an exception message
1463                 errors = ADP.RowUpdatingErrors();
1464                 rowUpdatedEvent.Errors = errors;
1465             }
1466             string message = errors.Message;
1467             dataRow.RowError += message; // MDAC 65808
1468
1469             if (!ContinueUpdateOnError) { // MDAC 66900
1470                 throw errors; // out of Update
1471             }
1472         }
1473
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;
1482                     }
1483                 }
1484             }
1485             IDbConnection connection = null;
1486             if (null != command) {
1487                 connection = command.Connection;
1488             }
1489             if (null == connection) {
1490                 throw ADP.UpdateConnectionRequired(StatementType.Batch, false);
1491             }
1492             return connection;
1493         }
1494
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);
1501             }
1502             return connection;
1503         }
1504
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);
1510             }
1511             return connection;
1512         }
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);
1521             default:
1522                 throw ADP.InvalidStatementType(statementType);
1523             }
1524         }
1525
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
1533                 connection.Close();
1534             }
1535         }
1536
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) {
1544                 connection.Open();
1545             }
1546         }
1547     }
1548 }