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
}
}