64be0023c72b4f2e7a29f60d210e0710ef201348
[mono.git] / mcs / class / System.Data / System.Data / DataRow.cs
1 //\r
2 // System.Data.DataRow.cs\r
3 //\r
4 // Author:\r
5 //   Rodrigo Moya <rodrigo@ximian.com>\r
6 //   Daniel Morgan <danmorg@sc.rr.com>\r
7 //   Tim Coleman <tim@timcoleman.com>\r
8 //   Ville Palo <vi64pa@koti.soon.fi>\r
9 //   Alan Tam Siu Lung <Tam@SiuLung.com>\r
10 //\r
11 // (C) Ximian, Inc 2002\r
12 // (C) Daniel Morgan 2002, 2003\r
13 // Copyright (C) 2002 Tim Coleman\r
14 //\r
15 \r
16 using System;\r
17 using System.Collections;\r
18 using System.Globalization;\r
19 \r
20 namespace System.Data {\r
21         /// <summary>\r
22         /// Represents a row of data in a DataTable.\r
23         /// </summary>\r
24         [Serializable]\r
25         public class DataRow\r
26         {\r
27                 #region Fields\r
28 \r
29                 private DataTable _table;\r
30 \r
31                 private object[] original;\r
32                 private object[] proposed;\r
33                 private object[] current;\r
34 \r
35                 private string[] columnErrors;\r
36                 private string rowError;\r
37                 private DataRowState rowState;\r
38                 internal int xmlRowID = 0;\r
39                 internal bool _nullConstraintViolation;\r
40                 private bool editing = false;\r
41                 private bool _hasParentCollection;\r
42 \r
43                 #endregion\r
44 \r
45                 #region Constructors\r
46 \r
47                 /// <summary>\r
48                 /// This member supports the .NET Framework infrastructure and is not intended to be \r
49                 /// used directly from your code.\r
50                 /// </summary>\r
51                 protected internal DataRow (DataRowBuilder builder)\r
52                 {\r
53                         _table = builder.Table;\r
54 \r
55                         original = null; \r
56                         \r
57                         proposed = new object[_table.Columns.Count];\r
58                         for (int c = 0; c < _table.Columns.Count; c++) \r
59                         {\r
60                                 proposed[c] = DBNull.Value;\r
61                         }\r
62                         \r
63                         columnErrors = new string[_table.Columns.Count];\r
64                         rowError = String.Empty;\r
65 \r
66                         //on first creating a DataRow it is always detached.\r
67                         rowState = DataRowState.Detached;\r
68                         \r
69                         foreach (DataColumn Col in _table.Columns) {\r
70                                 \r
71                                 if (Col.AutoIncrement) {\r
72                                         this [Col] = Col.AutoIncrementValue();\r
73                                 }\r
74                         }\r
75                         _table.Columns.CollectionChanged += new System.ComponentModel.CollectionChangeEventHandler(CollectionChanged);\r
76                 }\r
77 \r
78                 \r
79                 #endregion\r
80 \r
81                 #region Properties\r
82 \r
83                 /// <summary>\r
84                 /// Gets a value indicating whether there are errors in a row.\r
85                 /// </summary>\r
86                 public bool HasErrors {\r
87                         [MonoTODO]\r
88                         get {\r
89                                 if (RowError != string.Empty)\r
90                                         return true;\r
91 \r
92                                 for (int i= 0; i < columnErrors.Length; i++){\r
93                                         if (columnErrors[i] != null && columnErrors[i] != string.Empty)\r
94                                                 return true;\r
95                                 }\r
96 \r
97                                 return false;\r
98                         }\r
99                 }\r
100 \r
101                 /// <summary>\r
102                 /// Gets or sets the data stored in the column specified by name.\r
103                 /// </summary>\r
104                 public object this[string columnName] {\r
105                         get { return this[columnName, DataRowVersion.Default]; }\r
106                         set {\r
107                                 int columnIndex = _table.Columns.IndexOf (columnName);\r
108                                 if (columnIndex == -1)\r
109                                         throw new IndexOutOfRangeException ();\r
110                                 this[columnIndex] = value;\r
111                         }\r
112                 }\r
113 \r
114                 /// <summary>\r
115                 /// Gets or sets the data stored in specified DataColumn\r
116                 /// </summary>\r
117                 public object this[DataColumn column] {\r
118 \r
119                         get {\r
120                                 return this[column, DataRowVersion.Default];} \r
121                         set {\r
122                                 int columnIndex = _table.Columns.IndexOf (column);\r
123                                 if (columnIndex == -1)\r
124                                         throw new ArgumentException ("The column does not belong to this table.");\r
125                                 this[columnIndex] = value;\r
126                         }\r
127                 }\r
128 \r
129                 /// <summary>\r
130                 /// Gets or sets the data stored in column specified by index.\r
131                 /// </summary>\r
132                 public object this[int columnIndex] {\r
133                         get { return this[columnIndex, DataRowVersion.Default]; }\r
134                         set {\r
135                                 if (columnIndex < 0 || columnIndex > _table.Columns.Count)\r
136                                         throw new IndexOutOfRangeException ();\r
137                                 if (rowState == DataRowState.Deleted)\r
138                                         throw new DeletedRowInaccessibleException ();\r
139                                 DataColumn column = _table.Columns[columnIndex];\r
140                                 _table.ChangingDataColumn (this, column, value);\r
141                                 \r
142                                 \r
143                                 bool orginalEditing = editing;\r
144                                 if (!orginalEditing) BeginEdit ();\r
145                                 object v = SetColumnValue (value, columnIndex);\r
146                                 proposed[columnIndex] = v;\r
147                                 _table.ChangedDataColumn (this, column, v);\r
148                                 if (!orginalEditing) EndEdit ();\r
149                         }\r
150                 }\r
151 \r
152                 /// <summary>\r
153                 /// Gets the specified version of data stored in the named column.\r
154                 /// </summary>\r
155                 public object this[string columnName, DataRowVersion version] {\r
156                         get {\r
157                                 int columnIndex = _table.Columns.IndexOf (columnName);\r
158                                 if (columnIndex == -1)\r
159                                         throw new IndexOutOfRangeException ();\r
160                                 return this[columnIndex, version];\r
161                         }\r
162                 }\r
163 \r
164                 /// <summary>\r
165                 /// Gets the specified version of data stored in the specified DataColumn.\r
166                 /// </summary>\r
167                 public object this[DataColumn column, DataRowVersion version] {\r
168                         get {\r
169                                 int columnIndex = _table.Columns.IndexOf (column);\r
170                                 if (columnIndex == -1)\r
171                                         throw new ArgumentException ("The column does not belong to this table.");\r
172                                 return this[columnIndex, version];\r
173                         }\r
174                 }\r
175 \r
176                 /// <summary>\r
177                 /// Gets the data stored in the column, specified by index and version of the data to\r
178                 /// retrieve.\r
179                 /// </summary>\r
180                 public object this[int columnIndex, DataRowVersion version] {\r
181                         get {\r
182                                 if (columnIndex < 0 || columnIndex > _table.Columns.Count)\r
183                                         throw new IndexOutOfRangeException ();\r
184                                 // Non-existent version\r
185                                 if (rowState == DataRowState.Detached && version == DataRowVersion.Current || !HasVersion (version))\r
186                                         throw new VersionNotFoundException (Locale.GetText ("There is no " + version.ToString () + " data to access."));\r
187                                 // Accessing deleted rows\r
188                                 if (rowState == DataRowState.Deleted && version != DataRowVersion.Original)\r
189                                         throw new DeletedRowInaccessibleException ("Deleted row information cannot be accessed through the row.");\r
190                                 switch (version) {\r
191                                 case DataRowVersion.Default:\r
192                                         if (editing || rowState == DataRowState.Detached)\r
193                                                 return proposed[columnIndex];\r
194                                         return current[columnIndex];\r
195                                 case DataRowVersion.Proposed:\r
196                                         return proposed[columnIndex];\r
197                                 case DataRowVersion.Current:\r
198                                         return current[columnIndex];\r
199                                 case DataRowVersion.Original:\r
200                                         return original[columnIndex];\r
201                                 default:\r
202                                         throw new ArgumentException ();\r
203                                 }\r
204                         }\r
205                 }\r
206 \r
207                 /// <summary>\r
208                 /// Gets or sets all of the values for this row through an array.\r
209                 /// </summary>\r
210                 [MonoTODO]\r
211                 public object[] ItemArray {\r
212                         get { \r
213                                 return current; \r
214                         }\r
215                         set {\r
216                                 if (value.Length > _table.Columns.Count)\r
217                                         throw new ArgumentException ();\r
218 \r
219                                 if (rowState == DataRowState.Deleted)\r
220                                         throw new DeletedRowInaccessibleException ();\r
221                                 \r
222                                 object[] newItems = new object[_table.Columns.Count];                   \r
223                                 object v = null;\r
224                                 for (int i = 0; i < _table.Columns.Count; i++) {\r
225 \r
226                                         if (i < value.Length)\r
227                                                 v = value[i];\r
228                                         else\r
229                                                 v = null;\r
230 \r
231                                         newItems[i] = SetColumnValue (v, i);\r
232                                 }\r
233 \r
234                                 bool orginalEditing = editing;\r
235                                 if (!orginalEditing) BeginEdit ();\r
236                                 proposed = newItems;\r
237                                 if (!orginalEditing) EndEdit ();\r
238                         }\r
239                 }\r
240 \r
241                 private object SetColumnValue (object v, int index) \r
242                 {               \r
243                         object newval = null;\r
244                         DataColumn col = _table.Columns[index];\r
245                         \r
246                         if (_hasParentCollection && col.ReadOnly && v != this[index])\r
247                                 throw new ReadOnlyException ();\r
248 \r
249                         if (v == null)\r
250                         {\r
251                                 if(col.DefaultValue != DBNull.Value) \r
252                                 {\r
253                                         newval = col.DefaultValue;\r
254                                 }\r
255                                 else if(col.AutoIncrement == true) \r
256                                 {\r
257                                         newval = this [index];\r
258                                 }\r
259                                 else \r
260                                 {\r
261                                         if (!col.AllowDBNull)\r
262                                         {\r
263                                                 if (!this._table._duringDataLoad)\r
264                                                 {\r
265                                                         throw new NoNullAllowedException ();\r
266                                                 }\r
267                                                 else \r
268                                                 {\r
269                                                         //Constraint violations during data load is raise in DataTable EndLoad\r
270                                                         this._nullConstraintViolation = true;\r
271                                                         \r
272                                                 }\r
273                                         }\r
274 \r
275                                         newval= DBNull.Value;\r
276                                         \r
277                                 \r
278                                 }\r
279                         }               \r
280                         else if (v == DBNull.Value) \r
281                         {\r
282                                 \r
283                                 if (!col.AllowDBNull)\r
284                                 {\r
285                                         if (!this._table._duringDataLoad)\r
286                                         {\r
287                                                 throw new NoNullAllowedException ();\r
288                                         }\r
289                                         else \r
290                                         {\r
291                                                 //Constraint violations during data load is raise in DataTable EndLoad\r
292                                                 this._nullConstraintViolation = true;\r
293                                                         \r
294                                         }\r
295                                 }\r
296                                 newval= DBNull.Value;\r
297                         }\r
298                         else \r
299                         {       \r
300                                 Type vType = v.GetType(); // data type of value\r
301                                 Type cType = col.DataType; // column data type\r
302                                 if (cType != vType) \r
303                                 {\r
304                                         TypeCode typeCode = Type.GetTypeCode(cType);\r
305                                         switch(typeCode) {\r
306                                         case TypeCode.Boolean :\r
307                                                 v = Convert.ToBoolean (v);\r
308                                                 break;\r
309                                         case TypeCode.Byte  :\r
310                                                 v = Convert.ToByte (v);\r
311                                                 break;\r
312                                         case TypeCode.Char  :\r
313                                                 v = Convert.ToChar (v);\r
314                                                 break;\r
315                                         case TypeCode.DateTime  :\r
316                                                 v = Convert.ToDateTime (v);\r
317                                                 break;\r
318                                         case TypeCode.Decimal  :\r
319                                                 v = Convert.ToDecimal (v);\r
320                                                 break;\r
321                                         case TypeCode.Double  :\r
322                                                 v = Convert.ToDouble (v);\r
323                                                 break;\r
324                                         case TypeCode.Int16  :\r
325                                                 v = Convert.ToInt16 (v);\r
326                                                 break;\r
327                                         case TypeCode.Int32  :\r
328                                                 v = Convert.ToInt32 (v);\r
329                                                 break;\r
330                                         case TypeCode.Int64  :\r
331                                                 v = Convert.ToInt64 (v);\r
332                                                 break;\r
333                                         case TypeCode.SByte  :\r
334                                                 v = Convert.ToSByte (v);\r
335                                                 break;\r
336                                         case TypeCode.Single  :\r
337                                                 v = Convert.ToSingle (v);\r
338                                                 break;\r
339                                         case TypeCode.String  :\r
340                                                 v = Convert.ToString (v);\r
341                                                 break;\r
342                                         case TypeCode.UInt16  :\r
343                                                 v = Convert.ToUInt16 (v);\r
344                                                 break;\r
345                                         case TypeCode.UInt32  :\r
346                                                 v = Convert.ToUInt32 (v);\r
347                                                 break;\r
348                                         case TypeCode.UInt64  :\r
349                                                 v = Convert.ToUInt64 (v);\r
350                                                 break;\r
351                                         default :\r
352                                         switch(cType.ToString()) {\r
353                                                 case "System.TimeSpan" :\r
354                                                         v = (System.TimeSpan) v;\r
355                                                         break;\r
356                                                 case "System.Type" :\r
357                                                         v = (System.Type) v;\r
358                                                         break;\r
359                                                 case "System.Object" :\r
360                                                         //v = (System.Object) v;\r
361                                                         break;\r
362                                                 default:\r
363                                                         // FIXME: is exception correct?\r
364                                                         throw new InvalidCastException("Type not supported.");\r
365                                         }\r
366                                                 break;\r
367                                 }\r
368                                 vType = v.GetType();\r
369                                 }\r
370                                 newval = v;\r
371                                 if(col.AutoIncrement == true) {\r
372                                         long inc = Convert.ToInt64(v);\r
373                                         col.UpdateAutoIncrementValue (inc);\r
374                                 }\r
375                         }\r
376                         col.DataHasBeenSet = true;\r
377                         return newval;\r
378                 }\r
379 \r
380                 /// <summary>\r
381                 /// Gets or sets the custom error description for a row.\r
382                 /// </summary>\r
383                 public string RowError {\r
384                         get { return rowError; }\r
385                         set { rowError = value; }\r
386                 }\r
387 \r
388                 /// <summary>\r
389                 /// Gets the current state of the row in regards to its relationship to the\r
390                 /// DataRowCollection.\r
391                 /// </summary>\r
392                 public DataRowState RowState {\r
393                         get { return rowState; }\r
394                 }\r
395 \r
396                 //FIXME?: Couldn't find a way to set the RowState when adding the DataRow\r
397                 //to a Datatable so I added this method. Delete if there is a better way.\r
398                 internal void AttachRow() {\r
399                         current = proposed;\r
400                         proposed = null;\r
401                         rowState = DataRowState.Added;\r
402                 }\r
403 \r
404                 //FIXME?: Couldn't find a way to set the RowState when removing the DataRow\r
405                 //from a Datatable so I added this method. Delete if there is a better way.\r
406                 internal void DetachRow() {\r
407                         proposed = null;\r
408                         _hasParentCollection = false;\r
409                         rowState = DataRowState.Detached;\r
410                 }\r
411 \r
412                 /// <summary>\r
413                 /// Gets the DataTable for which this row has a schema.\r
414                 /// </summary>\r
415                 public DataTable Table {\r
416                         get { return _table; }\r
417                 }\r
418 \r
419                 /// <summary>\r
420                 /// Gets and sets index of row. This is used from \r
421                 /// XmlDataDocument.\r
422                 // </summary>\r
423                 internal int XmlRowID {\r
424                         get { return xmlRowID; }\r
425                         set { xmlRowID = value; }\r
426                 }\r
427 \r
428                 #endregion\r
429 \r
430                 #region Methods\r
431 \r
432                 /// <summary>\r
433                 /// Commits all the changes made to this row since the last time AcceptChanges was\r
434                 /// called.\r
435                 /// </summary>\r
436                 public void AcceptChanges () \r
437                 {\r
438                         EndEdit(); // in case it hasn't been called\r
439                         switch (rowState) {\r
440                         case DataRowState.Added:\r
441                         case DataRowState.Modified:\r
442                                 rowState = DataRowState.Unchanged;\r
443                                 break;\r
444                         case DataRowState.Deleted:\r
445                                 _table.Rows.Remove (this);\r
446                                 break;\r
447                         case DataRowState.Detached:\r
448                                 throw new RowNotInTableException("Cannot perform this operation on a row not in the table.");\r
449                         }\r
450                         // Accept from detached\r
451                         if (original == null)\r
452                                 original = new object[_table.Columns.Count];\r
453                         Array.Copy (current, original, _table.Columns.Count);\r
454                 }\r
455 \r
456                 /// <summary>\r
457                 /// Begins an edit operation on a DataRow object.\r
458                 /// </summary>\r
459                 [MonoTODO]\r
460                 public void BeginEdit () \r
461                 {\r
462                         if (rowState == DataRowState.Deleted)\r
463                                 throw new DeletedRowInaccessibleException ();\r
464                         if (!HasVersion (DataRowVersion.Proposed)) {\r
465                                 proposed = new object[_table.Columns.Count];\r
466                                 Array.Copy (current, proposed, current.Length);\r
467                         }\r
468                         //TODO: Suspend validation\r
469                         editing = true;\r
470                 }\r
471 \r
472                 /// <summary>\r
473                 /// Cancels the current edit on the row.\r
474                 /// </summary>\r
475                 [MonoTODO]\r
476                 public void CancelEdit () \r
477                 {\r
478                         editing = false;\r
479                         //TODO: Events\r
480                         if (HasVersion (DataRowVersion.Proposed)) {\r
481                                 proposed = null;\r
482                                 if (rowState == DataRowState.Modified)\r
483                                     rowState = DataRowState.Unchanged;\r
484                         }\r
485                 }\r
486 \r
487                 /// <summary>\r
488                 /// Clears the errors for the row, including the RowError and errors set with\r
489                 /// SetColumnError.\r
490                 /// </summary>\r
491                 public void ClearErrors () \r
492                 {\r
493                         rowError = String.Empty;\r
494                         columnErrors = new String[_table.Columns.Count];\r
495                 }\r
496 \r
497                 /// <summary>\r
498                 /// Deletes the DataRow.\r
499                 /// </summary>\r
500                 [MonoTODO]\r
501                 public void Delete () \r
502                 {\r
503                         switch (rowState) {\r
504                         case DataRowState.Added:\r
505                                 Table.Rows.Remove (this);\r
506                                 break;\r
507                         case DataRowState.Deleted:\r
508                                 throw new DeletedRowInaccessibleException ();\r
509                         default:\r
510                                 _table.DeletingDataRow(this, DataRowAction.Delete);\r
511                                 // check what to do with child rows\r
512                                 CheckChildRows(DataRowAction.Delete);\r
513                                 rowState = DataRowState.Deleted;\r
514                                 _table.DeletedDataRow(this, DataRowAction.Delete);\r
515                                 break;\r
516                         }\r
517                 }\r
518 \r
519                 // check the child rows of this row before deleting the row.\r
520                 private void CheckChildRows(DataRowAction action)\r
521                 {\r
522                         \r
523                         // in this method we find the row that this row is in a reltion with them.\r
524                         // in shortly we find all child rows of this row.\r
525                         // then we function according to the DeleteRule of the foriegnkey.\r
526 \r
527 \r
528                         // 1. find if this row is attached to dataset.\r
529                         // 2. find if EnforceConstraints is true.\r
530                         // 3. find if there are any constraint on the table that the row is in.\r
531                         if (_table.DataSet != null && _table.DataSet.EnforceConstraints && _table.Constraints.Count > 0)\r
532                         {\r
533                                 foreach (DataTable table in _table.DataSet.Tables)\r
534                                 {\r
535                                         // loop on all constraints of the table.\r
536                                         ConstraintCollection constraintsCollection = table.Constraints;\r
537                                         for (int i = 0; i < constraintsCollection.Count; i++)\r
538                                         {\r
539                                                 ForeignKeyConstraint fk = null;\r
540                                                 if (constraintsCollection[i] is ForeignKeyConstraint)\r
541                                                 {\r
542                                                         fk = (ForeignKeyConstraint)constraintsCollection[i];\r
543                                                         if (fk.RelatedTable == _table)\r
544                                                         {\r
545                                                                 //we create a dummy relation because we do not want to duplicate code of GetChild().\r
546                                                                 // we use the dummy relation to find child rows.\r
547                                                                 DataRelation rel = new DataRelation("dummy", fk.RelatedColumns, fk.Columns, false);\r
548                                                                 Rule rule;\r
549                                                                 if (action == DataRowAction.Delete)\r
550                                                                         rule = fk.DeleteRule;\r
551                                                                 else\r
552                                                                         rule = fk.UpdateRule;\r
553                                                                 CheckChildRows(rel, action, rule);\r
554                                                         }\r
555                                                 }                       \r
556                                         }\r
557                                 }\r
558                         }\r
559                 }\r
560 \r
561                 private void CheckChildRows(DataRelation rel, DataRowAction action, Rule rule)\r
562                 {                               \r
563                         DataRow[] childRows = GetChildRows(rel);\r
564                         switch (rule)\r
565                         {\r
566                                 case Rule.Cascade:  // delete or change all relted rows.\r
567                                         if (childRows != null)\r
568                                         {\r
569                                                 for (int j = 0; j < childRows.Length; j++)\r
570                                                 {\r
571                                                         // if action is delete we delete all child rows\r
572                                                         if (action == DataRowAction.Delete)\r
573                                                         {\r
574                                                                 if (childRows[j].RowState != DataRowState.Deleted)\r
575                                                                         childRows[j].Delete();\r
576                                                         }\r
577                                                         // if action is change we change the values in the child row\r
578                                                         else if (action == DataRowAction.Change)\r
579                                                         {\r
580                                                                 // change only the values in the key columns\r
581                                                                 // set the childcolumn value to the new parent row value\r
582                                                                 for (int k = 0; k < rel.ChildColumns.Length; k++)\r
583                                                                         childRows[j][rel.ChildColumns[k]] = this[rel.ParentColumns[k], DataRowVersion.Proposed];\r
584                                                         }\r
585                                                 }\r
586                                         }\r
587                                         break;\r
588                                 case Rule.None: // throw an exception if there are any child rows.\r
589                                         if (childRows != null)\r
590                                         {\r
591                                                 for (int j = 0; j < childRows.Length; j++)\r
592                                                 {\r
593                                                         if (childRows[j].RowState != DataRowState.Deleted)\r
594                                                         {\r
595                                                                 string changeStr = "Cannot change this row because constraints are enforced on relation " + rel.RelationName +", and changing this row will strand child rows.";\r
596                                                                 string delStr = "Cannot delete this row because constraints are enforced on relation " + rel.RelationName +", and deleting this row will strand child rows.";\r
597                                                                 string message = action == DataRowAction.Delete ? delStr : changeStr;\r
598                                                                 throw new InvalidConstraintException(message);\r
599                                                         }\r
600                                                 }\r
601                                         }\r
602                                         break;\r
603                                 case Rule.SetDefault: // set the values in the child rows to the defult value of the columns.\r
604                                         if (childRows != null)\r
605                                         {\r
606                                                 for (int j = 0; j < childRows.Length; j++)\r
607                                                 {\r
608                                                         DataRow child = childRows[j];\r
609                                                         if (childRows[j].RowState != DataRowState.Deleted)\r
610                                                         {\r
611                                                                 //set only the key columns to default\r
612                                                                 for (int k = 0; k < rel.ChildColumns.Length; k++)\r
613                                                                         child[rel.ChildColumns[k]] = rel.ChildColumns[k].DefaultValue;\r
614                                                         }\r
615                                                 }\r
616                                         }\r
617                                         break;\r
618                                 case Rule.SetNull: // set the values in the child row to null.\r
619                                         if (childRows != null)\r
620                                         {\r
621                                                 for (int j = 0; j < childRows.Length; j++)\r
622                                                 {\r
623                                                         DataRow child = childRows[j];\r
624                                                         if (childRows[j].RowState != DataRowState.Deleted)\r
625                                                         {\r
626                                                                 // set only the key columns to DBNull\r
627                                                                 for (int k = 0; k < rel.ChildColumns.Length; k++)\r
628                                                                         child.SetNull(rel.ChildColumns[k]);\r
629                                                         }\r
630                                                 }\r
631                                         }\r
632                                         break;\r
633                         }\r
634 \r
635                 }\r
636 \r
637                 /// <summary>\r
638                 /// Ends the edit occurring on the row.\r
639                 /// </summary>\r
640                 [MonoTODO]\r
641                 public void EndEdit () \r
642                 {\r
643                         editing = false;\r
644                         if (rowState == DataRowState.Detached)\r
645                                 return;\r
646                         if (HasVersion (DataRowVersion.Proposed))\r
647                         {\r
648                                 _table.ChangingDataRow(this, DataRowAction.Change);\r
649                                 if (rowState == DataRowState.Unchanged)\r
650                                         rowState = DataRowState.Modified;\r
651                                 \r
652                                 //Calling next method validates UniqueConstraints\r
653                                 //and ForeignKeys.\r
654                                 try\r
655                                 {\r
656                                         if (_table.DataSet == null || _table.DataSet.EnforceConstraints)\r
657                                                 _table.Rows.ValidateDataRowInternal(this);\r
658                                 }\r
659                                 catch (Exception e)\r
660                                 {\r
661                                         proposed = null;\r
662                                         throw e;\r
663                                 }\r
664                                 // check all child rows.\r
665                                 CheckChildRows(DataRowAction.Change);\r
666                                 current = proposed;\r
667                                 proposed = null;\r
668                                 _table.ChangedDataRow(this, DataRowAction.Change);\r
669                         }\r
670                 }\r
671 \r
672                 /// <summary>\r
673                 /// Gets the child rows of this DataRow using the specified DataRelation.\r
674                 /// </summary>\r
675                 public DataRow[] GetChildRows (DataRelation relation) \r
676                 {\r
677                         return GetChildRows (relation, DataRowVersion.Current);\r
678                 }\r
679 \r
680                 /// <summary>\r
681                 /// Gets the child rows of a DataRow using the specified RelationName of a\r
682                 /// DataRelation.\r
683                 /// </summary>\r
684                 public DataRow[] GetChildRows (string relationName) \r
685                 {\r
686                         return GetChildRows (Table.DataSet.Relations[relationName]);\r
687                 }\r
688 \r
689                 /// <summary>\r
690                 /// Gets the child rows of a DataRow using the specified DataRelation, and\r
691                 /// DataRowVersion.\r
692                 /// </summary>\r
693                 public DataRow[] GetChildRows (DataRelation relation, DataRowVersion version) \r
694                 {\r
695                         if (relation == null)\r
696                                 return new DataRow[0];\r
697 \r
698                         if (this.Table == null)\r
699                                 throw new RowNotInTableException();\r
700 \r
701                         if (relation.DataSet != this.Table.DataSet)\r
702                                 throw new ArgumentException();\r
703 \r
704                         // TODO: Caching for better preformance\r
705                         ArrayList rows = new ArrayList();\r
706                         DataColumn[] parentColumns = relation.ParentColumns;\r
707                         DataColumn[] childColumns = relation.ChildColumns;\r
708                         int numColumn = parentColumns.Length;\r
709                         if (HasVersion(version)) \r
710                         {\r
711                                 foreach (DataRow row in relation.ChildTable.Rows) \r
712                                 {\r
713                                         bool allColumnsMatch = false;\r
714                                         if (row.HasVersion(DataRowVersion.Default))\r
715                                         {\r
716                                                 allColumnsMatch = true;\r
717                                                 for (int columnCnt = 0; columnCnt < numColumn; ++columnCnt) \r
718                                                 {\r
719                                                         if (!this[parentColumns[columnCnt], version].Equals(\r
720                                                                 row[childColumns[columnCnt], DataRowVersion.Default])) \r
721                                                         {\r
722                                                                 allColumnsMatch = false;\r
723                                                                 break;\r
724                                                         }\r
725                                                 }\r
726                                         }\r
727                                         if (allColumnsMatch) rows.Add(row);\r
728                                 }\r
729                         }\r
730                         return rows.ToArray(typeof(DataRow)) as DataRow[];\r
731                 }\r
732 \r
733                 /// <summary>\r
734                 /// Gets the child rows of a DataRow using the specified RelationName of a\r
735                 /// DataRelation, and DataRowVersion.\r
736                 /// </summary>\r
737                 public DataRow[] GetChildRows (string relationName, DataRowVersion version) \r
738                 {\r
739                         return GetChildRows (Table.DataSet.Relations[relationName], version);\r
740                 }\r
741 \r
742                 /// <summary>\r
743                 /// Gets the error description of the specified DataColumn.\r
744                 /// </summary>\r
745                 public string GetColumnError (DataColumn column) \r
746                 {\r
747                         return GetColumnError (_table.Columns.IndexOf(column));\r
748                 }\r
749 \r
750                 /// <summary>\r
751                 /// Gets the error description for the column specified by index.\r
752                 /// </summary>\r
753                 public string GetColumnError (int columnIndex) \r
754                 {\r
755                         if (columnIndex < 0 || columnIndex >= columnErrors.Length)\r
756                                 throw new IndexOutOfRangeException ();\r
757 \r
758                         string retVal = columnErrors[columnIndex];\r
759                         if (retVal == null)\r
760                                 retVal = string.Empty;\r
761                         return retVal;\r
762                 }\r
763 \r
764                 /// <summary>\r
765                 /// Gets the error description for the column, specified by name.\r
766                 /// </summary>\r
767                 public string GetColumnError (string columnName) \r
768                 {\r
769                         return GetColumnError (_table.Columns.IndexOf(columnName));\r
770                 }\r
771 \r
772                 /// <summary>\r
773                 /// Gets an array of columns that have errors.\r
774                 /// </summary>\r
775                 public DataColumn[] GetColumnsInError () \r
776                 {\r
777                         ArrayList dataColumns = new ArrayList ();\r
778 \r
779                         for (int i = 0; i < columnErrors.Length; i += 1)\r
780                         {\r
781                                 if (columnErrors[i] != null && columnErrors[i] != String.Empty)\r
782                                         dataColumns.Add (_table.Columns[i]);\r
783                         }\r
784 \r
785                         return (DataColumn[])(dataColumns.ToArray (typeof(DataColumn)));\r
786                 }\r
787 \r
788                 /// <summary>\r
789                 /// Gets the parent row of a DataRow using the specified DataRelation.\r
790                 /// </summary>\r
791                 public DataRow GetParentRow (DataRelation relation) \r
792                 {\r
793                         return GetParentRow (relation, DataRowVersion.Current);\r
794                 }\r
795 \r
796                 /// <summary>\r
797                 /// Gets the parent row of a DataRow using the specified RelationName of a\r
798                 /// DataRelation.\r
799                 /// </summary>\r
800                 public DataRow GetParentRow (string relationName) \r
801                 {\r
802                         return GetParentRow (relationName, DataRowVersion.Current);\r
803                 }\r
804 \r
805                 /// <summary>\r
806                 /// Gets the parent row of a DataRow using the specified DataRelation, and\r
807                 /// DataRowVersion.\r
808                 /// </summary>\r
809                 public DataRow GetParentRow (DataRelation relation, DataRowVersion version) \r
810                 {\r
811                         DataRow[] rows = GetParentRows(relation, version);\r
812                         if (rows.Length == 0) return null;\r
813                         return rows[0];\r
814                 }\r
815 \r
816                 /// <summary>\r
817                 /// Gets the parent row of a DataRow using the specified RelationName of a \r
818                 /// DataRelation, and DataRowVersion.\r
819                 /// </summary>\r
820                 public DataRow GetParentRow (string relationName, DataRowVersion version) \r
821                 {\r
822                         return GetParentRow (Table.DataSet.Relations[relationName], version);\r
823                 }\r
824 \r
825                 /// <summary>\r
826                 /// Gets the parent rows of a DataRow using the specified DataRelation.\r
827                 /// </summary>\r
828                 public DataRow[] GetParentRows (DataRelation relation) \r
829                 {\r
830                         return GetParentRows (relation, DataRowVersion.Current);\r
831                 }\r
832 \r
833                 /// <summary>\r
834                 /// Gets the parent rows of a DataRow using the specified RelationName of a \r
835                 /// DataRelation.\r
836                 /// </summary>\r
837                 public DataRow[] GetParentRows (string relationName) \r
838                 {\r
839                         return GetParentRows (relationName, DataRowVersion.Current);\r
840                 }\r
841 \r
842                 /// <summary>\r
843                 /// Gets the parent rows of a DataRow using the specified DataRelation, and\r
844                 /// DataRowVersion.\r
845                 /// </summary>\r
846                 public DataRow[] GetParentRows (DataRelation relation, DataRowVersion version) \r
847                 {\r
848                         // TODO: Caching for better preformance\r
849                         if (relation == null)\r
850                                 return new DataRow[0];\r
851 \r
852                         if (this.Table == null)\r
853                                 throw new RowNotInTableException();\r
854 \r
855                         if (relation.DataSet != this.Table.DataSet)\r
856                                 throw new ArgumentException();\r
857 \r
858                         ArrayList rows = new ArrayList();\r
859                         DataColumn[] parentColumns = relation.ParentColumns;\r
860                         DataColumn[] childColumns = relation.ChildColumns;\r
861                         int numColumn = parentColumns.Length;\r
862                         if (HasVersion(version))\r
863                         {\r
864                                 foreach (DataRow row in relation.ParentTable.Rows) \r
865                                 {\r
866                                         bool allColumnsMatch = false;\r
867                                         if (row.HasVersion(DataRowVersion.Default))\r
868                                         {\r
869                                                 allColumnsMatch = true;\r
870                                                 for (int columnCnt = 0; columnCnt < numColumn; columnCnt++) \r
871                                                 {\r
872                                                         if (!this[childColumns[columnCnt], version].Equals(\r
873                                                                 row[parentColumns[columnCnt], DataRowVersion.Default])) \r
874                                                         {\r
875                                                                 allColumnsMatch = false;\r
876                                                                 break;\r
877                                                         }\r
878                                                 }\r
879                                         }\r
880                                         if (allColumnsMatch) rows.Add(row);\r
881                                 }\r
882                         }\r
883                         return rows.ToArray(typeof(DataRow)) as DataRow[];\r
884                 }\r
885 \r
886                 /// <summary>\r
887                 /// Gets the parent rows of a DataRow using the specified RelationName of a \r
888                 /// DataRelation, and DataRowVersion.\r
889                 /// </summary>\r
890                 public DataRow[] GetParentRows (string relationName, DataRowVersion version) \r
891                 {\r
892                         return GetParentRows (Table.DataSet.Relations[relationName], version);\r
893                 }\r
894 \r
895                 /// <summary>\r
896                 /// Gets a value indicating whether a specified version exists.\r
897                 /// </summary>\r
898                 public bool HasVersion (DataRowVersion version) \r
899                 {\r
900                         switch (version)\r
901                         {\r
902                                 case DataRowVersion.Default:\r
903                                         if (rowState == DataRowState.Deleted)\r
904                                                 return false;\r
905                                         if (rowState == DataRowState.Detached)\r
906                                                 return proposed != null;\r
907                                         return true;\r
908                                 case DataRowVersion.Proposed:\r
909                                         if (rowState == DataRowState.Deleted)\r
910                                                 return false;\r
911                                         return (proposed != null);\r
912                                 case DataRowVersion.Current:\r
913                                         if (rowState == DataRowState.Deleted || rowState == DataRowState.Detached)\r
914                                                 return false;\r
915                                         return (current != null);\r
916                                 case DataRowVersion.Original:\r
917                                         if (rowState == DataRowState.Detached)\r
918                                                 return false;\r
919                                         return (original != null);\r
920                         }\r
921                         return false;\r
922                 }\r
923 \r
924                 /// <summary>\r
925                 /// Gets a value indicating whether the specified DataColumn contains a null value.\r
926                 /// </summary>\r
927                 public bool IsNull (DataColumn column) \r
928                 {\r
929                         object o = this[column];\r
930                         return (o == null || o == DBNull.Value);\r
931                 }\r
932 \r
933                 /// <summary>\r
934                 /// Gets a value indicating whether the column at the specified index contains a null\r
935                 /// value.\r
936                 /// </summary>\r
937                 public bool IsNull (int columnIndex) \r
938                 {\r
939                         object o = this[columnIndex];\r
940                         return (o == null || o == DBNull.Value);\r
941                 }\r
942 \r
943                 /// <summary>\r
944                 /// Gets a value indicating whether the named column contains a null value.\r
945                 /// </summary>\r
946                 public bool IsNull (string columnName) \r
947                 {\r
948                         object o = this[columnName];\r
949                         return (o == null || o == DBNull.Value);\r
950                 }\r
951 \r
952                 /// <summary>\r
953                 /// Gets a value indicating whether the specified DataColumn and DataRowVersion\r
954                 /// contains a null value.\r
955                 /// </summary>\r
956                 public bool IsNull (DataColumn column, DataRowVersion version) \r
957                 {\r
958                         object o = this[column, version];\r
959                         return (o == null || o == DBNull.Value);\r
960                 }\r
961 \r
962                 /// <summary>\r
963                 /// Rejects all changes made to the row since AcceptChanges was last called.\r
964                 /// </summary>\r
965                 public void RejectChanges () \r
966                 {\r
967                         // If original is null, then nothing has happened since AcceptChanges\r
968                         // was last called.  We have no "original" to go back to.\r
969                         if (original != null)\r
970                         {\r
971                                 Array.Copy (original, current, _table.Columns.Count);\r
972                                \r
973                                 _table.ChangedDataRow (this, DataRowAction.Rollback);\r
974                                 CancelEdit ();\r
975                                 switch (rowState)\r
976                                 {\r
977                                         case DataRowState.Added:\r
978                                                 _table.Rows.Remove (this);\r
979                                                 break;\r
980                                         case DataRowState.Modified:\r
981                                                 rowState = DataRowState.Unchanged;\r
982                                                 break;\r
983                                         case DataRowState.Deleted:\r
984                                                 rowState = DataRowState.Unchanged;\r
985                                                 break;\r
986                                 } \r
987                                 \r
988                         }                       \r
989                         else {\r
990                                 // If rows are just loaded via Xml the original values are null.\r
991                                 // So in this case we have to remove all columns.\r
992                                 // FIXME: I'm not realy sure, does this break something else, but\r
993                                 // if so: FIXME ;)\r
994                                 \r
995                                 if ((rowState & DataRowState.Added) > 0)\r
996                                         _table.Rows.Remove (this);\r
997                         }\r
998                 }\r
999 \r
1000                 /// <summary>\r
1001                 /// Sets the error description for a column specified as a DataColumn.\r
1002                 /// </summary>\r
1003                 public void SetColumnError (DataColumn column, string error) \r
1004                 {\r
1005                         SetColumnError (_table.Columns.IndexOf (column), error);\r
1006                 }\r
1007 \r
1008                 /// <summary>\r
1009                 /// Sets the error description for a column specified by index.\r
1010                 /// </summary>\r
1011                 public void SetColumnError (int columnIndex, string error) \r
1012                 {\r
1013                         if (columnIndex < 0 || columnIndex >= columnErrors.Length)\r
1014                                 throw new IndexOutOfRangeException ();\r
1015                         columnErrors[columnIndex] = error;\r
1016                 }\r
1017 \r
1018                 /// <summary>\r
1019                 /// Sets the error description for a column specified by name.\r
1020                 /// </summary>\r
1021                 public void SetColumnError (string columnName, string error) \r
1022                 {\r
1023                         SetColumnError (_table.Columns.IndexOf (columnName), error);\r
1024                 }\r
1025 \r
1026                 /// <summary>\r
1027                 /// Sets the value of the specified DataColumn to a null value.\r
1028                 /// </summary>\r
1029                 protected void SetNull (DataColumn column) \r
1030                 {\r
1031                         this[column] = DBNull.Value;\r
1032                 }\r
1033 \r
1034                 /// <summary>\r
1035                 /// Sets the parent row of a DataRow with specified new parent DataRow.\r
1036                 /// </summary>\r
1037                 [MonoTODO]\r
1038                 public void SetParentRow (DataRow parentRow) \r
1039                 {\r
1040                         SetParentRow(parentRow, null);\r
1041                 }\r
1042 \r
1043                 /// <summary>\r
1044                 /// Sets the parent row of a DataRow with specified new parent DataRow and\r
1045                 /// DataRelation.\r
1046                 /// </summary>\r
1047                 [MonoTODO]\r
1048                 public void SetParentRow (DataRow parentRow, DataRelation relation) \r
1049                 {\r
1050                         if (_table == null || parentRow.Table == null)\r
1051                                 throw new RowNotInTableException();\r
1052 \r
1053                         if (parentRow != null && _table.DataSet != parentRow.Table.DataSet)\r
1054                                 throw new ArgumentException();\r
1055                         \r
1056                         BeginEdit();\r
1057                         if (relation == null)\r
1058                         {\r
1059                                 foreach (DataRelation parentRel in _table.ParentRelations)\r
1060                                 {\r
1061                                         DataColumn[] childCols = parentRel.ChildKeyConstraint.Columns;\r
1062                                         DataColumn[] parentCols = parentRel.ChildKeyConstraint.RelatedColumns;\r
1063                                         \r
1064                                         for (int i = 0; i < parentCols.Length; i++)\r
1065                                         {\r
1066                                                 if (parentRow == null)\r
1067                                                         this[childCols[i].Ordinal] = DBNull.Value;\r
1068                                                 else\r
1069                                                         this[childCols[i].Ordinal] = parentRow[parentCols[i]];\r
1070                                         }\r
1071                                         \r
1072                                 }\r
1073                         }\r
1074                         else\r
1075                         {\r
1076                                 DataColumn[] childCols = relation.ChildKeyConstraint.Columns;\r
1077                                 DataColumn[] parentCols = relation.ChildKeyConstraint.RelatedColumns;\r
1078                                         \r
1079                                 for (int i = 0; i < parentCols.Length; i++)\r
1080                                 {\r
1081                                         if (parentRow == null)\r
1082                                                 this[childCols[i].Ordinal] = DBNull.Value;\r
1083                                         else\r
1084                                                 this[childCols[i].Ordinal] = parentRow[parentCols[i]];\r
1085                                 }\r
1086                         }\r
1087                         EndEdit();\r
1088                 }\r
1089                 \r
1090                 //Copy all values of this DataaRow to the row parameter.\r
1091                 internal void CopyValuesToRow(DataRow row)\r
1092                 {\r
1093                                                 \r
1094                         if (row == null)\r
1095                                 throw new ArgumentNullException("row");\r
1096                         if (row == this)\r
1097                                 throw new ArgumentException("'row' is the same as this object");\r
1098 \r
1099                         DataColumnCollection columns = Table.Columns;\r
1100                         \r
1101                         for(int i = 0; i < columns.Count; i++){\r
1102 \r
1103                                 string columnName = columns[i].ColumnName;\r
1104                                 int index = row.Table.Columns.IndexOf(columnName);\r
1105                                 //if a column with the same name exists in both rows copy the values\r
1106                                 if(index != -1) {\r
1107                                         if (HasVersion(DataRowVersion.Original))\r
1108                                         {\r
1109                                                 if (row.original == null)\r
1110                                                         row.original = new object[row.Table.Columns.Count];\r
1111                                                 row.original[index] = row.SetColumnValue(original[i], index);\r
1112                                         }\r
1113                                         if (HasVersion(DataRowVersion.Current))\r
1114                                         {\r
1115                                                 if (row.current == null)\r
1116                                                         row.current = new object[row.Table.Columns.Count];\r
1117                                                 row.current[index] = row.SetColumnValue(current[i], index);\r
1118                                         }\r
1119                                         if (HasVersion(DataRowVersion.Proposed))\r
1120                                         {\r
1121                                                 if (row.proposed == null)\r
1122                                                         row.proposed = new object[row.Table.Columns.Count];\r
1123                                                 row.proposed[index] = row.SetColumnValue(proposed[i], index);\r
1124                                         }\r
1125                                         \r
1126                                         //Saving the current value as the column value\r
1127                                         row[index] = row.current[index];\r
1128                                         \r
1129                                 }\r
1130                         }\r
1131 \r
1132                         row.rowState = RowState;\r
1133                         row.RowError = RowError;\r
1134                         row.columnErrors = columnErrors;\r
1135                 }\r
1136 \r
1137                 \r
1138                 public void CollectionChanged(object sender, System.ComponentModel.CollectionChangeEventArgs args)\r
1139                 {\r
1140                         // if a column is added we hava to add an additional value the \r
1141                         // the priginal, current and propoed arrays.\r
1142                         // this scenario can happened in merge operation.\r
1143 \r
1144                         if (args.Action == System.ComponentModel.CollectionChangeAction.Add)\r
1145                         {\r
1146                                 object[] tmp;\r
1147                                 if (current != null)\r
1148                                 {\r
1149                                         tmp = new object[current.Length + 1];\r
1150                                         Array.Copy (current, tmp, current.Length);\r
1151                                         tmp[tmp.Length - 1] = DBNull.Value;\r
1152                                         current = tmp;\r
1153                                 }\r
1154                                 if (proposed != null)\r
1155                                 {\r
1156                                         tmp = new object[proposed.Length + 1];\r
1157                                         Array.Copy (proposed, tmp, proposed.Length);\r
1158                                         tmp[tmp.Length - 1] = DBNull.Value;\r
1159                                         proposed = tmp;\r
1160                                 }\r
1161                                 if(original != null)\r
1162                                 {\r
1163                                         tmp = new object[original.Length + 1];\r
1164                                         Array.Copy (original, tmp, original.Length);\r
1165                                         tmp[tmp.Length - 1] = DBNull.Value;\r
1166                                         original = tmp;\r
1167                                 }\r
1168 \r
1169                         }\r
1170                 }\r
1171 \r
1172                 internal bool IsRowChanged(DataRowState rowState) {\r
1173                         if((RowState & rowState) != 0)\r
1174                                 return true;\r
1175 \r
1176                         //we need to find if child rows of this row changed.\r
1177                         //if yes - we should return true\r
1178 \r
1179                         // if the rowState is deleted we should get the original version of the row\r
1180                         // else - we should get the current version of the row.\r
1181                         DataRowVersion version = (rowState == DataRowState.Deleted) ? DataRowVersion.Original : DataRowVersion.Current;\r
1182                         int count = Table.ChildRelations.Count;\r
1183                         for (int i = 0; i < count; i++){\r
1184                                 DataRelation rel = Table.ChildRelations[i];\r
1185                                 DataRow[] childRows = GetChildRows(rel, version);\r
1186                                 for (int j = 0; j < childRows.Length; j++){\r
1187                                         if (childRows[j].IsRowChanged(rowState))\r
1188                                                 return true;\r
1189                                 }\r
1190                         }\r
1191 \r
1192                         return false;\r
1193                 }\r
1194 \r
1195                 internal bool HasParentCollection\r
1196                 {\r
1197                         get\r
1198                         {\r
1199                                 return _hasParentCollection;\r
1200                         }\r
1201                         set\r
1202                         {\r
1203                                 _hasParentCollection = value;\r
1204                         }\r
1205                 }\r
1206 \r
1207                 #endregion // Methods\r
1208         }\r
1209 \r
1210         \r
1211 \r
1212 }\r