123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866 |
- 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
- {
- /// <summary>
- /// Base class for all datasources such as <see cref="TableDataSource"/>.
- /// </summary>
- [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
- /// <summary>
- /// Occurs when the FastReport engine loads data source with data.
- /// </summary>
- /// <remarks>
- /// 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
- /// <b>TableDataSource</b> uses data from the <b>DataTable</b> object bound to
- /// the <b>Table</b> property).
- /// </remarks>
- public event EventHandler Load;
- /// <summary>
- /// Gets or sets alias of this object.
- /// </summary>
- /// <remarks>
- /// Alias is a human-friendly name of this object. It may contain any symbols (including
- /// spaces and national symbols).
- /// </remarks>
- [Category("Design")]
- public new string Alias
- {
- get { return base.Alias; }
- set
- {
- UpdateExpressions(base.Alias, value);
- base.Alias = value;
- }
- }
- /// <summary>
- /// Gets a number of data rows in this datasource.
- /// </summary>
- /// <remarks>
- /// You should initialize the datasource by the <b>Init</b> method before using this property.
- /// </remarks>
- [Browsable(false)]
- public int RowCount
- {
- get { return rows.Count; }
- }
- /// <summary>
- /// Gets a value indicating that datasource has more rows, that is the <see cref="CurrentRowNo"/>
- /// is less than the <see cref="RowCount"/>.
- /// </summary>
- /// <remarks>
- /// <para>You should initialize the datasource by the <b>Init</b> method before using this property.</para>
- /// <para>Usually this property is used with the following code block:</para>
- /// <code>
- /// dataSource.Init();
- /// while (dataSource.HasMoreRows)
- /// {
- /// // do something...
- /// dataSource.Next();
- /// }
- /// </code>
- /// </remarks>
- [Browsable(false)]
- public bool HasMoreRows
- {
- get { return CurrentRowNo < RowCount; }
- }
- /// <summary>
- /// Gets the current data row.
- /// </summary>
- /// <remarks>
- /// <para>This property is updated when you call the <see cref="Next"/> method.</para>
- /// </remarks>
- [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;
- }
- }
- /// <summary>
- /// Gets an index of current data row.
- /// </summary>
- /// <remarks>
- /// <para>You should initialize the datasource by the <b>Init</b> method before using this property.</para>
- /// <para>This property is updated when you call the <see cref="Next"/> method.</para>
- /// </remarks>
- [Browsable(false)]
- public int CurrentRowNo
- {
- get { return currentRowNo; }
- set
- {
- currentRowNo = value;
- if (value >= 0 && value < rows.Count)
- currentRow = rows[value];
- else
- currentRow = null;
- }
- }
- /// <summary>
- /// Gets data stored in a specified column.
- /// </summary>
- /// <param name="columnAlias">Alias of a column.</param>
- /// <returns>The column's value.</returns>
- /// <remarks>
- /// You should initialize the datasource by the <b>Init</b> method before using this property.
- /// </remarks>
- [Browsable(false)]
- public object this[string columnAlias]
- {
- get
- {
- return GetValue(columnAlias);
- }
- }
- /// <summary>
- /// Gets data stored in a specified column.
- /// </summary>
- /// <param name="column">The column.</param>
- /// <returns>The column's value.</returns>
- /// <remarks>
- /// You should initialize the datasource by the <b>Init</b> method before using this property.
- /// </remarks>
- [Browsable(false)]
- public object this[Column column]
- {
- get
- {
- if (InternalRows.Count == 0)
- Init();
- if (column.Calculated)
- return column.Value;
- return GetValue(column);
- }
- }
- /// <summary>
- /// Forces loading of data for this datasource.
- /// </summary>
- /// <remarks>
- /// This property is <b>false</b> by default. Set it to <b>true</b> if you need to reload data
- /// each time when the datasource initialized. Note that this may slow down the performance.
- /// </remarks>
- [DefaultValue(false)]
- public bool ForceLoadData
- {
- get { return forceLoadData; }
- set { forceLoadData = value; }
- }
- /// <summary>
- /// This property is not relevant to this class.
- /// </summary>
- [Browsable(false)]
- public new Type DataType
- {
- get { return base.DataType; }
- set { base.DataType = value; }
- }
- /// <summary>
- /// This property is not relevant to this class.
- /// </summary>
- [Browsable(false)]
- public new ColumnBindableControl BindableControl
- {
- get { return base.BindableControl; }
- set { base.BindableControl = value; }
- }
- /// <summary>
- /// This property is not relevant to this class.
- /// </summary>
- [Browsable(false)]
- public new string CustomBindableControl
- {
- get { return base.CustomBindableControl; }
- set { base.CustomBindableControl = value; }
- }
- /// <summary>
- /// This property is not relevant to this class.
- /// </summary>
- [Browsable(false)]
- public new ColumnFormat Format
- {
- get { return base.Format; }
- set { base.Format = value; }
- }
- /// <summary>
- /// This property is not relevant to this class.
- /// </summary>
- [Browsable(false)]
- public new string Expression
- {
- get { return base.Expression; }
- set { base.Expression = value; }
- }
- /// <summary>
- /// This property is not relevant to this class.
- /// </summary>
- [Browsable(false)]
- public new bool Calculated
- {
- get { return base.Calculated; }
- set { base.Calculated = value; }
- }
- /// <summary>
- /// Gets the additional filter settings.
- /// </summary>
- 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<Indices, ArrayList> sortedChildRows = relation_SortedChildRows[relation] as SortedList<Indices, ArrayList>;
- if (sortedChildRows == null)
- {
- sortedChildRows = new SortedList<Indices, ArrayList>();
- 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
- /// <summary>
- /// Gets data stored in a specified column.
- /// </summary>
- /// <param name="alias">The column alias.</param>
- /// <returns>An object that contains the data.</returns>
- 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);
- }
- /// <summary>
- /// Gets data stored in a specified column.
- /// </summary>
- /// <param name="column">The column.</param>
- /// <returns>An object that contains the data.</returns>
- protected abstract object GetValue(Column column);
- #endregion
- #region Public Methods
- /// <summary>
- /// Initializes the datasource schema.
- /// </summary>
- /// <remarks>
- /// This method is used to support the FastReport.Net infrastructure. Do not call it directly.
- /// </remarks>
- public abstract void InitSchema();
- /// <summary>
- /// Loads the datasource with data.
- /// </summary>
- /// <remarks>
- /// This method is used to support the FastReport.Net infrastructure. Do not call it directly.
- /// </remarks>
- /// <param name="rows">Rows to fill with data.</param>
- 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);
- }
- /// <summary>
- /// Initializes this datasource.
- /// </summary>
- /// <remarks>
- /// This method fills the table with data. You should always call it before using most of
- /// datasource properties.
- /// </remarks>
- public void Init()
- {
- Init("");
- }
- /// <summary>
- /// Initializes this datasource and applies the specified filter.
- /// </summary>
- /// <param name="filter">The filter expression.</param>
- public void Init(string filter)
- {
- Init(filter, null);
- }
- /// <summary>
- /// Initializes this datasource, applies the specified filter and sorts the rows.
- /// </summary>
- /// <param name="filter">The filter expression.</param>
- /// <param name="sort">The collection of sort descriptors.</param>
- public void Init(string filter, SortCollection sort)
- {
- DataSourceBase parentData = null;
- Init(parentData, filter, sort);
- }
- /// <summary>
- /// Initializes this datasource and filters data rows according to the master-detail relation between
- /// this datasource and <b>parentData</b>.
- /// </summary>
- /// <param name="parentData">Parent datasource.</param>
- /// <remarks>
- /// To use master-detail relation, you must define the <see cref="Relation"/> object that describes
- /// the relation, and add it to the <b>Report.Dictionary.Relations</b> collection.
- /// </remarks>
- public void Init(DataSourceBase parentData)
- {
- Init(parentData, "", null);
- }
- /// <summary>
- /// Initializes this datasource and filters data rows according to the master-detail relation between
- /// this datasource and <b>parentData</b>. Also applies the specified filter and sorts the rows.
- /// </summary>
- /// <param name="parentData">Parent datasource.</param>
- /// <param name="filter">The filter expression.</param>
- /// <param name="sort">The collection of sort descriptors.</param>
- /// <remarks>
- /// To use master-detail relation, you must define the <see cref="Relation"/> object that describes
- /// the relation, and add it to the <b>Report.Dictionary.Relations</b> collection.
- /// </remarks>
- public void Init(DataSourceBase parentData, string filter, SortCollection sort)
- {
- Init(parentData, filter, sort, false);
- }
- /// <summary>
- /// Initializes this datasource and filters data rows according to the master-detail relation.
- /// Also applies the specified filter and sorts the rows.
- /// </summary>
- /// <param name="relation">The master-detail relation.</param>
- /// <param name="filter">The filter expression.</param>
- /// <param name="sort">The collection of sort descriptors.</param>
- /// <remarks>
- /// To use master-detail relation, you must define the <see cref="Relation"/> object that describes
- /// the relation, and add it to the <b>Report.Dictionary.Relations</b> collection.
- /// </remarks>
- 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();
- }
- /// <summary>
- /// Initializes the data source if it is not initialized yet.
- /// </summary>
- public void EnsureInit()
- {
- if (InternalRows.Count == 0)
- Init();
- }
- /// <summary>
- /// Navigates to the first row.
- /// </summary>
- /// <remarks>
- /// You should initialize the datasource by the <b>Init</b> method before using this method.
- /// </remarks>
- public void First()
- {
- CurrentRowNo = 0;
- }
- /// <summary>
- /// Navigates to the next row.
- /// </summary>
- /// <remarks>
- /// You should initialize the datasource by the <b>Init</b> method before using this method.
- /// </remarks>
- public void Next()
- {
- CurrentRowNo++;
- }
- /// <summary>
- /// Navigates to the prior row.
- /// </summary>
- /// <remarks>
- /// You should initialize the datasource by the <b>Init</b> method before using this method.
- /// </remarks>
- public void Prior()
- {
- CurrentRowNo--;
- }
- /// <inheritdoc/>
- public override void Serialize(FRWriter writer)
- {
- base.Serialize(writer);
- if (Enabled)
- writer.WriteBool("Enabled", Enabled);
- if (ForceLoadData)
- writer.WriteBool("ForceLoadData", ForceLoadData);
- }
- /// <inheritdoc/>
- 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;
- }
- /// <inheritdoc/>
- public override void InitializeComponent()
- {
- ClearData();
- }
- #endregion
- /// <summary>
- /// Initializes a new instance of the <see cref="DataSourceBase"/> class with default settings.
- /// </summary>
- 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;
- }
- }
- }
- }
|