using System;
using System.Collections.Generic;
using System.Data;
using System.ComponentModel;
using System.Collections;
using FastReport.Utils;
using System.Drawing.Design;
namespace FastReport.Data
{
///
/// Base class for all datasources such as .
///
[TypeConverter(typeof(FastReport.TypeConverters.DataSourceConverter))]
[Editor("FastReport.TypeEditors.DataSourceEditor, FastReport", typeof(UITypeEditor))]
public abstract class DataSourceBase : Column
{
#region Fields
private ArrayList internalRows;
private ArrayList rows;
private int currentRowNo;
protected object currentRow;
private Hashtable additionalFilter;
private bool forceLoadData;
private Hashtable columnIndices;
private Hashtable rowIndices;
private Hashtable relation_SortedChildRows;
private static bool FShowAccessDataMessage;
#endregion
#region Properties
///
/// Occurs when the FastReport engine loads data source with data.
///
///
/// Use this event if you want to implement load-on-demand. Event handler must load the data
/// into the data object which this datasource is bound to (for example, the
/// TableDataSource uses data from the DataTable object bound to
/// the Table property).
///
public event EventHandler Load;
///
/// Gets or sets alias of this object.
///
///
/// Alias is a human-friendly name of this object. It may contain any symbols (including
/// spaces and national symbols).
///
[Category("Design")]
public new string Alias
{
get { return base.Alias; }
set
{
UpdateExpressions(base.Alias, value);
base.Alias = value;
}
}
///
/// Gets a number of data rows in this datasource.
///
///
/// You should initialize the datasource by the Init method before using this property.
///
[Browsable(false)]
public int RowCount
{
get { return rows.Count; }
}
///
/// Gets a value indicating that datasource has more rows, that is the
/// is less than the .
///
///
/// You should initialize the datasource by the Init method before using this property.
/// Usually this property is used with the following code block:
///
/// dataSource.Init();
/// while (dataSource.HasMoreRows)
/// {
/// // do something...
/// dataSource.Next();
/// }
///
///
[Browsable(false)]
public bool HasMoreRows
{
get { return CurrentRowNo < RowCount; }
}
///
/// Gets the current data row.
///
///
/// This property is updated when you call the method.
///
[Browsable(false)]
public object CurrentRow
{
get
{
// in case we trying to print a datasource column in report title, init the datasource
if (InternalRows.Count == 0)
Init();
return currentRow;
}
}
///
/// Gets an index of current data row.
///
///
/// You should initialize the datasource by the Init method before using this property.
/// This property is updated when you call the method.
///
[Browsable(false)]
public int CurrentRowNo
{
get { return currentRowNo; }
set
{
currentRowNo = value;
if (value >= 0 && value < rows.Count)
currentRow = rows[value];
else
currentRow = null;
}
}
///
/// Gets data stored in a specified column.
///
/// Alias of a column.
/// The column's value.
///
/// You should initialize the datasource by the Init method before using this property.
///
[Browsable(false)]
public object this[string columnAlias]
{
get
{
return GetValue(columnAlias);
}
}
///
/// Gets data stored in a specified column.
///
/// The column.
/// The column's value.
///
/// You should initialize the datasource by the Init method before using this property.
///
[Browsable(false)]
public object this[Column column]
{
get
{
if (InternalRows.Count == 0)
Init();
if (column.Calculated)
return column.Value;
return GetValue(column);
}
}
///
/// Forces loading of data for this datasource.
///
///
/// This property is false by default. Set it to true if you need to reload data
/// each time when the datasource initialized. Note that this may slow down the performance.
///
[DefaultValue(false)]
public bool ForceLoadData
{
get { return forceLoadData; }
set { forceLoadData = value; }
}
///
/// This property is not relevant to this class.
///
[Browsable(false)]
public new Type DataType
{
get { return base.DataType; }
set { base.DataType = value; }
}
///
/// This property is not relevant to this class.
///
[Browsable(false)]
public new ColumnBindableControl BindableControl
{
get { return base.BindableControl; }
set { base.BindableControl = value; }
}
///
/// This property is not relevant to this class.
///
[Browsable(false)]
public new string CustomBindableControl
{
get { return base.CustomBindableControl; }
set { base.CustomBindableControl = value; }
}
///
/// This property is not relevant to this class.
///
[Browsable(false)]
public new ColumnFormat Format
{
get { return base.Format; }
set { base.Format = value; }
}
///
/// This property is not relevant to this class.
///
[Browsable(false)]
public new string Expression
{
get { return base.Expression; }
set { base.Expression = value; }
}
///
/// This property is not relevant to this class.
///
[Browsable(false)]
public new bool Calculated
{
get { return base.Calculated; }
set { base.Calculated = value; }
}
///
/// Gets the additional filter settings.
///
internal Hashtable AdditionalFilter
{
get { return additionalFilter; }
}
internal ArrayList Rows
{
get { return rows; }
}
internal ArrayList InternalRows
{
get { return internalRows; }
}
#endregion
#region Private Methods
private void GetChildRows(Relation relation)
{
// prepare consts
object parentRow = relation.ParentDataSource.CurrentRow;
int columnsCount = relation.ParentColumns.Length;
object[] parentValues = new object[columnsCount];
Column[] childColumns = new Column[columnsCount];
for (int i = 0; i < columnsCount; i++)
{
parentValues[i] = relation.ParentDataSource[relation.ParentColumns[i]];
childColumns[i] = Columns.FindByAlias(relation.ChildColumns[i]);
}
if (relation_SortedChildRows == null)
relation_SortedChildRows = new Hashtable();
// sort the child table at the first run. Use relation columns to sort.
SortedList sortedChildRows = relation_SortedChildRows[relation] as SortedList;
if (sortedChildRows == null)
{
sortedChildRows = new SortedList();
relation_SortedChildRows[relation] = sortedChildRows;
foreach (object row in InternalRows)
{
SetCurrentRow(row);
object[] values = new object[columnsCount];
for (int i = 0; i < columnsCount; i++)
{
values[i] = this[childColumns[i]];
}
Indices indices = new Indices(values);
ArrayList rows = null;
int index = sortedChildRows.IndexOfKey(indices);
if (index == -1)
{
rows = new ArrayList();
sortedChildRows.Add(indices, rows);
}
else
rows = sortedChildRows.Values[index];
rows.Add(row);
}
}
int indexOfKey = sortedChildRows.IndexOfKey(new Indices(parentValues));
if (indexOfKey != -1)
{
ArrayList rows = sortedChildRows.Values[indexOfKey];
foreach (object row in rows)
{
this.rows.Add(row);
}
}
}
private void ApplyAdditionalFilter()
{
for (int i = 0; i < rows.Count; i++)
{
CurrentRowNo = i;
foreach (DictionaryEntry de in AdditionalFilter)
{
object value = Report.GetColumnValueNullable((string)de.Key);
DataSourceFilter filter = de.Value as DataSourceFilter;
if (!filter.ValueMatch(value))
{
rows.RemoveAt(i);
i--;
break;
}
}
}
}
private void UpdateExpressions(string oldAlias, string newAlias)
{
if (Report != null)
{
// Update expressions in components.
foreach (Base component in Report.AllObjects)
{
// Update Text in TextObject instances.
if (component is TextObject)
{
TextObject text = component as TextObject;
string bracket = text.Brackets.Split(new char[] { ',' })[0];
if (String.IsNullOrEmpty(bracket))
{
bracket = "[";
}
text.Text = text.Text.Replace(bracket + oldAlias + ".", bracket + newAlias + ".");
}
// Update DataColumn in PictureObject instances.
else if (component is PictureObject)
{
PictureObject picture = component as PictureObject;
picture.DataColumn = picture.DataColumn.Replace(oldAlias + ".", newAlias + ".");
}
// Update Filter and Sort in DataBand instances.
else if (component is DataBand)
{
DataBand data = component as DataBand;
data.Filter = data.Filter.Replace("[" + oldAlias + ".", "[" + newAlias + ".");
foreach (Sort sort in data.Sort)
{
sort.Expression = sort.Expression.Replace("[" + oldAlias + ".", "[" + newAlias + ".");
}
}
}
}
}
#endregion
#region Protected Methods
///
/// Gets data stored in a specified column.
///
/// The column alias.
/// An object that contains the data.
protected virtual object GetValue(string alias)
{
Column column = columnIndices[alias] as Column;
if (column == null)
{
column = Columns.FindByAlias(alias);
columnIndices[alias] = column;
}
return GetValue(column);
}
///
/// Gets data stored in a specified column.
///
/// The column.
/// An object that contains the data.
protected abstract object GetValue(Column column);
#endregion
#region Public Methods
///
/// Initializes the datasource schema.
///
///
/// This method is used to support the FastReport.Net infrastructure. Do not call it directly.
///
public abstract void InitSchema();
///
/// Loads the datasource with data.
///
///
/// This method is used to support the FastReport.Net infrastructure. Do not call it directly.
///
/// Rows to fill with data.
public abstract void LoadData(ArrayList rows);
internal void LoadData()
{
LoadData(InternalRows);
}
internal void OnLoad()
{
if (Load != null)
{
// clear internal rows to force reload data
InternalRows.Clear();
Load(this, EventArgs.Empty);
}
}
internal void SetCurrentRow(object row)
{
currentRow = row;
}
internal void FindParentRow(Relation relation)
{
InitSchema();
LoadData();
int columnCount = relation.ChildColumns.Length;
object[] childValues = new object[columnCount];
for (int i = 0; i < columnCount; i++)
{
childValues[i] = relation.ChildDataSource[relation.ChildColumns[i]];
}
object result = null;
if (childValues[0] == null)
{
SetCurrentRow(null);
return;
}
// improve performance for single column index
if (columnCount == 1)
{
if (rowIndices.Count == 0)
{
foreach (object row in InternalRows)
{
SetCurrentRow(row);
rowIndices[this[relation.ParentColumns[0]]] = row;
}
}
result = rowIndices[childValues[0]];
if (result != null)
{
SetCurrentRow(result);
return;
}
}
foreach (object row in InternalRows)
{
SetCurrentRow(row);
bool found = true;
for (int i = 0; i < columnCount; i++)
{
if (!this[relation.ParentColumns[i]].Equals(childValues[i]))
{
found = false;
break;
}
}
if (found)
{
result = row;
break;
}
}
if (columnCount == 1)
rowIndices[childValues[0]] = result;
SetCurrentRow(result);
}
///
/// Initializes this datasource.
///
///
/// This method fills the table with data. You should always call it before using most of
/// datasource properties.
///
public void Init()
{
Init("");
}
///
/// Initializes this datasource and applies the specified filter.
///
/// The filter expression.
public void Init(string filter)
{
Init(filter, null);
}
///
/// Initializes this datasource, applies the specified filter and sorts the rows.
///
/// The filter expression.
/// The collection of sort descriptors.
public void Init(string filter, SortCollection sort)
{
DataSourceBase parentData = null;
Init(parentData, filter, sort);
}
///
/// Initializes this datasource and filters data rows according to the master-detail relation between
/// this datasource and parentData.
///
/// Parent datasource.
///
/// To use master-detail relation, you must define the object that describes
/// the relation, and add it to the Report.Dictionary.Relations collection.
///
public void Init(DataSourceBase parentData)
{
Init(parentData, "", null);
}
///
/// Initializes this datasource and filters data rows according to the master-detail relation between
/// this datasource and parentData. Also applies the specified filter and sorts the rows.
///
/// Parent datasource.
/// The filter expression.
/// The collection of sort descriptors.
///
/// To use master-detail relation, you must define the object that describes
/// the relation, and add it to the Report.Dictionary.Relations collection.
///
public void Init(DataSourceBase parentData, string filter, SortCollection sort)
{
Init(parentData, filter, sort, false);
}
///
/// Initializes this datasource and filters data rows according to the master-detail relation.
/// Also applies the specified filter and sorts the rows.
///
/// The master-detail relation.
/// The filter expression.
/// The collection of sort descriptors.
///
/// To use master-detail relation, you must define the object that describes
/// the relation, and add it to the Report.Dictionary.Relations collection.
///
public void Init(Relation relation, string filter, SortCollection sort)
{
Init(relation, filter, sort, false);
}
internal void Init(DataSourceBase parentData, string filter, SortCollection sort, bool useAllParentRows)
{
Relation relation = parentData != null ? DataHelper.FindRelation(Report.Dictionary, parentData, this) : null;
Init(relation, filter, sort, useAllParentRows);
}
internal void Init(Relation relation, string filter, SortCollection sort, bool useAllParentRows)
{
if (FShowAccessDataMessage)
Config.ReportSettings.OnProgress(Report, Res.Get("Messages,AccessingData"));
// InitSchema may fail sometimes (for example, when using OracleConnection with nested select).
try
{
InitSchema();
}
catch
{
}
LoadData();
// fill rows, emulate relation
rows.Clear();
if (relation != null && relation.Enabled)
{
if (useAllParentRows)
{
DataSourceBase parentData = relation.ParentDataSource;
// parentData must be initialized prior to calling this method!
parentData.First();
while (parentData.HasMoreRows)
{
GetChildRows(relation);
parentData.Next();
}
}
else
GetChildRows(relation);
}
else
{
foreach (object row in InternalRows)
{
rows.Add(row);
}
}
// filter data rows
if (FShowAccessDataMessage && rows.Count > 10000)
Config.ReportSettings.OnProgress(Report, Res.Get("Messages,PreparingData"));
if (filter != null && filter.Trim() != "")
{
for (int i = 0; i < rows.Count; i++)
{
CurrentRowNo = i;
object match = Report.Calc(filter);
if (match is bool && !(bool)match)
{
rows.RemoveAt(i);
i--;
}
}
}
// additional filter
if (AdditionalFilter.Count > 0)
ApplyAdditionalFilter();
// sort data rows
if (sort != null && sort.Count > 0)
{
string[] expressions = new string[sort.Count];
bool[] descending = new bool[sort.Count];
for (int i = 0; i < sort.Count; i++)
{
expressions[i] = sort[i].Expression;
descending[i] = sort[i].Descending;
}
rows.Sort(new RowComparer(Report, this, expressions, descending));
}
FShowAccessDataMessage = false;
First();
}
///
/// Initializes the data source if it is not initialized yet.
///
public void EnsureInit()
{
if (InternalRows.Count == 0)
Init();
}
///
/// Navigates to the first row.
///
///
/// You should initialize the datasource by the Init method before using this method.
///
public void First()
{
CurrentRowNo = 0;
}
///
/// Navigates to the next row.
///
///
/// You should initialize the datasource by the Init method before using this method.
///
public void Next()
{
CurrentRowNo++;
}
///
/// Navigates to the prior row.
///
///
/// You should initialize the datasource by the Init method before using this method.
///
public void Prior()
{
CurrentRowNo--;
}
///
public override void Serialize(FRWriter writer)
{
base.Serialize(writer);
if (Enabled)
writer.WriteBool("Enabled", Enabled);
if (ForceLoadData)
writer.WriteBool("ForceLoadData", ForceLoadData);
}
///
public override void Deserialize(FRReader reader)
{
// the Clear is needed to avoid duplicate columns in the inherited report
// when the same datasource is exists in both base and inherited report
Clear();
base.Deserialize(reader);
}
internal void ClearData()
{
columnIndices.Clear();
rowIndices.Clear();
InternalRows.Clear();
Rows.Clear();
additionalFilter.Clear();
relation_SortedChildRows = null;
FShowAccessDataMessage = true;
}
///
public override void InitializeComponent()
{
ClearData();
}
#endregion
///
/// Initializes a new instance of the class with default settings.
///
public DataSourceBase()
{
internalRows = new ArrayList();
rows = new ArrayList();
additionalFilter = new Hashtable();
columnIndices = new Hashtable();
rowIndices = new Hashtable();
SetFlags(Flags.HasGlobalName, true);
}
private class RowComparer : IComparer
{
private Report report;
private DataSourceBase dataSource;
private string[] expressions;
private bool[] descending;
private Column[] columns;
public int Compare(object x, object y)
{
int result = 0;
for (int i = 0; i < expressions.Length; i++)
{
IComparable i1;
IComparable i2;
if (columns[i] == null)
{
dataSource.SetCurrentRow(x);
i1 = report.Calc(expressions[i]) as IComparable;
dataSource.SetCurrentRow(y);
i2 = report.Calc(expressions[i]) as IComparable;
}
else
{
dataSource.SetCurrentRow(x);
i1 = columns[i].Value as IComparable;
dataSource.SetCurrentRow(y);
i2 = columns[i].Value as IComparable;
}
if (i1 != null)
result = i1.CompareTo(i2);
else if (i2 != null)
result = -1;
if (descending[i])
result = -result;
if (result != 0)
break;
}
return result;
}
public RowComparer(Report report, DataSourceBase dataSource, string[] expressions, bool[] descending)
{
this.report = report;
this.dataSource = dataSource;
this.expressions = expressions;
this.descending = descending;
// optimize performance if expression is a single data column
columns = new Column[expressions.Length];
for (int i = 0; i < expressions.Length; i++)
{
string expr = expressions[i];
if (expr.StartsWith("[") && expr.EndsWith("]"))
expr = expr.Substring(1, expr.Length - 2);
Column column = DataHelper.GetColumn(this.report.Dictionary, expr);
DataSourceBase datasource = DataHelper.GetDataSource(this.report.Dictionary, expr);
if (column != null && column.Parent == datasource)
columns[i] = column;
else
columns[i] = null;
}
}
}
private class Indices : IComparable
{
private object[] values;
public int CompareTo(object obj)
{
Indices indices = obj as Indices;
int result = 0;
for (int i = 0; i < values.Length; i++)
{
IComparable i1 = indices.values[i] as IComparable;
IComparable i2 = values[i] as IComparable;
if (i1 != null)
result = i1.CompareTo(i2);
else if (i2 != null)
result = -1;
if (result != 0)
break;
}
return result;
}
public Indices(object[] values)
{
this.values = values;
}
}
}
}