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