File: System\Data\LinqDataView.cs
Web Access
Project: src\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&lt;DataRow&gt;
        /// 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
    }
}