using DynamORM.Helpers; using DynamORM.Mapper; using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Dynamic; using System.IO; using System.Linq; namespace DynamORM { /// Cache data reader in memory. public class DynamicCachedReader : DynamicObject, IDataReader { #region Constructor and Data private class Data { internal DataTable _schema; internal int _fields; internal int _rows; internal int _rowsAffected; internal int _position = -1; internal int _cachePos = -1; internal IList _names; internal IDictionary _ordinals; internal IList _types; internal IList _cache; } private List _data = new List(); private int _currentDataPosition = 0; private bool _isDisposed; private DynamicCachedReader() { } /// Initializes a new instance of the class. /// Reader to cache. /// The offset row. /// The limit to number of tows. -1 is no limit. /// The progress delegate. public DynamicCachedReader(IDataReader reader, int offset = 0, int limit = -1, Func progress = null) { InitDataReader(reader, offset, limit, progress); } #endregion Constructor and Data #region Helpers /// Create data reader from dynamic enumerable. /// List of objects. /// Instance of containing objects data. public static DynamicCachedReader FromDynamicEnumerable(IEnumerable objects) { var first = (objects as IEnumerable).FirstOrDefault(); if (first == null) return null; var firstDict = first as IDictionary; var r = new DynamicCachedReader(); r.Init(firstDict.Keys.Count); r._data.Add(new Data()); var data = r._data[r._currentDataPosition]; for (int i = 0; i < firstDict.Keys.Count; i++) data._types.Add(null); foreach (dynamic elem in (objects as IEnumerable)) { int c = 0; var dict = elem as IDictionary; foreach (var k in firstDict.Keys) { object val = dict[k]; data._cache.Add(val); if (data._types[c] == null && val != null) data._types[c] = val.GetType(); c++; } data._rows++; } for (int i = 0; i < firstDict.Keys.Count; i++) if (data._types[i] == null) data._types[i] = typeof(string); data._schema = new DataTable("DYNAMIC"); data._schema.Columns.Add(new DataColumn("ColumnName", typeof(string))); data._schema.Columns.Add(new DataColumn("ColumnOrdinal", typeof(int))); data._schema.Columns.Add(new DataColumn("ColumnSize", typeof(int))); data._schema.Columns.Add(new DataColumn("NumericPrecision", typeof(short))); data._schema.Columns.Add(new DataColumn("NumericScale", typeof(short))); data._schema.Columns.Add(new DataColumn("DataType", typeof(Type))); data._schema.Columns.Add(new DataColumn("ProviderType", typeof(int))); data._schema.Columns.Add(new DataColumn("NativeType", typeof(int))); data._schema.Columns.Add(new DataColumn("AllowDBNull", typeof(bool))); data._schema.Columns.Add(new DataColumn("IsUnique", typeof(bool))); data._schema.Columns.Add(new DataColumn("IsKey", typeof(bool))); data._schema.Columns.Add(new DataColumn("IsAutoIncrement", typeof(bool))); int ordinal = 0; DataRow dr = null; foreach (var column in firstDict.Keys) { dr = data._schema.NewRow(); dr[0] = column; dr[1] = ordinal; dr[2] = 0; dr[3] = 0; dr[4] = 0; dr[5] = data._types[ordinal]; dr[6] = data._types[ordinal].ToDbType(); dr[7] = data._types[ordinal].ToDbType(); dr[8] = true; dr[9] = false; dr[10] = false; dr[11] = false; data._schema.Rows.Add(dr); data._names.Add(dr[0].ToString()); data._ordinals.Add(dr[0].ToString().ToUpper(), ordinal++); data._types.Add((Type)dr[5]); dr.AcceptChanges(); } dr.AcceptChanges(); return r; } /// Create data reader from enumerable. /// Type of enumerated objects. /// List of objects. /// Instance of containing objects data. public static DynamicCachedReader FromEnumerable(IEnumerable objects) { var mapper = DynamicMapperCache.GetMapper(); if (mapper == null) throw new InvalidCastException(string.Format("Object type '{0}' can't be mapped.", typeof(T).FullName)); var r = new DynamicCachedReader(); r.Init(mapper.ColumnsMap.Count + 1); r.CreateSchemaTable(mapper); r.FillFromEnumerable(objects, mapper); r.IsClosed = false; return r; } /// Create data reader from enumerable. /// Type of enumerated objects. /// List of objects. /// Instance of containing objects data. public static DynamicCachedReader FromEnumerable(Type elementType, IEnumerable objects) { var mapper = DynamicMapperCache.GetMapper(elementType); if (mapper == null) throw new InvalidCastException(string.Format("Object type '{0}' can't be mapped.", elementType.FullName)); var r = new DynamicCachedReader(); r.Init(mapper.ColumnsMap.Count + 1); r.CreateSchemaTable(mapper); r.FillFromEnumerable(elementType, objects, mapper); r.IsClosed = false; return r; } private void InitDataReader(IDataReader reader, int offset = 0, int limit = -1, Func progress = null) { do { Init(reader.FieldCount); var data = _data[_currentDataPosition]; data._schema = reader.GetSchemaTable(); data._rowsAffected = reader.RecordsAffected; int i = 0; for (i = 0; i < data._fields; i++) { data._names.Add(reader.GetName(i)); data._types.Add(reader.GetFieldType(i)); if (!data._ordinals.ContainsKey(reader.GetName(i).ToUpper())) data._ordinals.Add(reader.GetName(i).ToUpper(), i); } int current = 0; while (reader.Read()) { if (current < offset) { current++; continue; } for (i = 0; i < data._fields; i++) data._cache.Add(reader[i]); data._rows++; current++; if (limit >= 0 && data._rows >= limit) break; if (progress != null && !progress(this, data._rows)) break; } IsClosed = false; data._position = -1; data._cachePos = -1; if (progress != null) progress(this, data._rows); } while (reader.NextResult()); reader.Close(); } private void FillFromEnumerable(IEnumerable objects, DynamicTypeMap mapper) { var data = _data[_currentDataPosition]; foreach (var elem in objects) { foreach (var col in mapper.ColumnsMap) { object val = null; if (col.Value.Get != null) val = col.Value.Get(elem); data._cache.Add(val); } data._cache.Add(elem); data._rows++; } } private void FillFromEnumerable(Type elementType, IEnumerable objects, DynamicTypeMap mapper) { var data = _data[_currentDataPosition]; foreach (var elem in objects) { foreach (var col in mapper.ColumnsMap) { object val = null; if (col.Value.Get != null) val = col.Value.Get(elem); data._cache.Add(val); } data._cache.Add(elem); data._rows++; } } private void CreateSchemaTable(DynamicTypeMap mapper) { var data = _data[_currentDataPosition]; data._schema = new DataTable("DYNAMIC"); data._schema.Columns.Add(new DataColumn("ColumnName", typeof(string))); data._schema.Columns.Add(new DataColumn("ColumnOrdinal", typeof(int))); data._schema.Columns.Add(new DataColumn("ColumnSize", typeof(int))); data._schema.Columns.Add(new DataColumn("NumericPrecision", typeof(short))); data._schema.Columns.Add(new DataColumn("NumericScale", typeof(short))); data._schema.Columns.Add(new DataColumn("DataType", typeof(Type))); data._schema.Columns.Add(new DataColumn("ProviderType", typeof(int))); data._schema.Columns.Add(new DataColumn("NativeType", typeof(int))); data._schema.Columns.Add(new DataColumn("AllowDBNull", typeof(bool))); data._schema.Columns.Add(new DataColumn("IsUnique", typeof(bool))); data._schema.Columns.Add(new DataColumn("IsKey", typeof(bool))); data._schema.Columns.Add(new DataColumn("IsAutoIncrement", typeof(bool))); int ordinal = 0; DataRow dr = null; foreach (var column in mapper.ColumnsMap) { dr = data._schema.NewRow(); dr[0] = column.Value.Column.NullOr(x => x.Name ?? column.Value.Name, column.Value.Name); dr[1] = ordinal; dr[2] = column.Value.Column.NullOr(x => x.Size ?? int.MaxValue, int.MaxValue); dr[3] = column.Value.Column.NullOr(x => x.Precision ?? 0, 0); dr[4] = column.Value.Column.NullOr(x => x.Scale ?? 0, 0); dr[5] = column.Value.Column.NullOr(x => x.Type.HasValue ? x.Type.Value.ToType() : column.Value.Type, column.Value.Type); dr[6] = column.Value.Column.NullOr(x => x.Type ?? column.Value.Type.ToDbType(), column.Value.Type.ToDbType()); dr[7] = column.Value.Column.NullOr(x => x.Type ?? column.Value.Type.ToDbType(), column.Value.Type.ToDbType()); dr[8] = column.Value.Column.NullOr(x => x.IsKey, false) ? true : column.Value.Column.NullOr(x => x.AllowNull, true); dr[9] = column.Value.Column.NullOr(x => x.IsUnique, false); dr[10] = column.Value.Column.NullOr(x => x.IsKey, false); dr[11] = false; data._schema.Rows.Add(dr); data._names.Add(dr[0].ToString()); data._ordinals.Add(dr[0].ToString().ToUpper(), ordinal++); data._types.Add((Type)dr[5]); dr.AcceptChanges(); } dr = data._schema.NewRow(); dr[0] = "#O"; dr[1] = ordinal; dr[2] = int.MaxValue; dr[3] = 0; dr[4] = 0; dr[5] = mapper.Type; dr[6] = DbType.Object; dr[7] = DbType.Object; dr[8] = true; dr[9] = false; dr[10] = false; dr[11] = false; data._schema.Rows.Add(dr); data._names.Add("#O"); data._ordinals.Add("#O".ToUpper(), ordinal++); data._types.Add(mapper.Type); dr.AcceptChanges(); } private void Init(int fieldCount) { _data.Add(new Data() { _rows = 0, _fields = fieldCount, _names = new List(fieldCount), _ordinals = new Dictionary(fieldCount), _types = new List(fieldCount), _cache = new List(fieldCount * 100), }); _currentDataPosition = _data.Count - 1; } /// Sets the current position in reader. /// The position. public void SetPosition(int pos) { if (pos >= -1 && pos < _data[_currentDataPosition]._rows) { _data[_currentDataPosition]._position = pos; _data[_currentDataPosition]._cachePos = _data[_currentDataPosition]._position * _data[_currentDataPosition]._fields; } else throw new IndexOutOfRangeException(); } #endregion Helpers #region IDataReader Members /// Closes the System.Data.IDataReader Object. public void Close() { IsClosed = true; _currentDataPosition = -1; } /// Gets a value indicating the depth of nesting for the current row. /// This implementation use this field to indicate row count. public int Depth { get { return _data[_currentDataPosition]._rows; } } /// Returns a System.Data.DataTable that describes the column metadata of the /// System.Data.IDataReader.A System.Data.DataTable that describes /// the column metadata. /// The System.Data.IDataReader is closed. public DataTable GetSchemaTable() { return _data[_currentDataPosition]._schema; } /// Gets a value indicating whether the data reader is closed. public bool IsClosed { get; private set; } /// Advances the data reader to the next result, when reading the results of batch SQL statements. /// Returns true if there are more rows; otherwise, false. public bool NextResult() { _currentDataPosition++; return _data.Count < _currentDataPosition; } /// Advances the System.Data.IDataReader to the next record. /// Returns true if there are more rows; otherwise, false. public bool Read() { var data = _data[_currentDataPosition]; data._cachePos = (++data._position) * data._fields; return data._position < data._rows; } /// Gets the number of rows changed, inserted, or deleted by execution of the SQL statement. /// The number of rows changed, inserted, or deleted; 0 if no rows were affected or the statement /// failed; and -1 for SELECT statements. public int RecordsAffected { get { return _data[_currentDataPosition]._rowsAffected; } } #endregion IDataReader Members #region IDisposable Members /// Performs application-defined tasks associated with /// freeing, releasing, or resetting unmanaged resources. public void Dispose() { if (_isDisposed) return; _isDisposed = true; IsClosed = true; if (_data == null) return; foreach (var data in _data) { if (data == null) continue; if (data._names != null) data._names.Clear(); if (data._types != null) data._types.Clear(); if (data._cache != null) data._cache.Clear(); if (data._schema != null) data._schema.Dispose(); } _data.Clear(); } #endregion IDisposable Members #region IDataRecord Members /// Gets the number of columns in the current row. /// When not positioned in a valid record set, 0; otherwise, the number of columns in the current record. The default is -1. public int FieldCount { get { return _data[_currentDataPosition]._fields; } } /// Return the value of the specified field. /// The index of the field to find. /// Field value upon return. public bool GetBoolean(int i) { var data = _data[_currentDataPosition]; return (bool)data._cache[data._cachePos + i]; } /// Return the value of the specified field. /// The index of the field to find. /// Field value upon return. public byte GetByte(int i) { var data = _data[_currentDataPosition]; return (byte)data._cache[data._cachePos + i]; } /// Reads a stream of bytes from the specified column offset into the buffer /// as an array, starting at the given buffer offset. /// The index of the field to find. /// The index within the field from which to start the read operation. /// The buffer into which to read the stream of bytes. /// The index for buffer to start the read operation. /// The number of bytes to read. /// The actual number of bytes read. public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) { var data = _data[_currentDataPosition]; using (MemoryStream ms = new MemoryStream((byte[])data._cache[data._cachePos + i])) return ms.Read(buffer, bufferoffset, length); } /// Return the value of the specified field. /// The index of the field to find. /// Field value upon return. public char GetChar(int i) { var data = _data[_currentDataPosition]; return (char)data._cache[data._cachePos + i]; } /// Reads a stream of characters from the specified column offset into the buffer /// as an array, starting at the given buffer offset. /// The zero-based column ordinal. /// The index within the row from which to start the read operation. /// The buffer into which to read the stream of bytes. /// The index for buffer to start the read operation. /// The number of bytes to read. /// The actual number of characters read. public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) { var data = _data[_currentDataPosition]; using (MemoryStream ms = new MemoryStream((byte[])data._cache[data._cachePos + i])) { byte[] buff = new byte[buffer.Length]; long ret = ms.Read(buff, bufferoffset, length); for (int n = bufferoffset; n < ret; n++) buffer[n] = (char)buff[n]; return ret; } } /// Returns an System.Data.IDataReader for the specified column ordinal. /// The index of the field to find. /// An System.Data.IDataReader. public IDataReader GetData(int i) { return null; } /// Gets the data type information for the specified field. /// The index of the field to find. /// The data type information for the specified field. public string GetDataTypeName(int i) { return _data[_currentDataPosition]._types[i].Name; } /// Return the value of the specified field. /// The index of the field to find. /// Field value upon return. public DateTime GetDateTime(int i) { var data = _data[_currentDataPosition]; return (DateTime)data._cache[data._cachePos + i]; } /// Return the value of the specified field. /// The index of the field to find. /// Field value upon return. public decimal GetDecimal(int i) { var data = _data[_currentDataPosition]; return (decimal)data._cache[data._cachePos + i]; } /// Return the value of the specified field. /// The index of the field to find. /// Field value upon return. public double GetDouble(int i) { var data = _data[_currentDataPosition]; return (double)data._cache[data._cachePos + i]; } /// Gets the System.Type information corresponding to the type of System.Object /// that would be returned from System.Data.IDataRecord.GetValue(System.Int32). /// The index of the field to find. /// The System.Type information corresponding to the type of System.Object that /// would be returned from System.Data.IDataRecord.GetValue(System.Int32). public Type GetFieldType(int i) { return _data[_currentDataPosition]._types[i]; } /// Return the value of the specified field. /// The index of the field to find. /// Field value upon return. public float GetFloat(int i) { var data = _data[_currentDataPosition]; return (float)data._cache[data._cachePos + i]; } /// Return the value of the specified field. /// The index of the field to find. /// Field value upon return. public Guid GetGuid(int i) { var data = _data[_currentDataPosition]; return (Guid)data._cache[data._cachePos + i]; } /// Return the value of the specified field. /// The index of the field to find. /// Field value upon return. public short GetInt16(int i) { var data = _data[_currentDataPosition]; return (short)data._cache[data._cachePos + i]; } /// Return the value of the specified field. /// The index of the field to find. /// Field value upon return. public int GetInt32(int i) { var data = _data[_currentDataPosition]; return (int)data._cache[data._cachePos + i]; } /// Return the value of the specified field. /// The index of the field to find. /// Field value upon return. public long GetInt64(int i) { var data = _data[_currentDataPosition]; return (long)data._cache[data._cachePos + i]; } /// Gets the name for the field to find. /// The index of the field to find. /// The name of the field or the empty string (""), if there is no value to return. public string GetName(int i) { return _data[_currentDataPosition]._names[i]; } /// Return the index of the named field. /// The name of the field to find. /// The index of the named field. public int GetOrdinal(string name) { var data = _data[_currentDataPosition]; if (data._ordinals.ContainsKey(name.ToUpper())) return data._ordinals[name.ToUpper()]; return -1; } /// Return the value of the specified field. /// The index of the field to find. /// Field value upon return. public string GetString(int i) { var data = _data[_currentDataPosition]; return (string)data._cache[data._cachePos + i]; } /// Return the value of the specified field. /// The index of the field to find. /// Field value upon return. public object GetValue(int i) { var data = _data[_currentDataPosition]; return data._cache[data._cachePos + i]; } /// Gets all the attribute fields in the collection for the current record. /// An array of System.Object to copy the attribute fields into. /// The number of instances of System.Object in the array. public int GetValues(object[] values) { var data = _data[_currentDataPosition]; for (int i = 0; i < data._fields; i++) values[i] = data._cache[data._cachePos + i]; return data._fields; } /// Return whether the specified field is set to null. /// The index of the field to find. /// Returns true if the specified field is set to null; otherwise, false. public bool IsDBNull(int i) { var data = _data[_currentDataPosition]; return data._cache[data._cachePos + i] == null || data._cache[data._cachePos + i] == DBNull.Value; } /// Gets or sets specified value in current record. /// Name of column. /// Value of specified column. public object this[string name] { get { var data = _data[_currentDataPosition]; if (data._ordinals.ContainsKey(name.ToUpper())) return data._cache[data._cachePos + data._ordinals[name.ToUpper()]]; throw new IndexOutOfRangeException(String.Format("Field '{0}' not found.", name)); } } /// Gets or sets specified value in current record. /// The index of the field to find. /// Value of specified column. public object this[int i] { get { var data = _data[_currentDataPosition]; return data._cache[data._cachePos + i]; } } #endregion IDataRecord Members } }