//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // Microsoft // Microsoft //------------------------------------------------------------------------------ using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Diagnostics; using System.ComponentModel; using System.Data; using System.Text; using System.Data.DataSetExtensions; namespace System.Data { /// /// Represents a bindable, queryable DataView of DataRow, that can be created from from LINQ queries over DataTable /// and from DataTable. /// internal class LinqDataView : DataView, IBindingList, IBindingListView { /// /// A Comparer that compares a Key and a Row. /// internal Func comparerKeyRow; // comparer for DataView.Find(.. /// /// Builds the sort expression in case multiple selector/comparers are added /// internal readonly SortExpressionBuilder sortExpressionBuilder; /// /// Constructs a LinkDataView and its parent DataView. /// Does not create index on the DataView since filter and sort expressions are not yet provided. /// /// The input table from which LinkDataView is to be created. internal LinqDataView(DataTable table, SortExpressionBuilder sortExpressionBuilder) : base(table) { Debug.Assert(table != null, "null DataTable"); this.sortExpressionBuilder = sortExpressionBuilder ?? new SortExpressionBuilder(); } //I have two forms of predicate because I need to pass in null if predicate is null. Otherwise I need to convert it into delegate and pass it into // data view's constructor. That logic for checking null can't be embedded in the base constructor call. /// /// /// /// Table from which to create the view /// User-provided filter-predicate as a Func, bool>"/> /// User-provided predicate but in the form of System.Predicate /// Predicates are being replicated in different forms so that nulls can be passed in. /// For e.g. when user provides null predicate, base.Predicate should be set to null. I cant do that in the constructor initialization /// if I will have to create System.Predicate delegate from Func. /// /// The comparer function of DataRow to be used for sorting. /// A comparer function that compares a Key value to DataRow. /// Whether sorting is ascending or descending. /// Row state filter. For the purpose of LinkDataView it should always be CurrentRows. internal LinqDataView( DataTable table, Func predicate_func, Predicate predicate_system, Comparison comparison, Func comparerKeyRow, SortExpressionBuilder sortExpressionBuilder) //Parent constructor : base(table, predicate_system, comparison, DataViewRowState.CurrentRows) { this.sortExpressionBuilder = (sortExpressionBuilder == null) ? this.sortExpressionBuilder : sortExpressionBuilder; this.comparerKeyRow = comparerKeyRow; } /// /// Gets or sets the expression used to filter which rows are viewed in the LinqDataView /// public override string RowFilter { get { if (base.RowPredicate == null)//using string based filter or no filter { return base.RowFilter; } else //using expression based filter { return null; } } set { if (value == null) { base.RowPredicate = null; base.RowFilter = String.Empty; //INDEX rebuild twice } else { base.RowFilter = value; base.RowPredicate = null; } } } #region Find /// /// Searches the index and finds a single row where the sort-key matches the input key /// /// Value of the key to find /// Index of the first match of input key internal override int FindByKey(object key) { /////////////// Preconditions //////////////// //check that both string and expression based sort are never simultaneously set Debug.Assert(base.Sort != null); Debug.Assert(!(!String.IsNullOrEmpty(base.Sort) && base.SortComparison != null)); ///////////////////////////////////////////// if (!String.IsNullOrEmpty(base.Sort)) //use find for DV's sort string { return base.FindByKey(key); } else if (base.SortComparison == null) //neither string or expr set { //This is the exception message from DataView that we want to use throw ExceptionBuilder.IndexKeyLength(0, 0); } else //find for expression based sort { if (sortExpressionBuilder.Count !=1) throw DataSetUtil.InvalidOperation(Strings.LDV_InvalidNumOfKeys(sortExpressionBuilder.Count)); Index.ComparisonBySelector compareDelg = new Index.ComparisonBySelector(comparerKeyRow); List keyList = new List(); keyList.Add(key); Range range = FindRecords(compareDelg, keyList); return (range.Count == 0) ? -1 : range.Min; } } /// /// Since LinkDataView does not support multiple selectors/comparers, it does not make sense for /// them to Find using multiple keys. /// This overriden method prevents users calling multi-key find on dataview. /// internal override int FindByKey(object[] key) { //---------Checks ---------------- //must have string or expression based sort specified if (base.SortComparison == null && String.IsNullOrEmpty(base.Sort)) { //This is the exception message from DataView that we want to use throw ExceptionBuilder.IndexKeyLength(0, 0); } else if (base.SortComparison != null && key.Length != sortExpressionBuilder.Count) { throw DataSetUtil.InvalidOperation(Strings.LDV_InvalidNumOfKeys(sortExpressionBuilder.Count)); } //-------------------------------- if (base.SortComparison == null)//using string to sort return base.FindByKey(key); else { Index.ComparisonBySelector compareDelg = new Index.ComparisonBySelector(comparerKeyRow); List keyList = new List(); foreach (object singleKey in key) { keyList.Add(singleKey); } Range range = FindRecords(compareDelg, keyList); return (range.Count == 0) ? -1 : range.Min; } } /// /// Searches the index and finds rows where the sort-key matches the input key. /// Since LinkDataView does not support multiple selectors/comparers, it does not make sense for /// them to Find using multiple keys. This overriden method prevents users calling multi-key find on dataview. /// internal override DataRowView[] FindRowsByKey(object[] key) { //---------Checks ---------------- //must have string or expression based sort specified if (base.SortComparison == null && String.IsNullOrEmpty(base.Sort)) { //This is the exception message from DataView that we want to use throw ExceptionBuilder.IndexKeyLength(0, 0); } else if (base.SortComparison != null && key.Length != sortExpressionBuilder.Count) { throw DataSetUtil.InvalidOperation(Strings.LDV_InvalidNumOfKeys(sortExpressionBuilder.Count)); } //-------------------------------- if (base.SortComparison == null)//using string to sort { return base.FindRowsByKey(key); } else { Range range = FindRecords( new Index.ComparisonBySelector(comparerKeyRow), new List(key)); return base.GetDataRowViewFromRange(range); } } #endregion #region Misc Overrides /// /// Overriding DataView's SetIndex to prevent users from setting RowState filter to anything other /// than CurrentRows. /// internal override void SetIndex(string newSort, DataViewRowState newRowStates, IFilter newRowFilter) { //Throw only if expressions (filter or sort) are used and rowstate is not current rows if ( (base.SortComparison != null || base.RowPredicate != null) && newRowStates != DataViewRowState.CurrentRows) { throw DataSetUtil.Argument(Strings.LDVRowStateError); } else { base.SetIndex(newSort, newRowStates, newRowFilter); } } #endregion #region IBindingList /// /// Clears both expression-based and DataView's string-based sorting. /// void IBindingList.RemoveSort() { base.Sort = String.Empty; base.SortComparison = null; } /// /// Overrides IBindingList's SortProperty so that it returns null if expression based sort /// is used in the LinkDataView, otherwise it defers the result to DataView /// PropertyDescriptor IBindingList.SortProperty { get { return (base.SortComparison == null) ? base.GetSortProperty() : null; } } /// /// Overrides IBindingList's SortDescriptions so that it returns null if expression based sort /// is used in the LinkDataView, otherwise it defers the result to DataView /// ListSortDescriptionCollection IBindingListView.SortDescriptions { get { if (base.SortComparison == null) { return base.GetSortDescriptions(); } else { return new ListSortDescriptionCollection(); } } } /// /// Tells whether the LinqDataView is sorted or not /// bool IBindingList.IsSorted { get { //Sorted if either expression based sort or string based sort is set return !(base.SortComparison == null && base.Sort.Length == 0); } } #endregion } }