// // System.Data.DataTableReader.cs // // Author: // Sureshkumar T // Tim Coleman (tim@timcoleman.com) // // Copyright (C) Tim Coleman, 2003 // // // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // #if NET_2_0 using System.Collections; using System.Data.Common; using System.ComponentModel; using System.Text.RegularExpressions; namespace System.Data { public sealed class DataTableReader : DbDataReader { bool _closed; DataTable [] _tables; int _current = -1; int _index; DataTable _schemaTable; bool _tableCleared = false; bool _subscribed = false; DataRow _rowRef; bool _schemaChanged = false; #region Constructors public DataTableReader (DataTable dt) : this (new DataTable[] {dt}) { } public DataTableReader (DataTable[] dataTables) { if (dataTables == null || dataTables.Length <= 0) throw new ArgumentException ("Cannot Create DataTable. Argument Empty!"); this._tables = new DataTable [dataTables.Length]; for (int i = 0; i < dataTables.Length; i++) this._tables [i] = dataTables [i]; _closed = false; _index = 0; _current = -1; _rowRef = null; _tableCleared = false; SubscribeEvents (); } #endregion // Constructors #region Properties public override int Depth { get { return 0; } } public override int FieldCount { get { return CurrentTable.Columns.Count; } } public override bool HasRows { get { return CurrentTable.Rows.Count > 0; } } public override bool IsClosed { get { return _closed; } } public override object this [int index] { get { Validate (); if (index < 0 || index >= FieldCount) throw new ArgumentOutOfRangeException ("index " + index + " is not in the range"); DataRow row = CurrentRow; if (row.RowState == DataRowState.Deleted) throw new InvalidOperationException ("Deleted Row's information cannot be accessed!"); return row [index]; } } private DataTable CurrentTable { get { return _tables [_index]; } } private DataRow CurrentRow { get { return (DataRow) CurrentTable.Rows [_current]; } } public override object this [string name] { get { Validate (); DataRow row = CurrentRow; if (row.RowState == DataRowState.Deleted) throw new InvalidOperationException ("Deleted Row's information cannot be accessed!"); return row [name]; } } public override int RecordsAffected { get { return 0; } } #endregion // Properties #region Methods private void SubscribeEvents () { if (_subscribed) // avoid subscribing multiple times return; CurrentTable.TableCleared += new DataTableClearEventHandler (OnTableCleared); CurrentTable.RowChanged += new DataRowChangeEventHandler (OnRowChanged); CurrentTable.Columns.CollectionChanged += new CollectionChangeEventHandler (OnColumnCollectionChanged); for (int i=0; i < CurrentTable.Columns.Count; ++i) CurrentTable.Columns [i].PropertyChanged += new PropertyChangedEventHandler (OnColumnChanged); _subscribed = true; _schemaChanged = false; } private void UnsubscribeEvents () { if (!_subscribed) // avoid un-subscribing multiple times return; CurrentTable.TableCleared -= new DataTableClearEventHandler (OnTableCleared); CurrentTable.RowChanged -= new DataRowChangeEventHandler (OnRowChanged); CurrentTable.Columns.CollectionChanged -= new CollectionChangeEventHandler (OnColumnCollectionChanged); for (int i=0; i < CurrentTable.Columns.Count; ++i) CurrentTable.Columns [i].PropertyChanged -= new PropertyChangedEventHandler (OnColumnChanged); _subscribed = false; _schemaChanged = false; } public override void Close () { if (IsClosed) return; UnsubscribeEvents (); _closed = true; } public override bool GetBoolean (int i) { return (bool) GetValue (i); } public override byte GetByte (int i) { return (byte) GetValue (i); } public override long GetBytes (int i, long dataIndex, byte[] buffer, int bufferIndex, int length) { byte[] value = this [i] as byte[]; if (value == null) ThrowInvalidCastException (this [i].GetType (), typeof (byte[])); if (buffer == null) return value.Length; int copylen = length > value.Length ? value.Length : length; Array.Copy (value, dataIndex, buffer, bufferIndex, copylen); return copylen; } public override char GetChar (int i) { return (char) GetValue (i); } public override long GetChars (int i, long dataIndex, char[] buffer, int bufferIndex, int length) { char[] value = this [i] as char[]; if (value == null) ThrowInvalidCastException (this [i].GetType (), typeof (char[])); if (buffer == null) return value.Length; int copylen = length > value.Length ? value.Length : length; Array.Copy (value, dataIndex, buffer, bufferIndex, copylen); return copylen; } public override string GetDataTypeName (int i) { return GetFieldType (i).ToString (); } public override DateTime GetDateTime (int i) { return (DateTime) GetValue (i); } public override decimal GetDecimal (int i) { return (decimal) GetValue (i); } public override double GetDouble (int i) { return (double) GetValue (i); } public override IEnumerator GetEnumerator () { return new DbEnumerator (this); } public override Type GetProviderSpecificFieldType (int i) { return GetFieldType (i); } public override Type GetFieldType (int i) { ValidateClosed (); return CurrentTable.Columns [i].DataType; } public override float GetFloat (int i) { return (float) GetValue (i); } public override Guid GetGuid (int i) { return (Guid) GetValue (i); } public override short GetInt16 (int i) { return (short) GetValue (i); } public override int GetInt32 (int i) { return (int) GetValue (i); } public override long GetInt64 (int i) { return (long) GetValue (i); } public override string GetName (int i) { ValidateClosed (); return CurrentTable.Columns [i].ColumnName; } public override int GetOrdinal (string name) { ValidateClosed (); int index = CurrentTable.Columns.IndexOf (name); if (index == -1) throw new ArgumentException (String.Format ("Column {0} is not found in the schema", name)); return index; } public override object GetProviderSpecificValue (int i) { return GetValue (i); } public override int GetProviderSpecificValues (object[] values) { return GetValues (values); } public override string GetString (int i) { return (string) GetValue (i); } public override object GetValue (int i) { return this [i]; } public override int GetValues (object[] values) { Validate (); if (CurrentRow.RowState == DataRowState.Deleted) throw new DeletedRowInaccessibleException (""); int count = (FieldCount < values.Length ? FieldCount : values.Length); for (int i=0; i < count; ++i) values [i] = CurrentRow [i]; return count; } public override bool IsDBNull (int i) { return GetValue (i) is DBNull; } public override DataTable GetSchemaTable () { ValidateClosed (); ValidateSchemaIntact (); if (_schemaTable != null) return _schemaTable; DataTable dt = new DataTable (); dt.Columns.Add ("ColumnName", typeof (string)); dt.Columns.Add ("ColumnOrdinal", typeof (int)); dt.Columns.Add ("ColumnSize", typeof (int)); dt.Columns.Add ("NumericPrecision", typeof (short)); dt.Columns.Add ("NumericScale", typeof (short)); dt.Columns.Add ("DataType", typeof (Type)); dt.Columns.Add ("ProviderType", typeof (int)); dt.Columns.Add ("IsLong", typeof (bool)); dt.Columns.Add ("AllowDBNull", typeof (bool)); dt.Columns.Add ("IsReadOnly", typeof (bool)); dt.Columns.Add ("IsRowVersion", typeof (bool)); dt.Columns.Add ("IsUnique", typeof (bool)); dt.Columns.Add ("IsKey", typeof (bool)); dt.Columns.Add ("IsAutoIncrement", typeof (bool)); dt.Columns.Add ("BaseCatalogName", typeof (string)); dt.Columns.Add ("BaseSchemaName", typeof (string)); dt.Columns.Add ("BaseTableName", typeof (string)); dt.Columns.Add ("BaseColumnName", typeof (string)); dt.Columns.Add ("AutoIncrementSeed", typeof (Int64)); dt.Columns.Add ("AutoIncrementStep", typeof (Int64)); dt.Columns.Add ("DefaultValue", typeof (object)); dt.Columns.Add ("Expression", typeof (string)); dt.Columns.Add ("ColumnMapping", typeof (MappingType)); dt.Columns.Add ("BaseTableNamespace", typeof (string)); dt.Columns.Add ("BaseColumnNamespace", typeof (string)); DataRow row; DataColumn col; for (int i=0; i < CurrentTable.Columns.Count; ++i) { row = dt.NewRow (); col = CurrentTable.Columns [i]; row ["ColumnName"] = col.ColumnName; row ["BaseColumnName"] = col.ColumnName; row ["ColumnOrdinal"] = col.Ordinal; row ["ColumnSize"] = col.MaxLength; // ms.net doesent set precision and scale even for Decimal values // when are these set ? row ["NumericPrecision"] = DBNull.Value; row ["NumericScale"] = DBNull.Value; row ["DataType"] = col.DataType; row ["ProviderType"] = DBNull.Value; //col.ProviderType; // ms.net doesent set this when datatype is string and maxlength = -1 // when is this set ? row ["IsLong"] = false; row ["AllowDBNull"] = col.AllowDBNull; row ["IsReadOnly"] = col.ReadOnly; row ["IsRowVersion"] = false; //this is always false row ["IsUnique"] = col.Unique; row ["IsKey"] = (Array.IndexOf (CurrentTable.PrimaryKey, col) != -1) ; row ["IsAutoIncrement"]= col.AutoIncrement; row ["AutoIncrementSeed"] = col.AutoIncrementSeed; row ["AutoIncrementStep"] = col.AutoIncrementStep; row ["BaseCatalogName"] = (CurrentTable.DataSet != null ? CurrentTable.DataSet.DataSetName : null); row ["BaseSchemaName"] = DBNull.Value; // this is always null row ["BaseTableName"] = CurrentTable.TableName; row ["DefaultValue"] = col.DefaultValue; // If col expression depends on any external table , then the // Expression value is set to empty string in the schematable. if (col.Expression == "") row ["Expression"] = col.Expression; else { Regex reg = new Regex ("((Parent|Child)( )*[.(])", RegexOptions.IgnoreCase); if (reg.IsMatch (col.Expression, 0)) row ["Expression"] = DBNull.Value; else row ["Expression"] = col.Expression; } row ["ColumnMapping"] = col.ColumnMapping; row ["BaseTableNamespace"] = CurrentTable.Namespace; row ["BaseColumnNamespace"] = col.Namespace; dt.Rows.Add (row); } return _schemaTable = dt; } private void Validate () { ValidateClosed (); if (_index >= _tables.Length) throw new InvalidOperationException ("Invalid attempt to read when no data is present"); if (_tableCleared) throw new RowNotInTableException ("The table is cleared, no rows are accessible"); if (_current == -1) throw new InvalidOperationException ("DataReader is invalid for the DataTable"); ValidateSchemaIntact (); } private void ValidateClosed () { if (IsClosed) throw new InvalidOperationException ("Invalid attempt to read when the reader is closed"); } private void ValidateSchemaIntact () { if (_schemaChanged) throw new InvalidOperationException ("Schema of current DataTable '" + CurrentTable.TableName + "' in DataTableReader has changed, DataTableReader is invalid."); } void ThrowInvalidCastException (Type sourceType, Type destType) { throw new InvalidCastException ( String.Format ("Unable to cast object of type '{0}' to type '{1}'.", sourceType, destType)); } private bool MoveNext () { if (_index >= _tables.Length || _tableCleared) return false; do { _current++; } while (_current < CurrentTable.Rows.Count && CurrentRow.RowState == DataRowState.Deleted); _rowRef = _current < CurrentTable.Rows.Count ? CurrentRow : null; return _current < CurrentTable.Rows.Count; } public override bool NextResult () { if ((_index + 1) >= _tables.Length) { UnsubscribeEvents (); _index = _tables.Length; // to make any attempt invalid return false; // end of tables. } UnsubscribeEvents (); _index++; _current = -1; _rowRef = null; _schemaTable = null; // force to create fresh _tableCleared = false; SubscribeEvents (); return true; } public override bool Read () { ValidateClosed (); return MoveNext (); } #endregion // Methods #region // Event Handlers private void OnColumnChanged (object sender, PropertyChangedEventArgs args) { _schemaChanged = true; } private void OnColumnCollectionChanged (object sender, CollectionChangeEventArgs args) { _schemaChanged = true; } private void OnRowChanged (object src, DataRowChangeEventArgs args) { DataRowAction action = args.Action; DataRow row = args.Row; if (action == DataRowAction.Add) { if (_tableCleared && _current != -1) return; if (_current == -1 || (_current >= 0 && row.RowID > CurrentRow.RowID)) { _tableCleared = false; return; // no. movement required, if row added after current. } _current++; _rowRef = CurrentRow; } if (action == DataRowAction.Commit && row.RowState == DataRowState.Detached) { // if i am the row deleted, move one down if (_rowRef == row) { _current --; _rowRef = _current >= 0 ? CurrentRow : null; } // if the row deleted is the last row, move down if (_current >= CurrentTable.Rows.Count) { _current--; _rowRef = _current >= 0 ? CurrentRow : null; return; } // deleting a row below _current moves the row one down if (_current > 0 && _rowRef == CurrentTable.Rows [_current-1]) { _current--; _rowRef = CurrentRow; return; } } } private void OnTableCleared (object src, DataTableClearEventArgs args) { _tableCleared = true; } #endregion // Event Handlers } } #endif // NET_2_0