New test.
[mono.git] / mcs / class / System.Data / System.Data.Common / DbDataAdapter.cs
1 //
2 // System.Data.Common.DbDataAdapter.cs
3 //
4 // Author:
5 //   Rodrigo Moya (rodrigo@ximian.com)
6 //   Tim Coleman (tim@timcoleman.com)
7 //   Sureshkumar T <tsureshkumar@novell.com>
8 //
9 // (C) Ximian, Inc
10 // Copyright (C) Tim Coleman, 2002-2003
11 //
12
13 //
14 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
15 //
16 // Permission is hereby granted, free of charge, to any person obtaining
17 // a copy of this software and associated documentation files (the
18 // "Software"), to deal in the Software without restriction, including
19 // without limitation the rights to use, copy, modify, merge, publish,
20 // distribute, sublicense, and/or sell copies of the Software, and to
21 // permit persons to whom the Software is furnished to do so, subject to
22 // the following conditions:
23 // 
24 // The above copyright notice and this permission notice shall be
25 // included in all copies or substantial portions of the Software.
26 // 
27 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 //
35
36 using System;
37 using System.Collections;
38 using System.ComponentModel;
39 using System.Data;
40 using System.Runtime.InteropServices;
41
42 namespace System.Data.Common {
43 #if NET_2_0
44         public abstract class DbDataAdapter : DataAdapter, IDbDataAdapter, IDataAdapter, ICloneable
45 #else
46         public abstract class DbDataAdapter : DataAdapter, ICloneable
47 #endif
48         {
49                 #region Fields
50
51                 public const string DefaultSourceTableName = "Table";
52                 const string DefaultSourceColumnName = "Column";
53
54 #if NET_2_0
55                 IDbCommand _selectCommand;
56                 internal IDbCommand _updateCommand;
57                 internal IDbCommand _deleteCommand;
58                 internal IDbCommand _insertCommand;
59 #endif
60
61                 #endregion // Fields
62                 
63                 #region Constructors
64
65                 protected DbDataAdapter() 
66                 {
67                 }
68
69                 protected DbDataAdapter(DbDataAdapter adapter) : base(adapter)
70                 {
71                 }
72
73                 #endregion // Fields
74
75                 #region Properties
76
77 #if NET_2_0
78                 protected internal CommandBehavior FillCommandBehavior {
79                         get { throw new NotImplementedException (); }
80                         set { throw new NotImplementedException (); }
81                 }
82
83                 IDbCommand IDbDataAdapter.SelectCommand {
84                         get { return _selectCommand; }
85                         set { _selectCommand = value; }
86                 }
87
88                 IDbCommand IDbDataAdapter.UpdateCommand{
89                         get { return _updateCommand; }
90                         set { _updateCommand = value; }
91                 }
92
93                 IDbCommand IDbDataAdapter.DeleteCommand{
94                         get { return _deleteCommand; }
95                         set { _deleteCommand = value; }
96                 }
97
98                 IDbCommand IDbDataAdapter.InsertCommand{
99                         get { return _insertCommand; }
100                         set { _insertCommand = value; }
101                 }
102                 
103                 [Browsable (false)]
104                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
105                 public DbCommand SelectCommand {
106                         get { return (DbCommand) ((IDbDataAdapter) this).SelectCommand; }
107                         set { ((IDbDataAdapter) this).SelectCommand = value; }
108                 }
109
110                 [Browsable (false)]
111                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
112                 public DbCommand DeleteCommand {
113                         get { return (DbCommand) ((IDbDataAdapter) this).DeleteCommand; }
114                         set { ((IDbDataAdapter) this).DeleteCommand = value; }
115                 }
116
117                 [Browsable (false)]
118                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
119                 public DbCommand InsertCommand {
120                         get { return (DbCommand) ((IDbDataAdapter) this).InsertCommand; }
121                         set { ((IDbDataAdapter) this).InsertCommand = value; }
122                 }
123
124                 [Browsable (false)]
125                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
126                 public DbCommand UpdateCommand {
127                         get { return (DbCommand) ((IDbDataAdapter) this).UpdateCommand; }
128                         set { ((IDbDataAdapter) this).UpdateCommand = value; }
129                 }
130
131                 [MonoTODO]
132                 [DefaultValue (1)]
133                 public virtual int UpdateBatchSize {
134                         get { return 1; }
135                         set { throw new NotSupportedException (); }
136                 }
137 #else
138                 IDbCommand SelectCommand {
139                         get { return ((IDbDataAdapter) this).SelectCommand; }
140                 }
141
142                 IDbCommand UpdateCommand {
143                         get { return ((IDbDataAdapter) this).UpdateCommand; }
144                 }
145
146                 IDbCommand DeleteCommand {
147                         get { return ((IDbDataAdapter) this).DeleteCommand; }
148                 }
149
150                 IDbCommand InsertCommand {
151                         get { return ((IDbDataAdapter) this).InsertCommand; }
152                 } 
153 #endif
154
155                 #endregion // Properties
156                 
157                 #region Events
158
159 #if ONLY_1_0 || ONLY_1_1
160
161                 [DataCategory ("Fill")]
162                 [DataSysDescription ("Event triggered when a recoverable error occurs during Fill.")]
163                 public event FillErrorEventHandler FillError;
164
165 #endif
166                 #endregion // Events
167
168                 #region Methods
169
170 #if NET_2_0
171                 protected virtual RowUpdatedEventArgs CreateRowUpdatedEvent (DataRow dataRow, IDbCommand command,
172                                                                              StatementType statementType,
173                                                                              DataTableMapping tableMapping)
174                 {
175                         return new RowUpdatedEventArgs (dataRow, command, statementType, tableMapping);
176                 }
177
178                 protected virtual RowUpdatingEventArgs CreateRowUpdatingEvent (DataRow dataRow, IDbCommand command,
179                                                                                StatementType statementType,
180                                                                                DataTableMapping tableMapping)
181                 {
182                         return new RowUpdatingEventArgs (dataRow, command, statementType, tableMapping);
183                 }
184
185                 [MonoTODO]
186                 protected virtual void OnRowUpdated (RowUpdatedEventArgs value)
187                 {
188                         throw new NotImplementedException ();
189                 }
190
191                 [MonoTODO]
192                 protected virtual void OnRowUpdating (RowUpdatingEventArgs value)
193                 {
194                         throw new NotImplementedException ();
195                 }
196 #else
197                 protected abstract RowUpdatedEventArgs CreateRowUpdatedEvent (DataRow dataRow, IDbCommand command,
198                                                                              StatementType statementType,
199                                                                              DataTableMapping tableMapping);
200
201                 protected abstract RowUpdatingEventArgs CreateRowUpdatingEvent (DataRow dataRow, IDbCommand command,
202                                                                                StatementType statementType,
203                                                                                DataTableMapping tableMapping);
204
205                 protected abstract void OnRowUpdated (RowUpdatedEventArgs value);
206                 protected abstract void OnRowUpdating (RowUpdatingEventArgs value);
207 #endif
208
209
210                 private FillErrorEventArgs CreateFillErrorEvent (DataTable dataTable, object[] values, Exception e)
211                 {
212                         FillErrorEventArgs args = new FillErrorEventArgs (dataTable, values);
213                         args.Errors = e;
214                         args.Continue = false;
215                         return args;
216                 }
217
218                 protected override void Dispose (bool disposing)
219                 {
220                         if (disposing) {
221                                 IDbDataAdapter da = (IDbDataAdapter) this;
222                                 if (da.SelectCommand != null) {
223                                         da.SelectCommand.Dispose();
224                                         da.SelectCommand = null;
225                                 }
226                                 if (da.InsertCommand != null) {
227                                         da.InsertCommand.Dispose();
228                                         da.InsertCommand = null;
229                                 }
230                                 if (da.UpdateCommand != null) {
231                                         da.UpdateCommand.Dispose();
232                                         da.UpdateCommand = null;
233                                 }
234                                 if (da.DeleteCommand != null) {
235                                         da.DeleteCommand.Dispose();
236                                         da.DeleteCommand = null;
237                                 }
238                         }
239                 }
240
241                 public override int Fill (DataSet dataSet)
242                 {
243                         return Fill (dataSet, 0, 0, DefaultSourceTableName, ((IDbDataAdapter) this).SelectCommand, CommandBehavior.Default);
244                 }
245
246                 public int Fill (DataTable dataTable) 
247                 {
248                         if (dataTable == null)
249                                 throw new ArgumentNullException ("DataTable");
250
251                         return Fill (dataTable, ((IDbDataAdapter) this).SelectCommand, CommandBehavior.Default);
252                 }
253
254                 public int Fill (DataSet dataSet, string srcTable) 
255                 {
256                         return Fill (dataSet, 0, 0, srcTable, ((IDbDataAdapter) this).SelectCommand, CommandBehavior.Default);
257                 }
258
259 #if NET_2_0
260                 [MonoTODO ("This needs to be moved to DataAdapter.For now, just override")]
261                 protected override
262 #else
263                 protected virtual 
264 #endif
265                 int Fill (DataTable dataTable, IDataReader dataReader) 
266                 {
267                         if (dataReader.FieldCount == 0) {
268                                 dataReader.Close ();
269                                 return 0;
270                         }
271                         
272                         int count = 0;
273
274                         try {
275                                 string tableName = SetupSchema (SchemaType.Mapped, dataTable.TableName);
276                                 if (tableName != null) {
277                                         dataTable.TableName = tableName;
278                                         FillTable (dataTable, dataReader, 0, 0, ref count);
279                                 }
280                         } finally {
281                                 dataReader.Close ();
282                         }
283
284                         return count;
285                 }
286
287                 protected virtual int Fill (DataTable dataTable, IDbCommand command, CommandBehavior behavior) 
288                 {
289                         CommandBehavior commandBehavior = behavior;
290
291                         // first see that the connection is not close.
292                         if (command.Connection.State == ConnectionState.Closed) 
293                         {
294                                 command.Connection.Open ();
295                                 commandBehavior |= CommandBehavior.CloseConnection;
296                         }
297                         return Fill (dataTable, command.ExecuteReader (commandBehavior));
298                 }
299
300                 public int Fill (DataSet dataSet, int startRecord, int maxRecords, string srcTable) 
301                 {
302                         return this.Fill (dataSet, startRecord, maxRecords, srcTable, ((IDbDataAdapter) this).SelectCommand, CommandBehavior.Default);
303                 }
304
305 #if NET_2_0
306                 [MonoTODO]
307                 public int Fill (int startRecord, int maxRecords, DataTable[] dataTables)
308                 {
309                         throw new NotImplementedException ();
310                 }
311
312                 [MonoTODO]
313                 protected virtual int Fill (DataTable[] dataTables, int startRecord, int maxRecords, IDbCommand command, CommandBehavior behavior)
314                 {
315                         throw new NotImplementedException ();
316                 }
317 #endif
318                 
319                 
320 #if NET_2_0
321                 [MonoTODO ("This needs to be moved to DataAdapter.For now, just override")]
322                 protected override
323 #else
324                 protected virtual
325 #endif
326                 int Fill (DataSet dataSet, string srcTable, IDataReader dataReader, int startRecord, int maxRecords) 
327                 {
328                         if (dataSet == null)
329                                 throw new ArgumentNullException ("DataSet");
330
331                         if (startRecord < 0)
332                                 throw new ArgumentException ("The startRecord parameter was less than 0.");
333                         if (maxRecords < 0)
334                                 throw new ArgumentException ("The maxRecords parameter was less than 0.");
335
336                         DataTable dataTable = null;
337                         int resultIndex = 0;
338                         int count = 0;
339                         
340                         try {
341                                 string tableName = srcTable;
342                                 do {
343                                         // Non-resultset queries like insert, delete or update aren't processed.
344                                         if (dataReader.FieldCount != -1)
345                                         {
346                                                 tableName = SetupSchema (SchemaType.Mapped, tableName);
347                                                 if (tableName != null) {
348                                                         
349                                                         // check if the table exists in the dataset
350                                                         if (dataSet.Tables.Contains (tableName)) 
351                                                                 // get the table from the dataset
352                                                                 dataTable = dataSet.Tables [tableName];
353                                                         else {
354                                                                 // Do not create schema if MissingSchemAction is set to Ignore
355                                                                 if (this.MissingSchemaAction == MissingSchemaAction.Ignore)
356                                                                         continue;
357                                                                 dataTable = dataSet.Tables.Add (tableName);
358                                                         }
359         
360                                                         if (!FillTable (dataTable, dataReader, startRecord, maxRecords, ref count)) {
361                                                                 continue;
362                                                         }
363         
364                                                         tableName = String.Format ("{0}{1}", srcTable, ++resultIndex);
365         
366                                                         startRecord = 0;
367                                                         maxRecords = 0;
368                                                 }
369                                         }
370                                 } while (dataReader.NextResult ());
371                         } 
372                         finally {
373                                 dataReader.Close ();
374                         }
375
376                         return count;
377                 }
378                 
379                 protected virtual int Fill (DataSet dataSet, int startRecord, int maxRecords, string srcTable, IDbCommand command, CommandBehavior behavior) 
380                 {
381                         if (MissingSchemaAction == MissingSchemaAction.AddWithKey)
382                                 behavior |= CommandBehavior.KeyInfo;
383                         CommandBehavior commandBehavior = behavior;
384
385                         if (command.Connection.State == ConnectionState.Closed) {
386                                 command.Connection.Open ();
387                                 commandBehavior |= CommandBehavior.CloseConnection;
388                         }
389                         return Fill (dataSet, srcTable, command.ExecuteReader (commandBehavior), startRecord, maxRecords);
390                 }
391
392                 private bool FillTable (DataTable dataTable, IDataReader dataReader, int startRecord, int maxRecords, ref int counter)
393                 {
394                         if (dataReader.FieldCount == 0)
395                                 return false;
396
397                         int counterStart = counter;
398
399                         int[] mapping = BuildSchema (dataReader, dataTable, SchemaType.Mapped);
400                         
401                         int[] sortedMapping = new int[mapping.Length];
402                         int length = sortedMapping.Length;
403                         for(int i=0; i < sortedMapping.Length; i++) {
404                                 if (mapping[i] >= 0)
405                                         sortedMapping[mapping[i]] = i;
406                                 else
407                                         sortedMapping[--length] = i;
408                         }
409
410                         for (int i = 0; i < startRecord; i++) {
411                                 dataReader.Read ();
412                         }
413
414                         dataTable.BeginLoadData ();
415                         while (dataReader.Read () && (maxRecords == 0 || (counter - counterStart) < maxRecords)) {
416                                 try {
417                                         dataTable.LoadDataRow (dataReader, sortedMapping, length, AcceptChangesDuringFill);
418                                         counter++;
419                                 }
420                                 catch (Exception e) {
421                                         object[] readerArray = new object[dataReader.FieldCount];
422                                         object[] tableArray = new object[mapping.Length];
423                                         // we get the values from the datareader
424                                         dataReader.GetValues (readerArray);
425                                         // copy from datareader columns to table columns according to given mapping
426                                         for (int i = 0; i < mapping.Length; i++) {
427                                                 if (mapping[i] >= 0) {
428                                                         tableArray[i] = readerArray[mapping[i]];
429                                                 }
430                                         }
431                                         FillErrorEventArgs args = CreateFillErrorEvent (dataTable, tableArray, e);
432                                         OnFillError (args);
433
434                                         // if args.Continue is not set to true or if a handler is not set, rethrow the error..
435                                         if(!args.Continue)
436                                                 throw e;
437                                 }
438                         }
439                         dataTable.EndLoadData ();
440                         return true;
441                 }
442
443 #if NET_2_0
444                 /// <summary>
445                 ///     Fills the given datatable using values from reader. if a value 
446                 ///     for a column is  null, that will be filled with default value. 
447                 /// </summary>
448                 /// <returns>No. of rows affected </returns>
449                 internal static int FillFromReader (DataTable table,
450                                                     IDataReader reader,
451                                                     int start, 
452                                                     int length,
453                                                     int [] mapping,
454                                                     LoadOption loadOption
455                                                     )
456                 {
457                         if (reader.FieldCount == 0)
458                                 return 0 ;
459
460                         for (int i = 0; i < start; i++)
461                                 reader.Read ();
462
463                         int counter = 0;
464                         object [] values = new object [mapping.Length];
465                         while (reader.Read () &&
466                                (length == 0 || counter < length)) {
467                                 
468                                 for (int i = 0 ; i < mapping.Length; i++)
469                                         values [i] = mapping [i] < 0 ? null : reader [mapping [i]];
470                                         
471                                 table.BeginLoadData ();
472                                 table.LoadDataRow (values, loadOption);
473                                 table.EndLoadData ();
474                                 counter++;
475                         }
476                         return counter;
477                 }
478
479                 internal static int FillFromReader (DataTable table,
480                                                     IDataReader reader,
481                                                     int start, 
482                                                     int length,
483                                                     int [] mapping,
484                                                     LoadOption loadOption,
485                                                     FillErrorEventHandler errorHandler)
486                 {
487                         if (reader.FieldCount == 0)
488                                 return 0 ;
489
490                         for (int i = 0; i < start; i++)
491                                 reader.Read ();
492
493                         int counter = 0;
494                         object [] values = new object [mapping.Length];
495                         while (reader.Read () &&
496                                (length == 0 || counter < length)) {
497                                 
498                                 for (int i = 0 ; i < mapping.Length; i++)
499                                         values [i] = mapping [i] < 0 ? null : reader [mapping [i]];
500                                         
501                                 table.BeginLoadData ();
502                                 try {
503                                         table.LoadDataRow (values, loadOption);
504                                 } catch (Exception e) {
505                                         FillErrorEventArgs args = new FillErrorEventArgs (table, values);
506                                         args.Errors = e;
507                                         args.Continue = false;
508                                         errorHandler (table, args);
509                                         // if args.Continue is not set to true or if a handler is not set, rethrow the error..
510                                         if(!args.Continue)
511                                                 throw e;
512                                 }
513                                 table.EndLoadData ();
514                                 counter++;
515                         }
516                         return counter;
517                 }
518
519 #endif // NET_2_0
520
521                 public override DataTable[] FillSchema (DataSet dataSet, SchemaType schemaType) 
522                 {
523                         return FillSchema (dataSet, schemaType, ((IDbDataAdapter) this).SelectCommand, DefaultSourceTableName, CommandBehavior.Default);
524                 }
525
526                 public DataTable FillSchema (DataTable dataTable, SchemaType schemaType) 
527                 {
528                         return FillSchema (dataTable, schemaType, ((IDbDataAdapter) this).SelectCommand, CommandBehavior.Default);
529                 }
530
531                 public DataTable[] FillSchema (DataSet dataSet, SchemaType schemaType, string srcTable) 
532                 {
533                         return FillSchema (dataSet, schemaType, ((IDbDataAdapter) this).SelectCommand, srcTable, CommandBehavior.Default);
534                 }
535
536                 protected virtual DataTable FillSchema (DataTable dataTable, SchemaType schemaType, IDbCommand command, CommandBehavior behavior) 
537                 {
538                         if (dataTable == null)
539                                 throw new ArgumentNullException ("DataTable");
540
541                         behavior |= CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo;
542                         if (command.Connection.State == ConnectionState.Closed) {
543                                 command.Connection.Open ();
544                                 behavior |= CommandBehavior.CloseConnection;
545                         }
546
547                         IDataReader reader = command.ExecuteReader (behavior);
548                         try
549                         {
550                                 string tableName =  SetupSchema (schemaType, dataTable.TableName);
551                                 if (tableName != null)
552                                 {
553                                         // FillSchema should add the KeyInfo unless MissingSchemaAction
554                                         // is set to Ignore or Error.
555                                         MissingSchemaAction schemaAction = MissingSchemaAction;
556                                         if (!(schemaAction == MissingSchemaAction.Ignore ||
557                                                 schemaAction == MissingSchemaAction.Error))
558                                                 schemaAction = MissingSchemaAction.AddWithKey;
559
560                                         BuildSchema (reader, dataTable, schemaType, schemaAction,
561                                                 MissingMappingAction, TableMappings);
562                                 }
563                         }
564                         finally
565                         {
566                                 reader.Close ();
567                         }
568                         return dataTable;
569                 }
570
571                 protected virtual DataTable[] FillSchema (DataSet dataSet, SchemaType schemaType, IDbCommand command, string srcTable, CommandBehavior behavior) 
572                 {
573                         if (dataSet == null)
574                                 throw new ArgumentNullException ("DataSet");
575
576                         behavior |= CommandBehavior.SchemaOnly | CommandBehavior.KeyInfo;
577                         if (command.Connection.State == ConnectionState.Closed) {
578                                 command.Connection.Open ();
579                                 behavior |= CommandBehavior.CloseConnection;
580                         }
581
582                         IDataReader reader = command.ExecuteReader (behavior);
583                         ArrayList output = new ArrayList ();
584                         string tableName = srcTable;
585                         int index = 0;
586                         DataTable table;
587                         try
588                         {
589                                 // FillSchema should add the KeyInfo unless MissingSchemaAction
590                                 // is set to Ignore or Error.
591                                 MissingSchemaAction schemaAction = MissingSchemaAction;
592                                 if (!(MissingSchemaAction == MissingSchemaAction.Ignore ||
593                                         MissingSchemaAction == MissingSchemaAction.Error))
594                                         schemaAction = MissingSchemaAction.AddWithKey;
595
596                                 do {
597                                         tableName = SetupSchema (schemaType, tableName);
598                                         if (tableName != null)
599                                         {
600                                                 if (dataSet.Tables.Contains (tableName))
601                                                         table = dataSet.Tables [tableName];     
602                                                 else
603                                                 {
604                                                         // Do not create schema if MissingSchemAction is set to Ignore
605                                                         if (this.MissingSchemaAction == MissingSchemaAction.Ignore)
606                                                                 continue;
607                                                         table =  dataSet.Tables.Add (tableName);
608                                                 }
609                                                 
610                                                 BuildSchema (reader, table, schemaType, schemaAction,
611                                                         MissingMappingAction, TableMappings);
612                                                 output.Add (table);
613                                                 tableName = String.Format ("{0}{1}", srcTable, ++index);
614                                         }
615                                 }while (reader.NextResult ());
616                         }
617                         finally
618                         {
619                                 reader.Close ();
620                         }
621                         return (DataTable[]) output.ToArray (typeof (DataTable));
622                 }
623
624                 private string SetupSchema (SchemaType schemaType, string sourceTableName)
625                 {
626                         DataTableMapping tableMapping = null;
627
628                         if (schemaType == SchemaType.Mapped) 
629                         {
630                                 tableMapping = DataTableMappingCollection.GetTableMappingBySchemaAction (TableMappings, sourceTableName, sourceTableName, MissingMappingAction);
631                                 if (tableMapping != null)
632                                         return tableMapping.DataSetTable;
633                                 return null;
634                         }
635                         else
636                                 return sourceTableName;
637                 }
638
639                 [EditorBrowsable (EditorBrowsableState.Advanced)]
640                 public override IDataParameter[] GetFillParameters () 
641                 {
642                         IDataParameter[] parameters = new IDataParameter [SelectCommand.Parameters.Count];
643                         SelectCommand.Parameters.CopyTo (parameters, 0);
644                         return parameters;
645                 }
646                 
647                 // this method builds the schema for a given datatable. it returns a int array with 
648                 // "array[ordinal of datatable column] == index of source column in data reader".
649                 // each column in the datatable has a mapping to a specific column in the datareader,
650                 // the int array represents this match.
651                 private int[] BuildSchema (IDataReader reader, DataTable table, SchemaType schemaType)
652                 {
653                         return BuildSchema (reader, table, schemaType, MissingSchemaAction,
654                                             MissingMappingAction, TableMappings);
655                 }
656
657                 /// <summary>
658                 ///     Creates or Modifies the schema of the given DataTable based on the schema of
659                 ///     the reader and the arguments passed.
660                 /// </summary>
661                 internal static int[] BuildSchema (IDataReader reader,
662                                                    DataTable table,
663                                                    SchemaType schemaType,
664                                                    MissingSchemaAction missingSchAction,
665                                                    MissingMappingAction missingMapAction,
666                                                    DataTableMappingCollection dtMapping
667                                                    )
668                 {
669                         int readerIndex = 0;
670                         // FIXME : this fails if query has fewer columns than a table
671                         int[] mapping = new int[table.Columns.Count]; // mapping the reader indexes to the datatable indexes
672                         
673                         for(int i=0; i < mapping.Length; i++) {
674                                 mapping[i] = -1;
675                         }
676                         
677                         ArrayList primaryKey = new ArrayList ();
678                         ArrayList sourceColumns = new ArrayList ();
679                         bool createPrimaryKey = true;
680                         
681                         DataTable schemaTable = reader.GetSchemaTable ();
682
683                         DataColumn ColumnNameCol =  schemaTable.Columns["ColumnName"];
684                         DataColumn DataTypeCol = schemaTable.Columns["DataType"];
685                         DataColumn IsAutoIncrementCol = schemaTable.Columns["IsAutoIncrement"];
686                         DataColumn AllowDBNullCol = schemaTable.Columns["AllowDBNull"];
687                         DataColumn IsReadOnlyCol = schemaTable.Columns["IsReadOnly"];
688                         DataColumn IsKeyCol = schemaTable.Columns["IsKey"];
689                         DataColumn IsUniqueCol = schemaTable.Columns["IsUnique"];
690                         DataColumn ColumnSizeCol = schemaTable.Columns["ColumnSize"];
691
692                         foreach (DataRow schemaRow in schemaTable.Rows) {
693                                 // generate a unique column name in the source table.
694                                 string sourceColumnName;
695                                 string realSourceColumnName ;
696                                 if (ColumnNameCol == null || schemaRow.IsNull(ColumnNameCol) || (string)schemaRow [ColumnNameCol] == String.Empty) {
697                                         sourceColumnName = DefaultSourceColumnName;
698                                         realSourceColumnName = DefaultSourceColumnName + "1";
699                                 }
700                                 else {
701                                         sourceColumnName = (string) schemaRow [ColumnNameCol];
702                                         realSourceColumnName = sourceColumnName;
703                                 }
704
705                                 for (int i = 1; sourceColumns.Contains (realSourceColumnName); i += 1) 
706                                         realSourceColumnName = String.Format ("{0}{1}", sourceColumnName, i);
707                                 sourceColumns.Add(realSourceColumnName);
708
709                                 // generate DataSetColumnName from DataTableMapping, if any
710                                 string dsColumnName = realSourceColumnName;
711                                 DataTableMapping tableMapping = null;
712
713                                 //FIXME : The sourcetable name shud get passed as a parameter.. 
714                                 int index = dtMapping.IndexOfDataSetTable (table.TableName);
715                                 string srcTable = (index != -1 ? dtMapping[index].SourceTable : table.TableName);
716                                 tableMapping = DataTableMappingCollection.GetTableMappingBySchemaAction (dtMapping, srcTable, table.TableName, missingMapAction); 
717                                 if (tableMapping != null)
718                                 {
719                                         table.TableName = tableMapping.DataSetTable;
720                                         // check to see if the column mapping exists
721                                         DataColumnMapping columnMapping = DataColumnMappingCollection.GetColumnMappingBySchemaAction(tableMapping.ColumnMappings, realSourceColumnName, missingMapAction);
722                                         if (columnMapping != null)
723                                         {
724                                                 Type columnType = (Type)schemaRow[DataTypeCol];
725                                                 DataColumn col =
726                                                         columnMapping.GetDataColumnBySchemaAction(
727                                                                                                   table ,
728                                                                                                   columnType,
729                                                                                                   missingSchAction);
730
731                                                 if (col != null)
732                                                 {
733                                                         // if the column is not in the table - add it.
734                                                         if (table.Columns.IndexOf(col) == -1)
735                                                         {
736                                                                 if (missingSchAction == MissingSchemaAction.Add 
737                                                                     || missingSchAction == MissingSchemaAction.AddWithKey)
738                                                                         table.Columns.Add(col);
739
740                                                                 int[] tmp = new int[mapping.Length + 1];
741                                                                 Array.Copy(mapping,0,tmp,0,col.Ordinal);
742                                                                 Array.Copy(mapping,col.Ordinal,tmp,col.Ordinal + 1,mapping.Length - col.Ordinal);
743                                                                 mapping = tmp;
744                                                         }                               
745
746
747                                                         if (missingSchAction == MissingSchemaAction.AddWithKey) {
748                                     
749                                                                 object value = (AllowDBNullCol != null) ? schemaRow[AllowDBNullCol] : null;
750                                                                 bool allowDBNull = value is bool ? (bool)value : true;
751
752                                                                 value = (IsKeyCol != null) ? schemaRow[IsKeyCol] : null;
753                                                                 bool isKey = value is bool ? (bool)value : false;
754
755                                                                 value = (IsAutoIncrementCol != null) ? schemaRow[IsAutoIncrementCol] : null;
756                                                                 bool isAutoIncrement = value is bool ? (bool)value : false;
757
758                                                                 value = (IsReadOnlyCol != null) ? schemaRow[IsReadOnlyCol] : null;
759                                                                 bool isReadOnly = value is bool ? (bool)value : false;
760
761                                                                 value = (IsUniqueCol != null) ? schemaRow[IsUniqueCol] : null;
762                                                                 bool isUnique = value is bool ? (bool)value : false;
763                                                                 
764                                                                 col.AllowDBNull = allowDBNull;
765                                                                 // fill woth key info                                                           
766                                                                 if (isAutoIncrement && DataColumn.CanAutoIncrement(columnType)) {
767                                                                         col.AutoIncrement = true;
768                                                                         if (!allowDBNull)
769                                                                                 col.AllowDBNull = false;
770                                                                 }
771
772                                                                 if (columnType == DbTypes.TypeOfString) {
773                                                                         col.MaxLength = (ColumnSizeCol != null) ? (int)schemaRow[ColumnSizeCol] : 0;
774                                                                 }
775
776                                                                 if (isReadOnly)
777                                                                         col.ReadOnly = true;
778                                                                         
779                                                                 if (!allowDBNull && (!isReadOnly || isKey))
780                                                                         col.AllowDBNull = false;
781                                                                 if (isUnique && !isKey && !columnType.IsArray) {
782                                                                         col.Unique = true;
783                                                                         if (!allowDBNull)
784                                                                                 col.AllowDBNull = false;
785                                                                 }
786                                                                 
787                                                                 // This might not be set by all DataProviders
788                                                                 bool isHidden = false;
789                                                                 if (schemaTable.Columns.Contains ("IsHidden")) {
790                                                                         value = schemaRow["IsHidden"];
791                                                                         isHidden = ((value is bool) ? (bool)value : false);
792                                                                 }
793
794                                                                 if (isKey && !isHidden) {
795                                                                         primaryKey.Add (col);
796                                                                         if (allowDBNull)
797                                                                                 createPrimaryKey = false;
798                                                                 }
799                                                         }
800                                                         // add the ordinal of the column as a key and the index of the column in the datareader as a value.
801                                                         mapping[col.Ordinal] = readerIndex++;
802                                                 }
803                                         }
804                                 }
805                         }
806                         if (primaryKey.Count > 0) {
807                                 DataColumn[] colKey = (DataColumn[])(primaryKey.ToArray(typeof (DataColumn)));
808                                 if (createPrimaryKey)
809                                         table.PrimaryKey = colKey;
810                                 else {
811                                         UniqueConstraint uConstraint = new UniqueConstraint(colKey);
812                                         for (int i = 0; i < table.Constraints.Count; i++) {
813                                                 if (table.Constraints[i].Equals(uConstraint)) {
814                                                         uConstraint = null;
815                                                         break;
816                                                 }
817                                         }
818
819                                         if (uConstraint != null)
820                                                 table.Constraints.Add(uConstraint);
821                                 }
822                         }
823                         return mapping;
824                 }
825
826                 [MonoTODO]
827                 object ICloneable.Clone ()
828                 {
829                         throw new NotImplementedException ();
830                 }
831
832                 public int Update (DataRow[] dataRows) 
833                 {
834                         if (dataRows == null)
835                                 throw new ArgumentNullException("dataRows");
836                         
837                         if (dataRows.Length == 0)
838                                 return 0;
839
840                         if (dataRows[0] == null)
841                                 throw new ArgumentException("dataRows[0].");
842
843                         DataTable table = dataRows[0].Table;
844                         if (table == null)
845                                 throw new ArgumentException("table is null reference.");
846                         
847                         // all rows must be in the same table
848                         for (int i = 0; i < dataRows.Length; i++)
849                         {
850                                 if (dataRows[i] == null)
851                                         throw new ArgumentException("dataRows[" + i + "].");
852                                 if (dataRows[i].Table != table)
853                                         throw new ArgumentException(
854                                                                     " DataRow["
855                                                                     + i
856                                                                     + "] is from a different DataTable than DataRow[0].");
857                         }
858                         
859                         // get table mapping for this rows
860                         DataTableMapping tableMapping = TableMappings.GetByDataSetTable(table.TableName);
861                         if (tableMapping == null)
862                         {
863                                 tableMapping = DataTableMappingCollection.GetTableMappingBySchemaAction(
864                                                                                                         TableMappings,
865                                                                                                         table.TableName,
866                                                                                                         table.TableName,
867                                                                                                         MissingMappingAction);
868                                 if (tableMapping != null) {
869                                         foreach (DataColumn col in table.Columns) {
870                                                 if (tableMapping.ColumnMappings.IndexOf (col.ColumnName) >= 0)
871                                                         continue;
872                                                 DataColumnMapping columnMapping = DataColumnMappingCollection.GetColumnMappingBySchemaAction (tableMapping.ColumnMappings, col.ColumnName, MissingMappingAction);
873                                                 if (columnMapping == null)
874                                                         columnMapping = new DataColumnMapping (col.ColumnName, col.ColumnName);
875                                                 tableMapping.ColumnMappings.Add (columnMapping);
876                                         }
877                                 } else {
878                                         ArrayList cmc = new ArrayList ();
879                                         foreach (DataColumn col in table.Columns)
880                                                 cmc.Add (new DataColumnMapping (col.ColumnName, col.ColumnName));
881                                         tableMapping =
882                                                 new DataTableMapping (
883                                                                       table.TableName,
884                                                                       table.TableName,
885                                                                       cmc.ToArray (typeof (DataColumnMapping)) as DataColumnMapping []);
886                                 }
887                         }
888
889                         DataRow[] copy = table.NewRowArray(dataRows.Length);
890                         Array.Copy(dataRows, 0, copy, 0, dataRows.Length);
891                         return Update(copy, tableMapping);
892                 }
893
894                 public override int Update (DataSet dataSet) 
895                 {
896                         return Update (dataSet, DefaultSourceTableName);
897                 }
898
899                 public int Update (DataTable dataTable) 
900                 {
901                         /*
902                           int index = TableMappings.IndexOfDataSetTable (dataTable.TableName);
903                           if (index < 0)
904                           throw new ArgumentException ();
905                           return Update (dataTable, TableMappings [index]);
906                         */
907                         DataTableMapping tableMapping = TableMappings.GetByDataSetTable (dataTable.TableName);
908                         if (tableMapping == null)
909                         {
910                                 tableMapping = DataTableMappingCollection.GetTableMappingBySchemaAction (
911                                                                                                          TableMappings,
912                                                                                                          dataTable.TableName,
913                                                                                                          dataTable.TableName,
914                                                                                                          MissingMappingAction);
915                                 if (tableMapping != null) {
916                                         foreach (DataColumn col in dataTable.Columns) {
917                                                 if (tableMapping.ColumnMappings.IndexOf (col.ColumnName) >= 0)
918                                                         continue;
919                                                 DataColumnMapping columnMapping = DataColumnMappingCollection.GetColumnMappingBySchemaAction (tableMapping.ColumnMappings, col.ColumnName, MissingMappingAction);
920                                                 if (columnMapping == null)
921                                                         columnMapping = new DataColumnMapping (col.ColumnName, col.ColumnName);
922                                                 tableMapping.ColumnMappings.Add (columnMapping);
923                                         }
924                                 } else {
925                                         ArrayList cmc = new ArrayList ();
926                                         foreach (DataColumn col in dataTable.Columns)
927                                                 cmc.Add (new DataColumnMapping (col.ColumnName, col.ColumnName));
928                                         tableMapping =
929                                                 new DataTableMapping (
930                                                                       dataTable.TableName,
931                                                                       dataTable.TableName,
932                                                                       cmc.ToArray (typeof (DataColumnMapping)) as DataColumnMapping []);
933                                 }
934                         }
935                         return Update (dataTable, tableMapping);
936                 }
937
938                 private int Update (DataTable dataTable, DataTableMapping tableMapping)
939                 {
940                         DataRow[] rows = dataTable.NewRowArray(dataTable.Rows.Count);
941                         dataTable.Rows.CopyTo (rows, 0);
942                         return Update (rows, tableMapping);
943                 }
944
945                 protected virtual int Update (DataRow[] dataRows, DataTableMapping tableMapping) 
946                 {
947                         int updateCount = 0;
948                         foreach (DataRow row in dataRows) {
949                                 StatementType statementType = StatementType.Update;
950                                 IDbCommand command = null;
951                                 string commandName = String.Empty;
952
953                                 switch (row.RowState) {
954                                 case DataRowState.Added:
955                                         statementType = StatementType.Insert;
956                                         command = ((IDbDataAdapter) this).InsertCommand;
957                                         commandName = "Insert";
958                                         break;
959                                 case DataRowState.Deleted:
960                                         statementType = StatementType.Delete;
961                                         command = ((IDbDataAdapter) this).DeleteCommand;
962                                         commandName = "Delete";
963                                         break;
964                                 case DataRowState.Modified:
965                                         statementType = StatementType.Update;
966                                         command = ((IDbDataAdapter) this).UpdateCommand;
967                                         commandName = "Update";
968                                         break;
969                                 case DataRowState.Unchanged:
970                                 case DataRowState.Detached:
971                                         continue;
972                                 }
973
974                                 RowUpdatingEventArgs argsUpdating = CreateRowUpdatingEvent (row, command, statementType, tableMapping);
975                                 row.RowError = null;
976                                 OnRowUpdating(argsUpdating);
977                                 switch(argsUpdating.Status) {
978                                 case UpdateStatus.Continue :
979                                         //continue in update operation
980                                         break;
981                                 case UpdateStatus.ErrorsOccurred :
982                                         if (argsUpdating.Errors == null) {
983                                                 argsUpdating.Errors = ExceptionHelper.RowUpdatedError();
984                                         }
985                                         row.RowError += argsUpdating.Errors.Message;
986                                         if (!ContinueUpdateOnError) {
987                                                 throw argsUpdating.Errors;
988                                         }
989                                         continue;
990                                 case UpdateStatus.SkipAllRemainingRows :
991                                         return updateCount;
992                                 case UpdateStatus.SkipCurrentRow :
993                                         updateCount++;
994                                         continue;
995                                 default :
996                                         throw ExceptionHelper.InvalidUpdateStatus(argsUpdating.Status);
997                                 }
998                                 command = argsUpdating.Command;                                 
999                                 try {
1000                                         if (command != null) {
1001                                                 DataColumnMappingCollection columnMappings = tableMapping.ColumnMappings;
1002                                                 IDataParameter nullCheckParam = null;
1003                                                 foreach (IDataParameter parameter in command.Parameters) {
1004                                                         if ((parameter.Direction & ParameterDirection.Input) != 0) {
1005                                                                 string dsColumnName = parameter.SourceColumn;
1006                                                                 if (columnMappings.Contains(parameter.SourceColumn))
1007                                                                         dsColumnName = columnMappings [parameter.SourceColumn].DataSetColumn;
1008                                                                 if (dsColumnName == null || dsColumnName.Length <= 0) {
1009                                                                         nullCheckParam = parameter;
1010                                                                         continue;
1011                                                                 }
1012
1013                                                                 DataRowVersion rowVersion = parameter.SourceVersion;
1014                                                                 // Parameter version is ignored for non-update commands
1015                                                                 if (statementType == StatementType.Delete) 
1016                                                                         rowVersion = DataRowVersion.Original;
1017
1018                                                                 parameter.Value = row [dsColumnName, rowVersion];
1019                                                                 if (nullCheckParam != null && (parameter.Value != null
1020                                                                         && parameter.Value != DBNull.Value)) {
1021                                                                         nullCheckParam.Value = 0;
1022                                                                         nullCheckParam = null;
1023                                                                 }
1024                                                         }
1025                                                 }
1026                                         }
1027                                 }
1028                                 catch (Exception e) {
1029                                         argsUpdating.Errors = e;
1030                                         argsUpdating.Status = UpdateStatus.ErrorsOccurred;
1031                                 }
1032
1033                                 
1034                                 IDataReader reader = null;
1035                                 try {                                                           
1036                                         if (command == null) {
1037                                                 throw ExceptionHelper.UpdateRequiresCommand(commandName);
1038                                         }                               
1039                                 
1040                                         CommandBehavior commandBehavior = CommandBehavior.Default;
1041                                         if (command.Connection.State == ConnectionState.Closed) {
1042                                                 command.Connection.Open ();
1043                                                 commandBehavior |= CommandBehavior.CloseConnection;
1044                                         }
1045                                 
1046                                         // use ExecuteReader because we want to use the commandbehavior parameter.
1047                                         // so the connection will be closed if needed.
1048                                         reader = command.ExecuteReader (commandBehavior);
1049
1050                                         // update the current row, if the update command returns any resultset
1051                                         // ignore other than the first record.
1052                                         DataColumnMappingCollection columnMappings = tableMapping.ColumnMappings;
1053
1054                                         if (command.UpdatedRowSource == UpdateRowSource.Both ||
1055                                             command.UpdatedRowSource == UpdateRowSource.FirstReturnedRecord) {
1056                                                 if (reader.Read ()){
1057                                                         DataTable retSchema = reader.GetSchemaTable ();
1058                                                         foreach (DataRow dr in retSchema.Rows) {
1059                                                                 string columnName = dr ["ColumnName"].ToString ();
1060                                                                 string dstColumnName = columnName;
1061                                                                 if (columnMappings != null &&
1062                                                                     columnMappings.Contains(columnName))
1063                                                                         dstColumnName = columnMappings [dstColumnName].DataSetColumn;
1064                                                                 DataColumn dstColumn = row.Table.Columns [dstColumnName];
1065                                                                 if (dstColumn == null
1066                                                                     || (dstColumn.Expression != null
1067                                                                         && dstColumn.Expression.Length > 0))
1068                                                                         continue;
1069                                                                 // info from : http://www.error-bank.com/microsoft.public.dotnet.framework.windowsforms.databinding/
1070                                                                 // _35_hcsyiv0dha.2328@tk2msftngp10.phx.gbl_Thread.aspx
1071                                                                 // disable readonly for non-expression columns.
1072                                                                 bool readOnlyState = dstColumn.ReadOnly;
1073                                                                 dstColumn.ReadOnly = false;
1074                                                                 try {
1075                                                                         row [dstColumnName] = reader [columnName];
1076                                                                 } finally {
1077                                                                         dstColumn.ReadOnly = readOnlyState;
1078                                                                 }                                    
1079                                                         }
1080                                                 }
1081                                         }
1082                                         reader.Close ();
1083
1084                                         int tmp = reader.RecordsAffected; // records affected is valid only after closing reader
1085                                         // if the execute does not effect any rows we throw an exception.
1086                                         if (tmp == 0)
1087                                                 throw new DBConcurrencyException("Concurrency violation: the " + 
1088                                                                                  commandName +"Command affected 0 records.");
1089                                         updateCount += tmp;
1090                                         
1091                                         if (command.UpdatedRowSource == UpdateRowSource.Both ||
1092                                             command.UpdatedRowSource == UpdateRowSource.OutputParameters) {
1093                                                 // Update output parameters to row values
1094                                                 foreach (IDataParameter parameter in command.Parameters) {
1095                                                         if (parameter.Direction != ParameterDirection.InputOutput
1096                                                             && parameter.Direction != ParameterDirection.Output
1097                                                             && parameter.Direction != ParameterDirection.ReturnValue)
1098                                                                 continue;
1099
1100                                                         string dsColumnName = parameter.SourceColumn;
1101                                                         if (columnMappings != null &&
1102                                                             columnMappings.Contains(parameter.SourceColumn))
1103                                                                 dsColumnName = columnMappings [parameter.SourceColumn].DataSetColumn;
1104                                                         DataColumn dstColumn = row.Table.Columns [dsColumnName];
1105                                                         if (dstColumn == null
1106                                                             || (dstColumn.Expression != null 
1107                                                                 && dstColumn.Expression.Length > 0))
1108                                                                 continue;
1109                                                         bool readOnlyState = dstColumn.ReadOnly;
1110                                                         dstColumn.ReadOnly  = false;
1111                                                         try {
1112                                                                 row [dsColumnName] = parameter.Value;
1113                                                         } finally {
1114                                                                 dstColumn.ReadOnly = readOnlyState;
1115                                                         }
1116                                     
1117                                                 }
1118                                         }
1119                     
1120                                         RowUpdatedEventArgs updatedArgs = CreateRowUpdatedEvent(row, command, statementType, tableMapping);
1121                                         OnRowUpdated(updatedArgs);
1122                                         switch(updatedArgs.Status) {
1123                                         case UpdateStatus.Continue:
1124                                                 break;
1125                                         case UpdateStatus.ErrorsOccurred:
1126                                                 if (updatedArgs.Errors == null) {
1127                                                         updatedArgs.Errors = ExceptionHelper.RowUpdatedError();
1128                                                 }
1129                                                 row.RowError += updatedArgs.Errors.Message;
1130                                                 if (!ContinueUpdateOnError) {
1131                                                         throw updatedArgs.Errors;
1132                                                 }
1133                                                 break;
1134                                         case UpdateStatus.SkipCurrentRow:
1135                                                 continue;
1136                                         case UpdateStatus.SkipAllRemainingRows:
1137                                                 return updateCount;
1138                                         }
1139 #if NET_2_0
1140                                         if (!AcceptChangesDuringUpdate)
1141                                                 continue;
1142 #endif
1143                                         row.AcceptChanges ();
1144                                 } catch(Exception e) {
1145                                         row.RowError = e.Message;
1146                                         if (!ContinueUpdateOnError) {
1147                                                 throw e;
1148                                         }
1149                                 } finally {
1150                                         if (reader != null && ! reader.IsClosed) {
1151                                                 reader.Close ();
1152                                         }
1153                                 }       
1154                         }               
1155                         return updateCount;
1156                 }
1157
1158                 public int Update (DataSet dataSet, string sourceTable) 
1159                 {
1160                         MissingMappingAction mappingAction = MissingMappingAction;
1161
1162                         if (mappingAction == MissingMappingAction.Ignore)
1163                                 mappingAction = MissingMappingAction.Error;
1164
1165                         DataTableMapping tableMapping = DataTableMappingCollection.GetTableMappingBySchemaAction (TableMappings, sourceTable, sourceTable, mappingAction);
1166
1167                         DataTable dataTable = dataSet.Tables[tableMapping.DataSetTable];
1168                         if (dataTable == null)
1169                                 throw new ArgumentException (String.Format ("Missing table {0}",
1170                                                                             sourceTable));
1171                         return Update (dataTable, tableMapping);
1172                 }
1173                 
1174 #if NET_2_0
1175                 // All the batch methods, should be implemented, if supported,
1176                 // by individual providers 
1177
1178                 protected virtual int AddToBatch (IDbCommand cmd)
1179                 {
1180                         throw new NotSupportedException ();
1181                 }
1182
1183                 protected virtual void ClearBatch ()
1184                 {
1185                         throw new NotSupportedException ();
1186                 }
1187
1188                 protected virtual int ExecuteBatch ()
1189                 {
1190                         throw new NotSupportedException ();
1191                 }
1192
1193                 protected virtual IDataParameter GetBatchedParameter (int commandIdentifier, int parameterIdentifer)
1194                 {
1195                         throw new NotSupportedException ();
1196                 }
1197
1198                 protected virtual void InitializeBatching ()
1199                 {
1200                         throw new NotSupportedException ();
1201                 }
1202
1203                 protected virtual void TerminateBatching ()
1204                 {
1205                         throw new NotSupportedException ();
1206                 }
1207 #endif
1208
1209 #if ONLY_1_0 || ONLY_1_1
1210                 protected virtual void OnFillError (FillErrorEventArgs value) 
1211                 {
1212                         if (FillError != null)
1213                                 FillError (this, value);
1214                 }
1215 #endif
1216
1217                 #endregion // Methods
1218         }
1219 }