| File: System\Data\LinqDataView.cs | Web Access |
| Project: src\runtime\src\libraries\System.Data.Common\src\System.Data.Common.csproj (System.Data.Common) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace System.Data { /// <summary> /// Represents a bindable, queryable DataView of DataRow, that can be created from from LINQ queries over DataTable /// and from DataTable. /// </summary> internal sealed class LinqDataView : DataView, IBindingList, IBindingListView { /// <summary> /// A Comparer that compares a Key and a Row. /// </summary> internal Func<object, DataRow, int>? comparerKeyRow; // comparer for DataView.Find(.. /// <summary> /// Builds the sort expression in case multiple selector/comparers are added. /// </summary> internal readonly SortExpressionBuilder<DataRow>? sortExpressionBuilder; /// <summary> /// Constructs a LinkDataView and its parent DataView. /// Does not create index on the DataView since filter and sort expressions are not yet provided. /// </summary> /// <param name="table">The input table from which LinkDataView is to be created.</param> /// <param name="sortExpressionBuilder">The sort expression builder in case multiple selectors/comparers are added.</param> internal LinqDataView(DataTable table, SortExpressionBuilder<DataRow>? sortExpressionBuilder) : base(table) { Debug.Assert(table != null, "null DataTable"); this.sortExpressionBuilder = sortExpressionBuilder ?? new SortExpressionBuilder<DataRow>(); } /// <summary> /// /// </summary> /// <param name="table">Table from which to create the view</param> /// <param name="predicate_system">User-provided predicate but in the form of System.Predicate<DataRow> /// 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. /// </param> /// <param name="comparison">The comparer function of DataRow to be used for sorting. </param> /// <param name="comparerKeyRow">A comparer function that compares a Key value to DataRow.</param> /// <param name="sortExpressionBuilder">Combined sort expression build using mutiple sort expressions.</param> internal LinqDataView( DataTable table, Predicate<DataRow>? predicate_system, Comparison<DataRow>? comparison, Func<object, DataRow, int>? comparerKeyRow, SortExpressionBuilder<DataRow>? sortExpressionBuilder) : base(table, predicate_system, comparison, DataViewRowState.CurrentRows) { this.sortExpressionBuilder = sortExpressionBuilder ?? this.sortExpressionBuilder; this.comparerKeyRow = comparerKeyRow; } /// <summary> /// Gets or sets the expression used to filter which rows are viewed in the LinqDataView /// </summary> 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; } } [RequiresUnreferencedCode(Select.RequiresUnreferencedCodeMessage)] set { if (value == null) { base.RowPredicate = null; base.RowFilter = string.Empty; // INDEX rebuild twice } else { base.RowFilter = value; base.RowPredicate = null; } } } #region Find /// <summary> /// Searches the index and finds a single row where the sort-key matches the input key /// </summary> /// <param name="key">Value of the key to find</param> /// <returns>Index of the first match of input key</returns> internal override int FindByKey(object? key) { Debug.Assert(base.Sort != null); Debug.Assert(!(!string.IsNullOrEmpty(base.Sort) && base.SortComparison != null), "string and expression based sort cannot both be set"); 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(SR.Format(SR.LDV_InvalidNumOfKeys, sortExpressionBuilder.Count)); Index.ComparisonBySelector<object, DataRow> compareDelg = new Index.ComparisonBySelector<object, DataRow>(comparerKeyRow!); List<object?> keyList = new List<object?>(); keyList.Add(key); Range range = FindRecords<object, DataRow>(compareDelg, keyList); return (range.Count == 0) ? -1 : range.Min; } } /// <summary> /// Since LinkDataView does not support multiple selectors/comparers, it does not make sense for /// them to Find using multiple keys. /// This overridden method prevents users calling multi-key find on dataview. /// </summary> internal override int FindByKey(object?[] key) { // 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(SR.Format(SR.LDV_InvalidNumOfKeys, sortExpressionBuilder.Count)); } if (base.SortComparison == null) { // using string to sort return base.FindByKey(key); } else { Index.ComparisonBySelector<object, DataRow> compareDelg = new Index.ComparisonBySelector<object, DataRow>(comparerKeyRow!); List<object?> keyList = new List<object?>(); foreach (object? singleKey in key) { keyList.Add(singleKey); } Range range = FindRecords<object, DataRow>(compareDelg, keyList); return (range.Count == 0) ? -1 : range.Min; } } /// <summary> /// 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 overridden method prevents users calling multi-key find on dataview. /// </summary> internal override DataRowView[] FindRowsByKey(object?[] key) { // 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(SR.Format(SR.LDV_InvalidNumOfKeys, sortExpressionBuilder.Count)); } if (base.SortComparison == null)// using string to sort { return base.FindRowsByKey(key); } else { Range range = FindRecords<object, DataRow>( new Index.ComparisonBySelector<object, DataRow>(comparerKeyRow!), new List<object?>(key)); return base.GetDataRowViewFromRange(range); } } #endregion #region Misc Overrides /// <summary> /// Overriding DataView's SetIndex to prevent users from setting RowState filter to anything other /// than CurrentRows. /// </summary> 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(SR.LDVRowStateError); } else { base.SetIndex(newSort, newRowStates, newRowFilter); } } #endregion #region IBindingList /// <summary> /// Clears both expression-based and DataView's string-based sorting. /// </summary> void IBindingList.RemoveSort() { base.Sort = string.Empty; base.SortComparison = null; } /// <summary> /// 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 /// </summary> PropertyDescriptor? IBindingList.SortProperty { get { return (base.SortComparison == null) ? base.GetSortProperty() : null; } } /// <summary> /// 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 /// </summary> ListSortDescriptionCollection IBindingListView.SortDescriptions { get { if (base.SortComparison == null) { return base.GetSortDescriptions(); } else { return new ListSortDescriptionCollection(); } } } /// <summary> /// Tells whether the LinqDataView is sorted or not /// </summary> 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 } }