* DataRow.cs (CheckChildRows) : Improving the implementation - checking child for...
[mono.git] / mcs / class / System.Data / System.Data / DataTable.cs
1 //\r
2 // System.Data.DataTable.cs\r
3 //\r
4 // Author:\r
5 //   Franklin Wise <gracenote@earthlink.net>\r
6 //   Christopher Podurgiel (cpodurgiel@msn.com)\r
7 //   Daniel Morgan <danmorg@sc.rr.com>\r
8 //   Rodrigo Moya <rodrigo@ximian.com>\r
9 //   Tim Coleman (tim@timcoleman.com)\r
10 //   Ville Palo <vi64pa@koti.soon.fi>\r
11 //\r
12 // (C) Chris Podurgiel\r
13 // (C) Ximian, Inc 2002\r
14 // Copyright (C) Tim Coleman, 2002\r
15 // Copyright (C) Daniel Morgan, 2002-2003\r
16 //\r
17 \r
18 using System;\r
19 using System.Collections;\r
20 using System.ComponentModel;\r
21 using System.Globalization;\r
22 using System.Runtime.Serialization;\r
23 \r
24 namespace System.Data {\r
25         //[Designer]\r
26         [ToolboxItem (false)]\r
27         [DefaultEvent ("RowChanging")]\r
28         [DefaultProperty ("TableName")]\r
29         [DesignTimeVisible (false)]\r
30         [Serializable]\r
31         public class DataTable : MarshalByValueComponent, IListSource, ISupportInitialize, ISerializable \r
32         {\r
33                 internal DataSet dataSet;   \r
34                 \r
35                 private bool _caseSensitive;\r
36                 private DataColumnCollection _columnCollection;\r
37                 private ConstraintCollection _constraintCollection;\r
38                 private DataView _defaultView;\r
39 \r
40                 private string _displayExpression;\r
41                 private PropertyCollection _extendedProperties;\r
42                 private bool _hasErrors;\r
43                 private CultureInfo _locale;\r
44                 private int _minimumCapacity;\r
45                 private string _nameSpace;\r
46                 private DataRelationCollection _childRelations; \r
47                 private DataRelationCollection _parentRelations;\r
48                 private string _prefix;\r
49                 private DataColumn[] _primaryKey;\r
50                 private DataRowCollection _rows;\r
51                 private ISite _site;\r
52                 private string _tableName;\r
53                 private bool _containsListCollection;\r
54                 private string _encodedTableName;\r
55                 internal bool _duringDataLoad;\r
56                 private bool dataSetPrevEnforceConstraints;\r
57 \r
58 \r
59                 \r
60                 // If CaseSensitive property is changed once it does not anymore follow owner DataSet's \r
61                 // CaseSensitive property. So when you lost you virginity it's gone for ever\r
62                 private bool _virginCaseSensitive = true;\r
63 \r
64                 /// <summary>\r
65                 /// Initializes a new instance of the DataTable class with no arguments.\r
66                 /// </summary>\r
67                 \r
68                 public DataTable () \r
69                 {\r
70                         dataSet = null;\r
71                         _columnCollection = new DataColumnCollection(this);\r
72                         _constraintCollection = new ConstraintCollection(this); \r
73                         _extendedProperties = new PropertyCollection();\r
74                         _tableName = "";\r
75                         _nameSpace = null;\r
76                         _caseSensitive = false;         //default value\r
77                         _displayExpression = null;\r
78                         _primaryKey = null;\r
79                         _site = null;\r
80                         _rows = new DataRowCollection (this);\r
81                         \r
82                         //LAMESPEC: spec says 25 impl does 50\r
83                         _minimumCapacity = 50;\r
84                         \r
85                         _childRelations = new DataRelationCollection.DataTableRelationCollection (this);\r
86                         _parentRelations = new DataRelationCollection.DataTableRelationCollection (this);\r
87 \r
88                 \r
89                         _defaultView = new DataView(this);\r
90                 }\r
91 \r
92                 /// <summary>\r
93                 /// Intitalizes a new instance of the DataTable class with the specified table name.\r
94                 /// </summary>\r
95                 public DataTable (string tableName) : this () \r
96                 {\r
97                         _tableName = tableName;\r
98                 }\r
99 \r
100                 /// <summary>\r
101                 /// Initializes a new instance of the DataTable class with the SerializationInfo and the StreamingContext.\r
102                 /// </summary>\r
103                 [MonoTODO]\r
104                 protected DataTable (SerializationInfo info, StreamingContext context)\r
105                         : this () \r
106                 {\r
107                         //\r
108                         // TODO: Add constructor logic here\r
109                         //\r
110                 }\r
111 \r
112                 /// <summary>\r
113                 /// Indicates whether string comparisons within the table are case-sensitive.\r
114                 /// </summary>\r
115                 [DataSysDescription ("Indicates whether comparing strings within the table is case sensitive.")]        \r
116                 public bool CaseSensitive {\r
117                         get { return _caseSensitive; }\r
118                         set { \r
119                                 _virginCaseSensitive = false;\r
120                                 _caseSensitive = value; \r
121                         }\r
122                 }\r
123 \r
124                 internal bool VirginCaseSensitive {\r
125                         get { return _virginCaseSensitive; }\r
126                         set { _virginCaseSensitive = value; }\r
127                 }\r
128 \r
129                 internal void ChangedDataColumn (DataRow dr, DataColumn dc, object pv) \r
130                 {\r
131                         DataColumnChangeEventArgs e = new DataColumnChangeEventArgs (dr, dc, pv);\r
132                         OnColumnChanged(e);\r
133                 }\r
134 \r
135                 internal void ChangingDataColumn (DataRow dr, DataColumn dc, object pv) \r
136                 {\r
137                         DataColumnChangeEventArgs e = new DataColumnChangeEventArgs (dr, dc, pv);\r
138                         OnColumnChanging (e);\r
139                 }\r
140 \r
141                 internal void DeletedDataRow (DataRow dr, DataRowAction action) \r
142                 {\r
143                         DataRowChangeEventArgs e = new DataRowChangeEventArgs (dr, action);\r
144                         OnRowDeleted (e);\r
145                 }\r
146 \r
147                 internal void DeletingDataRow (DataRow dr, DataRowAction action) \r
148                 {\r
149                         DataRowChangeEventArgs e = new DataRowChangeEventArgs (dr, action);\r
150                         OnRowDeleting(e);\r
151                 }\r
152 \r
153                 internal void ChangedDataRow (DataRow dr, DataRowAction action) \r
154                 {\r
155                         DataRowChangeEventArgs e = new DataRowChangeEventArgs (dr, action);\r
156                         OnRowChanged (e);\r
157                 }\r
158 \r
159                 internal void ChangingDataRow (DataRow dr, DataRowAction action) \r
160                 {\r
161                         DataRowChangeEventArgs e = new DataRowChangeEventArgs (dr, action);\r
162                         OnRowChanging (e);\r
163                 }\r
164 \r
165                 /// <summary>\r
166                 /// Gets the collection of child relations for this DataTable.\r
167                 /// </summary>\r
168                 [Browsable (false)]\r
169                 [DataSysDescription ("Returns the child relations for this table.")]\r
170                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]\r
171                 public DataRelationCollection ChildRelations {\r
172                         get {\r
173                                 return _childRelations;\r
174                         }\r
175                 }\r
176 \r
177                 /// <summary>\r
178                 /// Gets the collection of columns that belong to this table.\r
179                 /// </summary>\r
180                 [DataCategory ("Data")]\r
181                 [DataSysDescription ("The collection that holds the columns for this table.")]\r
182                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]\r
183                 public DataColumnCollection Columns {\r
184                         get { return _columnCollection; }\r
185                 }\r
186 \r
187                 /// <summary>\r
188                 /// Gets the collection of constraints maintained by this table.\r
189                 /// </summary>\r
190                 [DataCategory ("Data")] \r
191                 [DataSysDescription ("The collection that holds the constraints for this table.")]\r
192                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]\r
193                 public ConstraintCollection Constraints {\r
194                         get { return _constraintCollection; }\r
195                 }\r
196 \r
197                 /// <summary>\r
198                 /// Gets the DataSet that this table belongs to.\r
199                 /// </summary>\r
200                 [Browsable (false)]\r
201                 [DataSysDescription ("Indicates the DataSet to which this table belongs.")]\r
202                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]\r
203                 public DataSet DataSet {\r
204                         get { return dataSet; }\r
205                 }\r
206 \r
207                 \r
208 \r
209                 /// <summary>\r
210                 /// Gets a customized view of the table which may \r
211                 /// include a filtered view, or a cursor position.\r
212                 /// </summary>\r
213                 [MonoTODO]      \r
214                 [Browsable (false)]\r
215                 [DataSysDescription ("This is the default DataView for the table.")]\r
216                 public DataView DefaultView {\r
217                         get { return _defaultView; }\r
218                 }\r
219                 \r
220 \r
221                 /// <summary>\r
222                 /// Gets or sets the expression that will return \r
223                 /// a value used to represent this table in the user interface.\r
224                 /// </summary>\r
225                 [DataCategory ("Data")]\r
226                 [DataSysDescription ("The expression used to compute the data-bound value of this row.")]       \r
227                 [DefaultValue ("")]\r
228                 public string DisplayExpression {\r
229                         get { return "" + _displayExpression; }\r
230                         set { _displayExpression = value; }\r
231                 }\r
232 \r
233                 /// <summary>\r
234                 /// Gets the collection of customized user information.\r
235                 /// </summary>\r
236                 [Browsable (false)]\r
237                 [DataCategory ("Data")]\r
238                 [DataSysDescription ("The collection that holds custom user information.")]\r
239                 public PropertyCollection ExtendedProperties {\r
240                         get { return _extendedProperties; }\r
241                 }\r
242 \r
243                 /// <summary>\r
244                 /// Gets a value indicating whether there are errors in \r
245                 /// any of the_rows in any of the tables of the DataSet to \r
246                 /// which the table belongs.\r
247                 /// </summary>\r
248                 [Browsable (false)]\r
249                 [DataSysDescription ("Returns whether the table has errors.")]\r
250                 public bool HasErrors {\r
251                         get { \r
252                                 // we can not use the _hasError flag because we do not know when to turn it off!\r
253                                 for (int i = 0; i < _rows.Count; i++)\r
254                                 {\r
255                                         if (_rows[i].HasErrors)\r
256                                                 return true;\r
257                                 }\r
258                                 return false;\r
259                         }\r
260                 }\r
261 \r
262                 /// <summary>\r
263                 /// Gets or sets the locale information used to \r
264                 /// compare strings within the table.\r
265                 /// </summary>\r
266                 [DataSysDescription ("Indicates a locale under which to compare strings within the table.")]\r
267                 public CultureInfo Locale {\r
268                         get { \r
269                                 // if the locale is null, we check for the DataSet locale\r
270                                 // and if the DataSet is null we return the current culture.\r
271                                 // this way if DataSet locale is changed, only if there is no locale for \r
272                                 // the DataTable it influece the Locale get;\r
273                                 if (_locale != null)\r
274                                         return _locale;\r
275                                 if (DataSet != null)\r
276                                         return DataSet.Locale;\r
277                                 return CultureInfo.CurrentCulture;\r
278                         }\r
279                         set { \r
280                                 if (_locale == null || !_locale.Equals(value))\r
281                                         _locale = value; \r
282                         }\r
283                 }\r
284 \r
285                 /// <summary>\r
286                 /// Gets or sets the initial starting size for this table.\r
287                 /// </summary>\r
288                 [DataCategory ("Data")]\r
289                 [DataSysDescription ("Indicates an initial starting size for this table.")]\r
290                 [DefaultValue (50)]\r
291                 public int MinimumCapacity {\r
292                         get { return _minimumCapacity; }\r
293                         set { _minimumCapacity = value; }\r
294                 }\r
295 \r
296                 /// <summary>\r
297                 /// Gets or sets the namespace for the XML represenation \r
298                 /// of the data stored in the DataTable.\r
299                 /// </summary>\r
300                 [DataCategory ("Data")]\r
301                 [DataSysDescription ("Indicates the XML uri namespace for the elements contained in this table.")]\r
302                 public string Namespace {\r
303                         get { return "" + _nameSpace; }\r
304                         set { _nameSpace = value; }\r
305                 }\r
306 \r
307                 /// <summary>\r
308                 /// Gets the collection of parent relations for \r
309                 /// this DataTable.\r
310                 /// </summary>\r
311                 [Browsable (false)]\r
312                 [DataSysDescription ("Returns the parent relations for this table.")]\r
313                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]\r
314                 public DataRelationCollection ParentRelations {\r
315                         get {   \r
316                                 return _parentRelations;\r
317                         }\r
318                 }\r
319 \r
320                 /// <summary>\r
321                 /// Gets or sets the namespace for the XML represenation\r
322                 ///  of the data stored in the DataTable.\r
323                 /// </summary>\r
324                 [DataCategory ("Data")]\r
325                 [DataSysDescription ("Indicates the Prefix of the namespace used for this table in XML representation.")]\r
326                 [DefaultValue ("")]\r
327                 public string Prefix {\r
328                         get { return "" + _prefix; }\r
329                         set { _prefix = value; }\r
330                 }\r
331 \r
332                 /// <summary>\r
333                 /// Gets or sets an array of columns that function as \r
334                 /// primary keys for the data table.\r
335                 /// </summary>\r
336                 [DataCategory ("Data")]\r
337                 [DataSysDescription ("Indicates the column(s) that represent the primary key for this table.")]\r
338                 public DataColumn[] PrimaryKey {\r
339                         get {\r
340                                 UniqueConstraint uc = UniqueConstraint.GetPrimaryKeyConstraint( Constraints);\r
341                                 if (null == uc) return new DataColumn[] {};\r
342                                 return uc.Columns;\r
343                         }\r
344                         set {\r
345 \r
346                                 //YUK: msft removes a previous unique constraint if it is flagged as a pk  \r
347                                 //when a new pk is set \r
348                                 \r
349                                 //clear Primary Key if value == null\r
350                                 if (null == value) {\r
351                                         \r
352                                         RemoveUniqueConstraints ();\r
353                                         return;\r
354                                 }\r
355 \r
356                                 //Does constraint exist for these columns\r
357                                 UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet(\r
358                                         this.Constraints, (DataColumn[]) value);\r
359 \r
360                                 //if constraint doesn't exist for columns\r
361                                 //create new unique primary key constraint\r
362                                 if (null == uc) {\r
363 \r
364                                         RemoveUniqueConstraints ();                                             \r
365                                         \r
366                                         foreach (DataColumn Col in (DataColumn[]) value) {\r
367 \r
368                                                 if (Col.Table == null)\r
369                                                         break;\r
370 \r
371                                                 if (Columns.IndexOf (Col) < 0)\r
372                                                         throw new ArgumentException ("PrimaryKey columns do not belong to this table.");\r
373                                         }\r
374 \r
375 \r
376                                         uc = new UniqueConstraint( (DataColumn[]) value, true);\r
377                                         \r
378                                         Constraints.Add (uc);\r
379                                 }\r
380                                 else { //set existing constraint as the new primary key\r
381                                         UniqueConstraint.SetAsPrimaryKey(this.Constraints, uc);\r
382                                 }\r
383                                 \r
384                         }\r
385                 }\r
386 \r
387                 /// <summary>\r
388                 /// Gets the collection of_rows that belong to this table.\r
389                 /// </summary>\r
390                 [Browsable (false)]\r
391                 [DataSysDescription ("Indicates the collection that holds the rows of data for this table.")]   \r
392                 public DataRowCollection Rows {\r
393                         get { return _rows; }\r
394                 }\r
395 \r
396                 /// <summary>\r
397                 /// Gets or sets an System.ComponentModel.ISite \r
398                 /// for the DataTable.\r
399                 /// </summary>\r
400                 [Browsable (false)]\r
401                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]\r
402                 public override ISite Site {\r
403                         get { return _site; }\r
404                         set { _site = value; }\r
405                 }\r
406 \r
407                 /// <summary>\r
408                 /// Gets or sets the name of the the DataTable.\r
409                 /// </summary>\r
410                 [DataCategory ("Data")]\r
411                 [DataSysDescription ("Indicates the name used to look up this table in the Tables collection of a DataSet.")]\r
412                 [DefaultValue ("")]     \r
413                 [RefreshProperties (RefreshProperties.All)]\r
414                 public string TableName {\r
415                         get { return "" + _tableName; }\r
416                         set { _tableName = value; }\r
417                 }\r
418                 \r
419                 bool IListSource.ContainsListCollection {\r
420                         get {\r
421                                 // the collection is a DataView\r
422                                 return false;\r
423                         }\r
424                 }\r
425 \r
426                 /// <summary>\r
427                 /// Commits all the changes made to this table since the \r
428                 /// last time AcceptChanges was called.\r
429                 /// </summary>\r
430                 public void AcceptChanges () \r
431                 {\r
432                         //FIXME: Do we need to validate anything here or\r
433                         //try to catch any errors to deal with them?\r
434                         \r
435                         // we do not use foreach because if one of the rows is in Delete state\r
436                         // it will be romeved from Rows and we get an exception.\r
437                         DataRow myRow;\r
438                         for (int i = 0; i < Rows.Count; )\r
439                         {\r
440                                 myRow = Rows[i];\r
441                                 myRow.AcceptChanges();\r
442 \r
443                                 // if the row state is Detached it meens that it was removed from row list (Rows)\r
444                                 // so we should not increase 'i'.\r
445                                 if (myRow.RowState != DataRowState.Detached)\r
446                                         i++;\r
447                         }\r
448                 }\r
449 \r
450                 /// <summary>\r
451                 /// Begins the initialization of a DataTable that is used \r
452                 /// on a form or used by another component. The initialization\r
453                 /// occurs at runtime.\r
454                 /// </summary>\r
455                 public void BeginInit () \r
456                 {\r
457                 }\r
458 \r
459                 /// <summary>\r
460                 /// Turns off notifications, index maintenance, and \r
461                 /// constraints while loading data.\r
462                 /// </summary>\r
463                 [MonoTODO]\r
464                 public void BeginLoadData () \r
465                 {\r
466                         if (!this._duringDataLoad)\r
467                         {\r
468                                 //duringDataLoad is important to EndLoadData and\r
469                                 //for not throwing unexpected exceptions.\r
470                                 this._duringDataLoad = true;\r
471                         \r
472                                 if (this.dataSet != null)\r
473                                 {\r
474                                         //Saving old Enforce constraints state for later\r
475                                         //use in the EndLoadData.\r
476                                         this.dataSetPrevEnforceConstraints = this.dataSet.EnforceConstraints;\r
477                                         this.dataSet.EnforceConstraints = false;\r
478                                 }\r
479                         }\r
480                         return;\r
481                 }\r
482 \r
483                 /// <summary>\r
484                 /// Clears the DataTable of all data.\r
485                 /// </summary>\r
486                 public void Clear () {\r
487                         // TODO: thow an exception if any rows that \r
488                         //       have enforced child relations \r
489                         //       that would result in child rows being orphaned\r
490                         // now we check if any ForeignKeyConstraint is referncing 'table'.\r
491                         if (DataSet != null)\r
492                         {\r
493                                 IEnumerator tableEnumerator = DataSet.Tables.GetEnumerator();\r
494                         \r
495                                 // loop on all tables in dataset\r
496                                 while (tableEnumerator.MoveNext())\r
497                                 {\r
498                                         IEnumerator constraintEnumerator = ((DataTable) tableEnumerator.Current).Constraints.GetEnumerator();\r
499                                         // loop on all constrains in the current table\r
500                                         while (constraintEnumerator.MoveNext())\r
501                                         {\r
502                                                 Object o = constraintEnumerator.Current;\r
503                                                 // we only check ForeignKeyConstraint\r
504                                                 if (o is ForeignKeyConstraint)\r
505                                                 {\r
506                                                         ForeignKeyConstraint fc = (ForeignKeyConstraint) o;\r
507                                                         if(fc.RelatedTable == this && fc.Table.Rows.Count > 0)\r
508                                                                 throw new InvalidConstraintException("Cannot clear table " + fc.RelatedTable + " because ForeignKeyConstraint " + fc.ConstraintName + " enforces constraints and there are child rows in " + fc.Table);\r
509                                                 }\r
510                                         }\r
511                                 }\r
512                         }\r
513                         \r
514                         // TODO: throw a NotSupportedException if the DataTable is part\r
515                         //       of a DataSet that is binded to an XmlDataDocument\r
516                         \r
517                         _rows.Clear ();\r
518                 }\r
519 \r
520                 /// <summary>\r
521                 /// Clones the structure of the DataTable, including\r
522                 ///  all DataTable schemas and constraints.\r
523                 /// </summary>\r
524                 [MonoTODO]\r
525                 public virtual DataTable Clone () \r
526                 {\r
527                         DataTable Copy = new DataTable ();                      \r
528                         \r
529                         CopyProperties (Copy);\r
530                         return Copy;\r
531                 }\r
532 \r
533                 /// <summary>\r
534                 /// Computes the given expression on the current_rows that \r
535                 /// pass the filter criteria.\r
536                 /// </summary>\r
537                 [MonoTODO]\r
538                 public object Compute (string expression, string filter) \r
539                 {\r
540                         // TODO: implement this function based\r
541                         //       on Select (expression)\r
542                         //\r
543                         // expression is an aggregate function\r
544                         // filter is an expression used to limit rows\r
545 \r
546                         object obj = null;\r
547 \r
548                         // filter rows\r
549                         ExpressionElement Expression = new ExpressionMainElement (filter);\r
550                         \r
551                         ArrayList List = new ArrayList ();\r
552                         foreach (DataRow Row in Rows) {\r
553                                 \r
554                                 if (Expression.Test (Row))\r
555                                         List.Add (Row);\r
556                         }\r
557                         \r
558                         DataRow[] rows = (DataRow [])List.ToArray (typeof (DataRow));\r
559 \r
560                         // TODO: with the filtered rows, execute the aggregate function\r
561                         //       mentioned in expression\r
562 \r
563                         return obj;\r
564                 }\r
565 \r
566                 /// <summary>\r
567                 /// Copies both the structure and data for this DataTable.\r
568                 /// </summary>\r
569                 [MonoTODO]      \r
570                 public DataTable Copy () \r
571                 {\r
572                         DataTable Copy = new DataTable ();\r
573                         CopyProperties (Copy);\r
574 \r
575                         // we can not simply copy the row values (NewRow [C.ColumnName] = Row [C.ColumnName])\r
576                         // because if the row state is deleted we get an exception.\r
577                         Copy._duringDataLoad = true;\r
578                         foreach (DataRow Row in Rows) {\r
579                                 DataRow NewRow = Copy.NewRow ();\r
580                                 Copy.Rows.Add (NewRow);\r
581                                 Row.CopyValuesToRow(NewRow);\r
582                         }\r
583                         Copy._duringDataLoad = false;           \r
584                         return Copy;\r
585                 }\r
586 \r
587                 [MonoTODO]\r
588                 private void CopyProperties (DataTable Copy) \r
589                 {\r
590                                         \r
591                         Copy.CaseSensitive = CaseSensitive;\r
592                         Copy.VirginCaseSensitive = VirginCaseSensitive;\r
593 \r
594                         // Copy.ChildRelations\r
595                         // Copy.Constraints\r
596                         // Copy.Container\r
597                         // Copy.DefaultView\r
598                         // Copy.DesignMode\r
599                         Copy.DisplayExpression = DisplayExpression;\r
600                         // Copy.ExtendedProperties\r
601                         Copy.Locale = Locale;\r
602                         Copy.MinimumCapacity = MinimumCapacity;\r
603                         Copy.Namespace = Namespace;\r
604                         // Copy.ParentRelations\r
605                         Copy.Prefix = Prefix;\r
606                         Copy.Site = Site;\r
607                         Copy.TableName = TableName;\r
608 \r
609 \r
610 \r
611                         // Copy columns\r
612                         foreach (DataColumn Column in Columns) {\r
613                                 \r
614                                 Copy.Columns.Add (CopyColumn (Column)); \r
615                         }\r
616 \r
617                         CopyConstraints(Copy);\r
618                         // add primary key to the copy\r
619                         if (PrimaryKey.Length > 0)\r
620                         {\r
621                                 DataColumn[] pColumns = new DataColumn[PrimaryKey.Length];\r
622                                 for (int i = 0; i < pColumns.Length; i++)\r
623                                         pColumns[i] = Copy.Columns[PrimaryKey[i].ColumnName];\r
624 \r
625                                 Copy.PrimaryKey = pColumns;\r
626                         }\r
627                 }\r
628 \r
629                 private void CopyConstraints(DataTable copy)\r
630                 {\r
631                         UniqueConstraint origUc;\r
632                         UniqueConstraint copyUc;\r
633                         for (int i = 0; i < this.Constraints.Count; i++)\r
634                         {\r
635                                 if (this.Constraints[i] is UniqueConstraint)\r
636                                 {\r
637                                         origUc = (UniqueConstraint)this.Constraints[i];\r
638                                         DataColumn[] columns = new DataColumn[origUc.Columns.Length];\r
639                                         for (int j = 0; j < columns.Length; j++)\r
640                                                 columns[j] = copy.Columns[origUc.Columns[j].ColumnName];\r
641                                         \r
642                                         copyUc = new UniqueConstraint(origUc.ConstraintName, columns, origUc.IsPrimaryKey);\r
643                                         copy.Constraints.Add(copyUc);\r
644                                 }\r
645                         }\r
646                 }\r
647                 /// <summary>\r
648                 /// Ends the initialization of a DataTable that is used \r
649                 /// on a form or used by another component. The \r
650                 /// initialization occurs at runtime.\r
651                 /// </summary>\r
652                 [MonoTODO]\r
653                 public void EndInit () \r
654                 {\r
655 \r
656                 }\r
657 \r
658                 /// <summary>\r
659                 /// Turns on notifications, index maintenance, and \r
660                 /// constraints after loading data.\r
661                 /// </summary>\r
662                 [MonoTODO]\r
663                 public void EndLoadData() \r
664                 {\r
665                         int i = 0;\r
666                         if (this._duringDataLoad) \r
667                         {\r
668                                 //Returning from loading mode, raising exceptions as usual\r
669                                 this._duringDataLoad = false;\r
670                                 \r
671                                 if (this.dataSet !=null)\r
672                                 {\r
673                                         //Getting back to previous EnforceConstraint state\r
674                                         this.dataSet.EnforceConstraints = this.dataSetPrevEnforceConstraints;\r
675                                 }\r
676                                 for (i=0 ; i<this.Rows.Count ; i++)\r
677                                 {\r
678                                         if (this.Rows[i]._nullConstraintViolation )\r
679                                         {\r
680                                                 throw new ConstraintException ("Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.");\r
681                                         }\r
682                                                 \r
683                                 }\r
684 \r
685                         }\r
686 \r
687                 }\r
688 \r
689                 /// <summary>\r
690                 /// Gets a copy of the DataTable that contains all\r
691                 ///  changes made to it since it was loaded or \r
692                 ///  AcceptChanges was last called.\r
693                 /// </summary>\r
694                 [MonoTODO]\r
695                 public DataTable GetChanges() \r
696                 {\r
697                         return GetChanges(DataRowState.Added | DataRowState.Deleted | DataRowState.Modified);\r
698                 }\r
699 \r
700                 /// <summary>\r
701                 /// Gets a copy of the DataTable containing all \r
702                 /// changes made to it since it was last loaded, or \r
703                 /// since AcceptChanges was called, filtered by DataRowState.\r
704                 /// </summary>\r
705                 [MonoTODO]      \r
706                 public DataTable GetChanges(DataRowState rowStates) \r
707                 {\r
708                         DataTable copyTable = null;\r
709 \r
710                         IEnumerator rowEnumerator = Rows.GetEnumerator();\r
711                         while (rowEnumerator.MoveNext()) {\r
712                                 DataRow row = (DataRow)rowEnumerator.Current;\r
713                                 if (row.IsRowChanged(rowStates)) {\r
714                                         if (copyTable == null)\r
715                                                 copyTable = Clone();\r
716                                         DataRow newRow = copyTable.NewRow();\r
717                                         copyTable.Rows.Add(newRow);\r
718                                         row.CopyValuesToRow(newRow);\r
719                                 }\r
720                         }\r
721                          \r
722                         return copyTable;\r
723                 }\r
724 \r
725                 /// <summary>\r
726                 /// Gets an array of DataRow objects that contain errors.\r
727                 /// </summary>\r
728                 [MonoTODO]\r
729                 public DataRow[] GetErrors () \r
730                 {\r
731                         ArrayList errors = new ArrayList();\r
732                         for (int i = 0; i < _rows.Count; i++)\r
733                         {\r
734                                 if (_rows[i].HasErrors)\r
735                                         errors.Add(_rows[i]);\r
736                         }\r
737                         \r
738                         return (DataRow[]) errors.ToArray(typeof(DataRow));\r
739                 }\r
740         \r
741                 /// <summary>\r
742                 /// This member is only meant to support Mono's infrastructure \r
743                 /// </summary>\r
744                 protected virtual DataTable CreateInstance () \r
745                 {\r
746                         return Activator.CreateInstance (this.GetType (), true) as DataTable;\r
747                 }\r
748 \r
749                 /// <summary>\r
750                 /// This member is only meant to support Mono's infrastructure \r
751                 /// </summary>\r
752                 protected virtual Type GetRowType () \r
753                 {\r
754                         return typeof (DataRow);\r
755                 }\r
756 \r
757                 /// <summary>\r
758                 /// This member is only meant to support Mono's infrastructure \r
759                 /// \r
760                 /// Used for Data Binding between System.Web.UI. controls \r
761                 /// like a DataGrid\r
762                 /// or\r
763                 /// System.Windows.Forms controls like a DataGrid\r
764                 /// </summary>\r
765                 IList IListSource.GetList () \r
766                 {\r
767                         IList list = (IList) _defaultView;\r
768                         return list;\r
769                 }\r
770                                 \r
771                 /// <summary>\r
772                 /// Copies a DataRow into a DataTable, preserving any \r
773                 /// property settings, as well as original and current values.\r
774                 /// </summary>\r
775                 [MonoTODO]\r
776                 public void ImportRow (DataRow row) \r
777                 {\r
778                         DataRow newRow = NewRow();\r
779                         Rows.Add(newRow);\r
780                         row.CopyValuesToRow(newRow);\r
781                         \r
782                 }\r
783 \r
784                 /// <summary>\r
785                 /// This member is only meant to support Mono's infrastructure          \r
786                 /// </summary>\r
787                 [MonoTODO]\r
788                 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context) \r
789                 {\r
790                 }\r
791 \r
792                 /// <summary>\r
793                 /// Finds and updates a specific row. If no matching row\r
794                 ///  is found, a new row is created using the given values.\r
795                 /// </summary>\r
796                 [MonoTODO]\r
797                 public DataRow LoadDataRow (object[] values, bool fAcceptChanges) \r
798                 {\r
799                         DataRow row = null;\r
800                         if (PrimaryKey.Length == 0) {\r
801                                 row = Rows.Add (values);\r
802                                 if (fAcceptChanges)\r
803                                         row.AcceptChanges ();\r
804                         }\r
805                         else {\r
806                                 bool hasPrimaryValues = true;\r
807                                 // initiate an array that has the values of the primary keys.\r
808                                 object[] keyValues = new object[PrimaryKey.Length];\r
809                                 for (int i = 0; i < keyValues.Length && hasPrimaryValues; i++)\r
810                                 {\r
811                                         if(PrimaryKey[i].Ordinal < values.Length)\r
812                                                 keyValues[i] = values[PrimaryKey[i].Ordinal];\r
813                                         else\r
814                                                 hasPrimaryValues = false;\r
815                                 }\r
816                                 \r
817                                 if (hasPrimaryValues){\r
818                                         // find the row in the table.\r
819                                         row = Rows.Find(keyValues);\r
820                                 }\r
821                                 \r
822                                 if (row == null)\r
823                                         row = Rows.Add (values);\r
824                                 else\r
825                                         row.ItemArray = values;\r
826                                 \r
827                                 if (fAcceptChanges)\r
828                                         row.AcceptChanges ();\r
829                         }\r
830                                 \r
831                         return row;\r
832                 }\r
833 \r
834                 /// <summary>\r
835                 /// Creates a new DataRow with the same schema as the table.\r
836                 /// </summary>\r
837                 public DataRow NewRow () \r
838                 {\r
839                         return this.NewRowFromBuilder (new DataRowBuilder (this, 0, 0));\r
840                 }\r
841 \r
842                 /// <summary>\r
843                 /// This member supports the .NET Framework infrastructure\r
844                 ///  and is not intended to be used directly from your code.\r
845                 /// </summary>\r
846                 protected internal DataRow[] NewRowArray (int size) \r
847                 {\r
848                         return (DataRow[]) Array.CreateInstance (GetRowType (), size);\r
849                 }\r
850 \r
851                 /// <summary>\r
852                 /// Creates a new row from an existing row.\r
853                 /// </summary>\r
854                 protected virtual DataRow NewRowFromBuilder (DataRowBuilder builder) \r
855                 {\r
856                         return new DataRow (builder);\r
857                 }\r
858         \r
859 \r
860                 /// <summary>\r
861                 /// Rolls back all changes that have been made to the \r
862                 /// table since it was loaded, or the last time AcceptChanges\r
863                 ///  was called.\r
864                 /// </summary>\r
865                 [MonoTODO]\r
866                 public void RejectChanges () \r
867                 {       \r
868                         for (int i = _rows.Count - 1; i >= 0; i--) {\r
869                                 DataRow row = _rows [i];\r
870                                 if (row.RowState != DataRowState.Unchanged)\r
871                                         _rows [i].RejectChanges ();\r
872                         }\r
873                 }\r
874 \r
875                 /// <summary>\r
876                 /// Resets the DataTable to its original state.\r
877                 /// </summary>          \r
878                 [MonoTODO]\r
879                 public virtual void Reset () \r
880                 {\r
881                         Clear();\r
882                         while (ParentRelations.Count > 0)\r
883                         {\r
884                                 if (dataSet.Relations.Contains(ParentRelations[ParentRelations.Count - 1].RelationName))\r
885                                         dataSet.Relations.Remove(ParentRelations[ParentRelations.Count - 1]);\r
886                         }\r
887 \r
888                         while (ChildRelations.Count > 0)\r
889                         {\r
890                                 if (dataSet.Relations.Contains(ChildRelations[ChildRelations.Count - 1].RelationName))\r
891                                         dataSet.Relations.Remove(ChildRelations[ChildRelations.Count - 1]);\r
892                         }\r
893                         Constraints.Clear();\r
894                         Columns.Clear();\r
895                 }\r
896 \r
897                 /// <summary>\r
898                 /// Gets an array of all DataRow objects.\r
899                 /// </summary>\r
900                 public DataRow[] Select () \r
901                 {\r
902                         return Select(String.Empty, String.Empty, DataViewRowState.CurrentRows);\r
903                 }\r
904 \r
905                 /// <summary>\r
906                 /// Gets an array of all DataRow objects that match \r
907                 /// the filter criteria in order of primary key (or \r
908                 /// lacking one, order of addition.)\r
909                 /// </summary>\r
910                 public DataRow[] Select (string filterExpression) \r
911                 {\r
912                         return Select(filterExpression, String.Empty, DataViewRowState.CurrentRows);\r
913                 }\r
914 \r
915                 /// <summary>\r
916                 /// Gets an array of all DataRow objects that \r
917                 /// match the filter criteria, in the the \r
918                 /// specified sort order.\r
919                 /// </summary>\r
920                 public DataRow[] Select (string filterExpression, string sort) \r
921                 {\r
922                         return Select(filterExpression, sort, DataViewRowState.CurrentRows);\r
923                 }\r
924 \r
925                 /// <summary>\r
926                 /// Gets an array of all DataRow objects that match\r
927                 /// the filter in the order of the sort, that match \r
928                 /// the specified state.\r
929                 /// </summary>\r
930                 [MonoTODO]\r
931                 public DataRow[] Select(string filterExpression, string sort, DataViewRowState recordStates) \r
932                 {\r
933                         DataRow[] dataRows = null;\r
934                         if (filterExpression == null)\r
935                                 filterExpression = String.Empty;\r
936 \r
937                         ExpressionElement Expression = null;\r
938                         if (filterExpression != null && filterExpression.Length != 0)\r
939                                 Expression = new ExpressionMainElement(filterExpression);\r
940 \r
941                         ArrayList List = new ArrayList();\r
942                         int recordStateFilter = GetRowStateFilter(recordStates);\r
943                         foreach (DataRow Row in Rows) \r
944                         {\r
945                                 if (((int)Row.RowState & recordStateFilter) != 0)\r
946                                 {\r
947                                         if (Expression == null || Expression.Test(Row))\r
948                                                 List.Add(Row);\r
949                                 }\r
950                         }\r
951 \r
952                         dataRows = (DataRow[])List.ToArray(typeof(DataRow));\r
953                         \r
954 \r
955                         if (sort != null && !sort.Equals(String.Empty)) \r
956                         {\r
957                                 SortableColumn[] sortableColumns = null;\r
958 \r
959                                 sortableColumns = ParseTheSortString (sort);\r
960                                 if (sortableColumns == null)\r
961                                         throw new Exception ("sort expression result is null");\r
962                                 if (sortableColumns.Length == 0)\r
963                                         throw new Exception("sort expression result is 0");\r
964 \r
965                                 RowSorter rowSorter = new RowSorter (dataRows, sortableColumns);\r
966                                 dataRows = rowSorter.SortRows ();\r
967 \r
968                                 sortableColumns = null;\r
969                                 rowSorter = null;\r
970                         }\r
971 \r
972                         \r
973                         return dataRows;\r
974                 }\r
975 \r
976                 private static int GetRowStateFilter(DataViewRowState recordStates)\r
977                 {\r
978                         int flag = 0;\r
979 \r
980                         if ((recordStates & DataViewRowState.Added) != 0)\r
981                                 flag |= (int)DataRowState.Added;\r
982                         if ((recordStates & DataViewRowState.Deleted) != 0)\r
983                                 flag |= (int)DataRowState.Deleted;\r
984                         if ((recordStates & DataViewRowState.ModifiedCurrent) != 0)\r
985                                 flag |= (int)DataRowState.Modified;\r
986                         if ((recordStates & DataViewRowState.ModifiedOriginal) != 0)\r
987                                 flag |= (int)DataRowState.Modified;\r
988                         if ((recordStates & DataViewRowState.Unchanged) != 0)\r
989                                 flag |= (int)DataRowState.Unchanged;\r
990 \r
991                         return flag;\r
992                 }\r
993 \r
994                 /// <summary>\r
995                 /// Gets the TableName and DisplayExpression, if \r
996                 /// there is one as a concatenated string.\r
997                 /// </summary>\r
998                 public override string ToString() \r
999                 {\r
1000                         //LAMESPEC: spec says concat the two. impl puts a \r
1001                         //plus sign infront of DisplayExpression\r
1002                         string retVal = TableName;\r
1003                         if(DisplayExpression != null && DisplayExpression != "")\r
1004                                 retVal += " + " + DisplayExpression;\r
1005                         return retVal;\r
1006                 }\r
1007 \r
1008                 \r
1009                 #region Events /////////////////\r
1010                 \r
1011                 /// <summary>\r
1012                 /// Raises the ColumnChanged event.\r
1013                 /// </summary>\r
1014                 protected virtual void OnColumnChanged (DataColumnChangeEventArgs e) {\r
1015                         if (null != ColumnChanged) {\r
1016                                 ColumnChanged (this, e);\r
1017                         }\r
1018                 }\r
1019 \r
1020                 /// <summary>\r
1021                 /// Raises the ColumnChanging event.\r
1022                 /// </summary>\r
1023                 protected virtual void OnColumnChanging (DataColumnChangeEventArgs e) {\r
1024                         if (null != ColumnChanging) {\r
1025                                 ColumnChanging (this, e);\r
1026                         }\r
1027                 }\r
1028 \r
1029                 /// <summary>\r
1030                 /// Raises the PropertyChanging event.\r
1031                 /// </summary>\r
1032                 [MonoTODO]\r
1033                 protected internal virtual void OnPropertyChanging (PropertyChangedEventArgs pcevent) {\r
1034                         //      if (null != PropertyChanging)\r
1035                         //      {\r
1036                         //              PropertyChanging (this, e);\r
1037                         //      }\r
1038                 }\r
1039 \r
1040                 /// <summary>\r
1041                 /// Notifies the DataTable that a DataColumn is being removed.\r
1042                 /// </summary>\r
1043                 [MonoTODO]\r
1044                 protected internal virtual void OnRemoveColumn (DataColumn column) {\r
1045                         //      if (null != RemoveColumn)\r
1046                         //      {\r
1047                         //              RemoveColumn(this, e);\r
1048                         //      }\r
1049                 }\r
1050 \r
1051                 /// <summary>\r
1052                 /// Raises the RowChanged event.\r
1053                 /// </summary>\r
1054                 protected virtual void OnRowChanged (DataRowChangeEventArgs e) {\r
1055                         if (null != RowChanged) {\r
1056                                 RowChanged(this, e);\r
1057                         }\r
1058                 }\r
1059 \r
1060 \r
1061                 /// <summary>\r
1062                 /// Raises the RowChanging event.\r
1063                 /// </summary>\r
1064                 protected virtual void OnRowChanging (DataRowChangeEventArgs e) {\r
1065                         if (null != RowChanging) {\r
1066                                 RowChanging(this, e);\r
1067                         }\r
1068                 }\r
1069 \r
1070                 /// <summary>\r
1071                 /// Raises the RowDeleted event.\r
1072                 /// </summary>\r
1073                 protected virtual void OnRowDeleted (DataRowChangeEventArgs e) {\r
1074                         if (null != RowDeleted) {\r
1075                                 RowDeleted(this, e);\r
1076                         }\r
1077                 }\r
1078 \r
1079                 /// <summary>\r
1080                 /// Raises the RowDeleting event.\r
1081                 /// </summary>\r
1082                 protected virtual void OnRowDeleting (DataRowChangeEventArgs e) {\r
1083                         if (null != RowDeleting) {\r
1084                                 RowDeleting(this, e);\r
1085                         }\r
1086                 }\r
1087 \r
1088                 [MonoTODO]\r
1089                 private DataColumn CopyColumn (DataColumn Column) {\r
1090                         DataColumn Copy = new DataColumn ();\r
1091 \r
1092                         // Copy all the properties of column\r
1093                         Copy.AllowDBNull = Column.AllowDBNull;\r
1094                         Copy.AutoIncrement = Column.AutoIncrement;\r
1095                         Copy.AutoIncrementSeed = Column.AutoIncrementSeed;\r
1096                         Copy.AutoIncrementStep = Column.AutoIncrementStep;\r
1097                         Copy.Caption = Column.Caption;\r
1098                         Copy.ColumnMapping = Column.ColumnMapping;\r
1099                         Copy.ColumnName = Column.ColumnName;\r
1100                         //Copy.Container\r
1101                         Copy.DataType = Column.DataType;\r
1102                         Copy.DefaultValue = Column.DefaultValue;                        \r
1103                         Copy.Expression = Column.Expression;\r
1104                         //Copy.ExtendedProperties\r
1105                         Copy.MaxLength = Column.MaxLength;\r
1106                         Copy.Namespace = Column.Namespace;\r
1107                         Copy.Prefix = Column.Prefix;\r
1108                         Copy.ReadOnly = Column.ReadOnly;\r
1109                         //Copy.Site\r
1110                         //we do not copy the unique value - it will be copyied when copying the constraints.\r
1111                         //Copy.Unique = Column.Unique;\r
1112                         \r
1113                         return Copy;\r
1114                 }                       \r
1115 \r
1116                 /// <summary>\r
1117                 /// Occurs when after a value has been changed for \r
1118                 /// the specified DataColumn in a DataRow.\r
1119                 /// </summary>\r
1120                 [DataCategory ("Data")] \r
1121                 [DataSysDescription ("Occurs when a value has been changed for this column.")]\r
1122                 public event DataColumnChangeEventHandler ColumnChanged;\r
1123 \r
1124                 /// <summary>\r
1125                 /// Occurs when a value is being changed for the specified \r
1126                 /// DataColumn in a DataRow.\r
1127                 /// </summary>\r
1128                 [DataCategory ("Data")]\r
1129                 [DataSysDescription ("Occurs when a value has been submitted for this column. The user can modify the proposed value and should throw an exception to cancel the edit.")]\r
1130                 public event DataColumnChangeEventHandler ColumnChanging;\r
1131 \r
1132                 /// <summary>\r
1133                 /// Occurs after a DataRow has been changed successfully.\r
1134                 /// </summary>\r
1135                 [DataCategory ("Data")] \r
1136                 [DataSysDescription ("Occurs after a row in the table has been successfully edited.")]\r
1137                 public event DataRowChangeEventHandler RowChanged;\r
1138 \r
1139                 /// <summary>\r
1140                 /// Occurs when a DataRow is changing.\r
1141                 /// </summary>\r
1142                 [DataCategory ("Data")] \r
1143                 [DataSysDescription ("Occurs when the row is being changed so that the event handler can modify or cancel the change. The user can modify values in the row and should throw an  exception to cancel the edit.")]\r
1144                 public event DataRowChangeEventHandler RowChanging;\r
1145 \r
1146                 /// <summary>\r
1147                 /// Occurs after a row in the table has been deleted.\r
1148                 /// </summary>\r
1149                 [DataCategory ("Data")] \r
1150                 [DataSysDescription ("Occurs after a row in the table has been successfully deleted.")] \r
1151                 public event DataRowChangeEventHandler RowDeleted;\r
1152 \r
1153                 /// <summary>\r
1154                 /// Occurs before a row in the table is about to be deleted.\r
1155                 /// </summary>\r
1156                 [DataCategory ("Data")] \r
1157                 [DataSysDescription ("Occurs when a row in the table marked for deletion. Throw an exception to cancel the deletion.")]\r
1158                 public event DataRowChangeEventHandler RowDeleting;\r
1159                 \r
1160                 #endregion // Events\r
1161 \r
1162                 /// <summary>\r
1163                 ///  Removes all UniqueConstraints\r
1164                 /// </summary>\r
1165                 private void RemoveUniqueConstraints () \r
1166                 {\r
1167                         foreach (Constraint Cons in Constraints) {\r
1168                                 \r
1169                                 if (Cons is UniqueConstraint) {\r
1170                                         Constraints.Remove (Cons);\r
1171                                         break;\r
1172                                 }\r
1173                         }\r
1174                         \r
1175                         UniqueConstraint.SetAsPrimaryKey(this.Constraints, null);\r
1176                 }\r
1177 \r
1178                 // to parse the sort string for DataTable:Select(expression,sort)\r
1179                 // into sortable columns (think ORDER BY, \r
1180                 // such as, "customer ASC, price DESC" )\r
1181                 private SortableColumn[] ParseTheSortString (string sort) \r
1182                 {\r
1183                         SortableColumn[] sortColumns = null;\r
1184                         ArrayList columns = null;\r
1185                 \r
1186                         if (sort != null && !sort.Equals ("")) {\r
1187                                 columns = new ArrayList ();\r
1188                                 string[] columnExpression = sort.Trim ().Split (new char[1] {','});\r
1189                         \r
1190                                 for (int c = 0; c < columnExpression.Length; c++) {\r
1191                                         string[] columnSortInfo = columnExpression[c].Trim ().Split (new char[1] {' '});\r
1192                                 \r
1193                                         string columnName = columnSortInfo[0].Trim ();\r
1194                                         string sortOrder = "ASC";\r
1195                                         if (columnSortInfo.Length > 1) \r
1196                                                 sortOrder = columnSortInfo[1].Trim ().ToUpper ();\r
1197                                         \r
1198                                         ListSortDirection sortDirection = ListSortDirection.Ascending;\r
1199                                         switch (sortOrder) {\r
1200                                         case "ASC":\r
1201                                                 sortDirection = ListSortDirection.Ascending;\r
1202                                                 break;\r
1203                                         case "DESC":\r
1204                                                 sortDirection = ListSortDirection.Descending;\r
1205                                                 break;\r
1206                                         default:\r
1207                                                 throw new IndexOutOfRangeException ("Could not find column: " + columnExpression[c]);\r
1208                                         }\r
1209                                         Int32 ord = 0;\r
1210                                         try {\r
1211                                                 ord = Int32.Parse (columnName);\r
1212                                         }\r
1213                                         catch (FormatException) {\r
1214                                                 ord = -1;\r
1215                                         }\r
1216                                         DataColumn dc = null;\r
1217                                         if (ord == -1)                          \r
1218                                                 dc = _columnCollection[columnName];\r
1219                                         else\r
1220                                                 dc = _columnCollection[ord];\r
1221                                         SortableColumn sortCol = new SortableColumn (dc,sortDirection);\r
1222                                         columns.Add (sortCol);\r
1223                                 }       \r
1224                                 sortColumns = (SortableColumn[]) columns.ToArray (typeof (SortableColumn));\r
1225                         }               \r
1226                         return sortColumns;\r
1227                 }\r
1228         \r
1229                 private class SortableColumn \r
1230                 {\r
1231                         private DataColumn col;\r
1232                         private ListSortDirection dir;\r
1233 \r
1234                         internal SortableColumn (DataColumn column, \r
1235                                                 ListSortDirection direction) \r
1236                         {\r
1237                                 col = column;\r
1238                                 dir = direction;\r
1239                         }\r
1240 \r
1241                         public DataColumn Column {\r
1242                                 get {\r
1243                                         return col;\r
1244                                 }\r
1245                         }\r
1246 \r
1247                         public ListSortDirection SortDirection {\r
1248                                 get {\r
1249                                         return dir;\r
1250                                 }\r
1251                         }\r
1252                 }\r
1253 \r
1254                 private class RowSorter : IComparer \r
1255                 {\r
1256                         private SortableColumn[] sortColumns;\r
1257                         private DataRow[] rowsToSort;\r
1258                         \r
1259                         internal RowSorter(DataRow[] unsortedRows, \r
1260                                         SortableColumn[] sortColumns) \r
1261                         {\r
1262                                 this.sortColumns = sortColumns;\r
1263                                 this.rowsToSort = unsortedRows;\r
1264                         }\r
1265 \r
1266                         public SortableColumn[] SortColumns {\r
1267                                 get {\r
1268                                         return sortColumns;\r
1269                                 }\r
1270                         }\r
1271                         \r
1272                         public DataRow[] SortRows () \r
1273                         {\r
1274                                 Array.Sort (rowsToSort, this);\r
1275                                 return rowsToSort;\r
1276                         }\r
1277 \r
1278                         int IComparer.Compare (object x, object y) \r
1279                         {\r
1280                                 if(x == null)\r
1281                                         throw new Exception ("Object to compare is null: x");\r
1282                                 if(y == null)\r
1283                                         throw new Exception ("Object to compare is null: y");\r
1284                                 if(!(x is DataRow))\r
1285                                         throw new Exception ("Object to compare is not DataRow: x is " + x.GetType().ToString());\r
1286                                 if(!(y is DataRow))\r
1287                                         throw new Exception ("Object to compare is not DataRow: y is " + x.GetType().ToString());\r
1288 \r
1289                                 DataRow rowx = (DataRow) x;\r
1290                                 DataRow rowy = (DataRow) y;\r
1291 \r
1292                                 for(int i = 0; i < sortColumns.Length; i++) {\r
1293                                         SortableColumn sortColumn = sortColumns[i];\r
1294                                         DataColumn dc = sortColumn.Column;\r
1295 \r
1296                                         IComparable objx = (IComparable) rowx[dc];\r
1297                                         object objy = rowy[dc];\r
1298 \r
1299                                         int result = CompareObjects (objx, objy);\r
1300                                         if (result != 0) {\r
1301                                                 if (sortColumn.SortDirection == ListSortDirection.Ascending) {\r
1302                                                         return result;\r
1303                                                 }\r
1304                                                 else {\r
1305                                                         return -result;\r
1306                                                 }\r
1307                                         }\r
1308                                 }\r
1309                                 return 0;\r
1310                         }\r
1311 \r
1312                         private int CompareObjects (object a, object b) \r
1313                         {\r
1314                                 if (a == b)\r
1315                                         return 0;\r
1316                                 else if (a == null)\r
1317                                         return -1;\r
1318                                 else if (a == DBNull.Value)\r
1319                                         return -1;\r
1320                                 else if (b == null)\r
1321                                         return 1;\r
1322                                 else if (b == DBNull.Value)\r
1323                                         return 1;\r
1324 \r
1325                                 if((a is string) && (b is string)) {\r
1326                                         a = ((string) a).ToUpper ();\r
1327                                         b = ((string) b).ToUpper ();                    \r
1328                                 }\r
1329 \r
1330                                 if (a is IComparable)\r
1331                                         return ((a as IComparable).CompareTo (b));\r
1332                                 else if (b is IComparable)\r
1333                                         return -((b as IComparable).CompareTo (a));\r
1334 \r
1335                                 throw new ArgumentException ("Neither a nor b IComparable");\r
1336                         }\r
1337                 }\r
1338         }\r
1339 }\r