// // System.Data.DataTable.cs // // Author: // Franklin Wise // Christopher Podurgiel (cpodurgiel@msn.com) // Daniel Morgan // Rodrigo Moya // Tim Coleman (tim@timcoleman.com) // Ville Palo // // (C) Chris Podurgiel // (C) Ximian, Inc 2002 // Copyright (C) Tim Coleman, 2002 // Copyright (C) Daniel Morgan, 2002-2003 // using System; using System.Collections; using System.ComponentModel; using System.Globalization; using System.Runtime.Serialization; namespace System.Data { //[Designer] [ToolboxItem (false)] [DefaultEvent ("RowChanging")] [DefaultProperty ("TableName")] [DesignTimeVisible (false)] [Serializable] public class DataTable : MarshalByValueComponent, IListSource, ISupportInitialize, ISerializable { internal DataSet dataSet; private bool _caseSensitive; private DataColumnCollection _columnCollection; private ConstraintCollection _constraintCollection; private DataView _defaultView; private string _displayExpression; private PropertyCollection _extendedProperties; private bool _hasErrors; private CultureInfo _locale; private int _minimumCapacity; private string _nameSpace; private DataRelationCollection _childRelations; private DataRelationCollection _parentRelations; private string _prefix; private DataColumn[] _primaryKey; private DataRowCollection _rows; private ISite _site; private string _tableName; private bool _containsListCollection; private string _encodedTableName; // If CaseSensitive property is changed once it does not anymore follow owner DataSet's // CaseSensitive property. So when you lost you virginity it's gone for ever private bool _virginCaseSensitive = true; /// /// Initializes a new instance of the DataTable class with no arguments. /// public DataTable () { dataSet = null; _columnCollection = new DataColumnCollection(this); _constraintCollection = new ConstraintCollection(); _extendedProperties = new PropertyCollection(); _tableName = ""; _nameSpace = null; _caseSensitive = false; //default value _displayExpression = null; _primaryKey = null; _site = null; _rows = new DataRowCollection (this); _locale = CultureInfo.CurrentCulture; //LAMESPEC: spec says 25 impl does 50 _minimumCapacity = 50; _childRelations = new DataRelationCollection.DataTableRelationCollection (this); _parentRelations = new DataRelationCollection.DataTableRelationCollection (this); _defaultView = new DataView(this); } /// /// Intitalizes a new instance of the DataTable class with the specified table name. /// public DataTable (string tableName) : this () { _tableName = tableName; } /// /// Initializes a new instance of the DataTable class with the SerializationInfo and the StreamingContext. /// [MonoTODO] protected DataTable (SerializationInfo info, StreamingContext context) : this () { // // TODO: Add constructor logic here // } /// /// Indicates whether string comparisons within the table are case-sensitive. /// [DataSysDescription ("Indicates whether comparing strings within the table is case sensitive.")] public bool CaseSensitive { get { return _caseSensitive; } set { _virginCaseSensitive = false; _caseSensitive = value; } } internal bool VirginCaseSensitive { get { return _virginCaseSensitive; } set { _virginCaseSensitive = value; } } internal void ChangedDataColumn (DataRow dr, DataColumn dc, object pv) { DataColumnChangeEventArgs e = new DataColumnChangeEventArgs (dr, dc, pv); OnColumnChanged(e); } internal void ChangingDataColumn (DataRow dr, DataColumn dc, object pv) { DataColumnChangeEventArgs e = new DataColumnChangeEventArgs (dr, dc, pv); OnColumnChanging (e); } internal void DeletedDataRow (DataRow dr, DataRowAction action) { DataRowChangeEventArgs e = new DataRowChangeEventArgs (dr, action); OnRowDeleted (e); } internal void ChangedDataRow (DataRow dr, DataRowAction action) { DataRowChangeEventArgs e = new DataRowChangeEventArgs (dr, action); OnRowChanged (e); } /// /// Gets the collection of child relations for this DataTable. /// [Browsable (false)] [DataSysDescription ("Returns the child relations for this table.")] [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public DataRelationCollection ChildRelations { get { return _childRelations; } } /// /// Gets the collection of columns that belong to this table. /// [DataCategory ("Data")] [DataSysDescription ("The collection that holds the columns for this table.")] [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] public DataColumnCollection Columns { get { return _columnCollection; } } /// /// Gets the collection of constraints maintained by this table. /// [DataCategory ("Data")] [DataSysDescription ("The collection that holds the constraints for this table.")] [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)] public ConstraintCollection Constraints { get { return _constraintCollection; } } /// /// Gets the DataSet that this table belongs to. /// [Browsable (false)] [DataSysDescription ("Indicates the DataSet to which this table belongs.")] [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public DataSet DataSet { get { return dataSet; } } /// /// Gets a customized view of the table which may /// include a filtered view, or a cursor position. /// [MonoTODO] [Browsable (false)] [DataSysDescription ("This is the default DataView for the table.")] public DataView DefaultView { get { return _defaultView; } } /// /// Gets or sets the expression that will return /// a value used to represent this table in the user interface. /// [DataCategory ("Data")] [DataSysDescription ("The expression used to compute the data-bound value of this row.")] [DefaultValue ("")] public string DisplayExpression { get { return "" + _displayExpression; } set { _displayExpression = value; } } /// /// Gets the collection of customized user information. /// [Browsable (false)] [DataCategory ("Data")] [DataSysDescription ("The collection that holds custom user information.")] public PropertyCollection ExtendedProperties { get { return _extendedProperties; } } /// /// Gets a value indicating whether there are errors in /// any of the_rows in any of the tables of the DataSet to /// which the table belongs. /// [Browsable (false)] [DataSysDescription ("Returns whether the table has errors.")] public bool HasErrors { get { return _hasErrors; } } /// /// Gets or sets the locale information used to /// compare strings within the table. /// [DataSysDescription ("Indicates a locale under which to compare strings within the table.")] public CultureInfo Locale { get { return _locale; } set { _locale = value; } } /// /// Gets or sets the initial starting size for this table. /// [DataCategory ("Data")] [DataSysDescription ("Indicates an initial starting size for this table.")] [DefaultValue (50)] public int MinimumCapacity { get { return _minimumCapacity; } set { _minimumCapacity = value; } } /// /// Gets or sets the namespace for the XML represenation /// of the data stored in the DataTable. /// [DataCategory ("Data")] [DataSysDescription ("Indicates the XML uri namespace for the elements contained in this table.")] public string Namespace { get { return "" + _nameSpace; } set { _nameSpace = value; } } /// /// Gets the collection of parent relations for /// this DataTable. /// [Browsable (false)] [DataSysDescription ("Returns the parent relations for this table.")] [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public DataRelationCollection ParentRelations { get { return _parentRelations; } } /// /// Gets or sets the namespace for the XML represenation /// of the data stored in the DataTable. /// [DataCategory ("Data")] [DataSysDescription ("Indicates the Prefix of the namespace used for this table in XML representation.")] [DefaultValue ("")] public string Prefix { get { return "" + _prefix; } set { _prefix = value; } } /// /// Gets or sets an array of columns that function as /// primary keys for the data table. /// [DataCategory ("Data")] [DataSysDescription ("Indicates the column(s) that represent the primary key for this table.")] public DataColumn[] PrimaryKey { get { UniqueConstraint uc = UniqueConstraint.GetPrimaryKeyConstraint( Constraints); if (null == uc) return new DataColumn[] {}; return uc.Columns; } set { //YUK: msft removes a previous unique constraint if it is flagged as a pk //when a new pk is set //clear Primary Key if value == null if (null == value) { UniqueConstraint.SetAsPrimaryKey(this.Constraints, null); return; } //Does constraint exist for these columns UniqueConstraint uc = UniqueConstraint.GetUniqueConstraintForColumnSet( this.Constraints, (DataColumn[]) value); //if constraint doesn't exist for columns //create new unique primary key constraint if (null == uc) { uc = new UniqueConstraint( (DataColumn[]) value, true); } else { //set existing constraint as the new primary key UniqueConstraint.SetAsPrimaryKey(this.Constraints, uc); } } } /// /// Gets the collection of_rows that belong to this table. /// [Browsable (false)] [DataSysDescription ("Indicates the collection that holds the rows of data for this table.")] public DataRowCollection Rows { get { return _rows; } } /// /// Gets or sets an System.ComponentModel.ISite /// for the DataTable. /// [Browsable (false)] [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] public override ISite Site { get { return _site; } set { _site = value; } } /// /// Gets or sets the name of the the DataTable. /// [DataCategory ("Data")] [DataSysDescription ("Indicates the name used to look up this table in the Tables collection of a DataSet.")] [DefaultValue ("")] [RefreshProperties (RefreshProperties.All)] public string TableName { get { return "" + _tableName; } set { _tableName = value; } } bool IListSource.ContainsListCollection { get { // the collection is a DataView return false; } } /// /// Commits all the changes made to this table since the /// last time AcceptChanges was called. /// public void AcceptChanges () { //FIXME: Do we need to validate anything here or //try to catch any errors to deal with them? foreach(DataRow myRow in _rows) { myRow.AcceptChanges(); } } /// /// Begins the initialization of a DataTable that is used /// on a form or used by another component. The initialization /// occurs at runtime. /// public void BeginInit () { } /// /// Turns off notifications, index maintenance, and /// constraints while loading data. /// [MonoTODO] public void BeginLoadData () { } /// /// Clears the DataTable of all data. /// public void Clear () { _rows.Clear (); } /// /// Clones the structure of the DataTable, including /// all DataTable schemas and constraints. /// [MonoTODO] public virtual DataTable Clone () { DataTable Copy = new DataTable (); CopyProperties (Copy); return Copy; } /// /// Computes the given expression on the current_rows that /// pass the filter criteria. /// [MonoTODO] public object Compute (string expression, string filter) { //FIXME: //Do a real compute object obj = "a"; return obj; } /// /// Copies both the structure and data for this DataTable. /// [MonoTODO] public DataTable Copy () { DataTable Copy = new DataTable (); CopyProperties (Copy); foreach (DataRow Row in Rows) { DataRow NewRow = Copy.NewRow (); NewRow.RowError = Row.RowError; foreach (DataColumn C in Copy.Columns) { NewRow [C.ColumnName] = Row [C.ColumnName]; } Copy.Rows.Add (NewRow); } return Copy; } [MonoTODO] private void CopyProperties (DataTable Copy) { Copy.CaseSensitive = CaseSensitive; Copy.VirginCaseSensitive = VirginCaseSensitive; // Copy.ChildRelations // Copy.Constraints // Copy.Container // Copy.DefaultView // Copy.DesignMode Copy.DisplayExpression = DisplayExpression; // Copy.ExtendedProperties Copy.Locale = Locale; Copy.MinimumCapacity = MinimumCapacity; Copy.Namespace = Namespace; // Copy.ParentRelations Copy.Prefix = Prefix; //Copy.PrimaryKey = PrimaryKey; Copy.Site = Site; Copy.TableName = TableName; // Copy columns foreach (DataColumn Column in Columns) { Copy.Columns.Add (CopyColumn (Column)); } } /// /// Ends the initialization of a DataTable that is used /// on a form or used by another component. The /// initialization occurs at runtime. /// [MonoTODO] public void EndInit () { } /// /// Turns on notifications, index maintenance, and /// constraints after loading data. /// [MonoTODO] public void EndLoadData() { } /// /// Gets a copy of the DataTable that contains all /// changes made to it since it was loaded or /// AcceptChanges was last called. /// [MonoTODO] public DataTable GetChanges() { //TODO: return this; } /// /// Gets a copy of the DataTable containing all /// changes made to it since it was last loaded, or /// since AcceptChanges was called, filtered by DataRowState. /// [MonoTODO] public DataTable GetChanges(DataRowState rowStates) { //TODO: return this; } /// /// Gets an array of DataRow objects that contain errors. /// [MonoTODO] public DataRow[] GetErrors () { throw new NotImplementedException (); } /// /// This member is only meant to support Mono's infrastructure /// protected virtual DataTable CreateInstance () { return Activator.CreateInstance (this.GetType (), true) as DataTable; } /// /// This member is only meant to support Mono's infrastructure /// protected virtual Type GetRowType () { return typeof (DataRow); } /// /// This member is only meant to support Mono's infrastructure /// /// Used for Data Binding between System.Web.UI. controls /// like a DataGrid /// or /// System.Windows.Forms controls like a DataGrid /// IList IListSource.GetList () { IList list = (IList) _defaultView; return list; } /// /// Copies a DataRow into a DataTable, preserving any /// property settings, as well as original and current values. /// [MonoTODO] public void ImportRow (DataRow row) { } /// /// This member is only meant to support Mono's infrastructure /// [MonoTODO] void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context) { } /// /// Finds and updates a specific row. If no matching row /// is found, a new row is created using the given values. /// [MonoTODO] public DataRow LoadDataRow (object[] values, bool fAcceptChanges) { DataRow row = null; if (PrimaryKey.Length == 0) { row = Rows.Add (values); if (fAcceptChanges) row.AcceptChanges (); } else throw new NotImplementedException (); return row; } /// /// Creates a new DataRow with the same schema as the table. /// public DataRow NewRow () { return this.NewRowFromBuilder (new DataRowBuilder (this, 0, 0)); } /// /// This member supports the .NET Framework infrastructure /// and is not intended to be used directly from your code. /// protected internal DataRow[] NewRowArray (int size) { return (DataRow[]) Array.CreateInstance (GetRowType (), size); } /// /// Creates a new row from an existing row. /// protected virtual DataRow NewRowFromBuilder (DataRowBuilder builder) { return new DataRow (builder); } /// /// Rolls back all changes that have been made to the /// table since it was loaded, or the last time AcceptChanges /// was called. /// [MonoTODO] public void RejectChanges () { //foreach(DataRow myRow in _rows) //{ for (int i = _rows.Count - 1; i >= 0; i--) { DataRow row = _rows [i]; if (row.RowState != DataRowState.Unchanged) _rows [i].RejectChanges (); } } /// /// Resets the DataTable to its original state. /// [MonoTODO] public virtual void Reset () { } /// /// Gets an array of all DataRow objects. /// public DataRow[] Select () { DataRow[] dataRows = new DataRow[_rows.Count]; _rows.CopyTo (dataRows, 0); return dataRows; } /// /// Gets an array of all DataRow objects that match /// the filter criteria in order of primary key (or /// lacking one, order of addition.) /// public DataRow[] Select (string filterExpression) { ExpressionElement Expression = new ExpressionMainElement (filterExpression); ArrayList List = new ArrayList (); foreach (DataRow Row in Rows) { if (Expression.Test (Row)) List.Add (Row); } return (DataRow [])List.ToArray (typeof (DataRow)); } /// /// Gets an array of all DataRow objects that /// match the filter criteria, in the the /// specified sort order. /// public DataRow[] Select (string filterExpression, string sort) { DataRow[] dataRows = null; if (filterExpression != null && filterExpression.Equals (String.Empty) == false) dataRows = Select (filterExpression); else dataRows = Select (); if (sort != null && !sort.Equals (String.Empty)) { SortableColumn[] sortableColumns = null; sortableColumns = ParseTheSortString (sort); if (sortableColumns == null) throw new Exception ("sort expression result is null"); if (sortableColumns.Length == 0) throw new Exception("sort expression result is 0"); RowSorter rowSorter = new RowSorter (dataRows, sortableColumns); dataRows = rowSorter.SortRows (); sortableColumns = null; rowSorter = null; } return dataRows; } /// /// Gets an array of all DataRow objects that match /// the filter in the order of the sort, that match /// the specified state. /// [MonoTODO] public DataRow[] Select(string filterExpression, string sort, DataViewRowState recordStates) { DataRow[] dataRows = null; // TODO: do something with recordStates dataRows = Select (filterExpression, sort); return dataRows; } /// /// Gets the TableName and DisplayExpression, if /// there is one as a concatenated string. /// public override string ToString() { //LAMESPEC: spec says concat the two. impl puts a //plus sign infront of DisplayExpression return TableName + " " + DisplayExpression; } #region Events ///////////////// /// /// Raises the ColumnChanged event. /// protected virtual void OnColumnChanged (DataColumnChangeEventArgs e) { if (null != ColumnChanged) { ColumnChanged (this, e); } } /// /// Raises the ColumnChanging event. /// protected virtual void OnColumnChanging (DataColumnChangeEventArgs e) { if (null != ColumnChanging) { ColumnChanging (this, e); } } /// /// Raises the PropertyChanging event. /// [MonoTODO] protected internal virtual void OnPropertyChanging (PropertyChangedEventArgs pcevent) { // if (null != PropertyChanging) // { // PropertyChanging (this, e); // } } /// /// Notifies the DataTable that a DataColumn is being removed. /// [MonoTODO] protected internal virtual void OnRemoveColumn (DataColumn column) { // if (null != RemoveColumn) // { // RemoveColumn(this, e); // } } /// /// Raises the RowChanged event. /// protected virtual void OnRowChanged (DataRowChangeEventArgs e) { if (null != RowChanged) { RowChanged(this, e); } } /// /// Raises the RowChanging event. /// protected virtual void OnRowChanging (DataRowChangeEventArgs e) { if (null != RowChanging) { RowChanging(this, e); } } /// /// Raises the RowDeleted event. /// protected virtual void OnRowDeleted (DataRowChangeEventArgs e) { if (null != RowDeleted) { RowDeleted(this, e); } } /// /// Raises the RowDeleting event. /// protected virtual void OnRowDeleting (DataRowChangeEventArgs e) { if (null != RowDeleting) { RowDeleting(this, e); } } [MonoTODO] private DataColumn CopyColumn (DataColumn Column) { DataColumn Copy = new DataColumn (); // Copy all the properties of column Copy.AllowDBNull = Column.AllowDBNull; Copy.AutoIncrement = Column.AutoIncrement; Copy.AutoIncrementSeed = Column.AutoIncrementSeed; Copy.AutoIncrementStep = Column.AutoIncrementStep; Copy.Caption = Column.Caption; Copy.ColumnMapping = Column.ColumnMapping; Copy.ColumnName = Column.ColumnName; // Copy.Container // Copy.DataType // Copy.DefaultValue Copy.Expression = Column.Expression; //Copy.ExtendedProperties Copy.MaxLength = Column.MaxLength; Copy.Namespace = Column.Namespace; Copy.Prefix = Column.Prefix; Copy.ReadOnly = Column.ReadOnly; //Copy.Site Copy.Unique = Column.Unique; return Copy; } /// /// Occurs when after a value has been changed for /// the specified DataColumn in a DataRow. /// [DataCategory ("Data")] [DataSysDescription ("Occurs when a value has been changed for this column.")] public event DataColumnChangeEventHandler ColumnChanged; /// /// Occurs when a value is being changed for the specified /// DataColumn in a DataRow. /// [DataCategory ("Data")] [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.")] public event DataColumnChangeEventHandler ColumnChanging; /// /// Occurs after a DataRow has been changed successfully. /// [DataCategory ("Data")] [DataSysDescription ("Occurs after a row in the table has been successfully edited.")] public event DataRowChangeEventHandler RowChanged; /// /// Occurs when a DataRow is changing. /// [DataCategory ("Data")] [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.")] public event DataRowChangeEventHandler RowChanging; /// /// Occurs after a row in the table has been deleted. /// [DataCategory ("Data")] [DataSysDescription ("Occurs after a row in the table has been successfully deleted.")] public event DataRowChangeEventHandler RowDeleted; /// /// Occurs before a row in the table is about to be deleted. /// [DataCategory ("Data")] [DataSysDescription ("Occurs when a row in the table marked for deletion. Throw an exception to cancel the deletion.")] public event DataRowChangeEventHandler RowDeleting; #endregion // Events // to parse the sort string for DataTable:Select(expression,sort) // into sortable columns (think ORDER BY, // such as, "customer ASC, price DESC" ) private SortableColumn[] ParseTheSortString (string sort) { SortableColumn[] sortColumns = null; ArrayList columns = null; if (sort != null && !sort.Equals ("")) { columns = new ArrayList (); string[] columnExpression = sort.Trim ().Split (new char[1] {','}); for (int c = 0; c < columnExpression.Length; c++) { string[] columnSortInfo = columnExpression[c].Trim ().Split (new char[1] {' '}); string columnName = columnSortInfo[0].Trim (); string sortOrder = "ASC"; if (columnSortInfo.Length > 1) sortOrder = columnSortInfo[1].Trim ().ToUpper (); ListSortDirection sortDirection = ListSortDirection.Ascending; switch (sortOrder) { case "ASC": sortDirection = ListSortDirection.Ascending; break; case "DESC": sortDirection = ListSortDirection.Descending; break; default: throw new IndexOutOfRangeException ("Could not find column: " + columnExpression[c]); } Int32 ord = 0; try { ord = Int32.Parse (columnName); } catch (FormatException) { ord = -1; } DataColumn dc = null; if (ord == -1) dc = _columnCollection[columnName]; else dc = _columnCollection[ord]; SortableColumn sortCol = new SortableColumn (dc,sortDirection); columns.Add (sortCol); } sortColumns = (SortableColumn[]) columns.ToArray (typeof (SortableColumn)); } return sortColumns; } private class SortableColumn { private DataColumn col; private ListSortDirection dir; internal SortableColumn (DataColumn column, ListSortDirection direction) { col = column; dir = direction; } public DataColumn Column { get { return col; } } public ListSortDirection SortDirection { get { return dir; } } } private class RowSorter : IComparer { private SortableColumn[] sortColumns; private DataRow[] rowsToSort; internal RowSorter(DataRow[] unsortedRows, SortableColumn[] sortColumns) { this.sortColumns = sortColumns; this.rowsToSort = unsortedRows; } public SortableColumn[] SortColumns { get { return sortColumns; } } public DataRow[] SortRows () { Array.Sort (rowsToSort, this); return rowsToSort; } int IComparer.Compare (object x, object y) { if(x == null) throw new Exception ("Object to compare is null: x"); if(y == null) throw new Exception ("Object to compare is null: y"); if(!(x is DataRow)) throw new Exception ("Object to compare is not DataRow: x is " + x.GetType().ToString()); if(!(y is DataRow)) throw new Exception ("Object to compare is not DataRow: y is " + x.GetType().ToString()); DataRow rowx = (DataRow) x; DataRow rowy = (DataRow) y; for(int i = 0; i < sortColumns.Length; i++) { SortableColumn sortColumn = sortColumns[i]; DataColumn dc = sortColumn.Column; IComparable objx = (IComparable) rowx[dc]; object objy = rowy[dc]; int result = CompareObjects (objx, objy); if (result != 0) { if (sortColumn.SortDirection == ListSortDirection.Ascending) { return result; } else { return -result; } } } return 0; } private int CompareObjects (object a, object b) { if (a == b) return 0; else if (a == null) return -1; else if (a == DBNull.Value) return -1; else if (b == null) return 1; else if (b == DBNull.Value) return 1; if((a is string) && (b is string)) { a = ((string) a).ToUpper (); b = ((string) b).ToUpper (); } if (a is IComparable) return ((a as IComparable).CompareTo (b)); else if (b is IComparable) return -((b as IComparable).CompareTo (a)); throw new ArgumentException ("Neither a nor b IComparable"); } } } }