File: System\Data\EnumerableRowCollection.cs
Project: ndp\fx\src\DataSet\System.Data.DataSetExtensions.csproj (System.Data.DataSetExtensions)
//------------------------------------------------------------------------------
// <copyright file="GenericEnumRowCollection.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------
 
using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.Data;
using System.Linq;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Collections.ObjectModel;
using System.Data.DataSetExtensions;
 
namespace System.Data
{
    /// <summary>
    /// Provides an entry point so that Cast operator call can be intercepted within an extension method.
    /// </summary>
    public abstract class EnumerableRowCollection : IEnumerable
    {
        internal abstract Type ElementType { get; }
        internal abstract DataTable Table { get; }
 
        internal EnumerableRowCollection()
        {
        }
 
        IEnumerator IEnumerable.GetEnumerator()
        {
            return null;
        }
    }
 
    /// <summary>
    /// This class provides a wrapper for DataTables to allow for querying via LINQ.
    /// </summary>
    public class EnumerableRowCollection<TRow> : EnumerableRowCollection, IEnumerable<TRow>
    {
        private readonly DataTable _table;
        private readonly IEnumerable<TRow> _enumerableRows;
        private readonly List<Func<TRow, bool>> _listOfPredicates;
 
        // Stores list of sort expression in the order provided by user. E.g. order by, thenby, thenby descending..
        private readonly SortExpressionBuilder<TRow> _sortExpression;
 
        private readonly Func<TRow, TRow> _selector;
 
        #region Properties
 
        internal override Type ElementType
        {
            get
            {
                return typeof(TRow);
            }
 
        }
 
        internal IEnumerable<TRow> EnumerableRows
        {
            get
            {
                return _enumerableRows;
            }
        }
 
        internal override DataTable Table
        {
            get
            {
                return _table;
            }
        }
 
 
        #endregion Properties
 
        #region Constructors
 
        /// <summary>
        /// This constructor is used when Select operator is called with output Type other than input row Type.
        /// Basically fail on GetLDV(), but other LINQ operators must work.
        /// </summary>
        internal EnumerableRowCollection(IEnumerable<TRow> enumerableRows, bool isDataViewable, DataTable table)
        {
            Debug.Assert(!isDataViewable || table != null, "isDataViewable bug table is null");
 
            _enumerableRows = enumerableRows;
            if (isDataViewable)
            {
                _table = table;
            }
            _listOfPredicates = new List<Func<TRow, bool>>();
            _sortExpression = new SortExpressionBuilder<TRow>();
        }
 
        /// <summary>
        /// Basic Constructor
        /// </summary>
        internal EnumerableRowCollection(DataTable table)
        {
            _table = table;
            _enumerableRows = table.Rows.Cast<TRow>();
            _listOfPredicates = new List<Func<TRow, bool>>();
            _sortExpression = new SortExpressionBuilder<TRow>();
        }
 
        /// <summary>
        /// Copy Constructor that sets the input IEnumerable as enumerableRows
        /// Used to maintain IEnumerable that has linq operators executed in the same order as the user
        /// </summary>
        internal EnumerableRowCollection(EnumerableRowCollection<TRow> source, IEnumerable<TRow> enumerableRows, Func<TRow, TRow> selector)
        {
            Debug.Assert(null != enumerableRows, "null enumerableRows");
 
            _enumerableRows = enumerableRows;
            _selector = selector;
            if (null != source)
            {
                if (null == source._selector)
                {
                    _table = source._table;
                }
                _listOfPredicates = new List<Func<TRow, bool>>(source._listOfPredicates);
                _sortExpression = source._sortExpression.Clone(); //deep copy the List
            }
            else
            {
                _listOfPredicates = new List<Func<TRow, bool>>();
                _sortExpression = new SortExpressionBuilder<TRow>();
            }
        }
 
        #endregion Constructors
 
        #region PublicInterface
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
 
        /// <summary>
        ///  This method returns an strongly typed iterator
        ///  for the underlying DataRow collection.
        /// </summary>
        /// <returns>
        ///   A strongly typed iterator.
        /// </returns>
        public IEnumerator<TRow> GetEnumerator()
        {
            return _enumerableRows.GetEnumerator();
        }
        #endregion PublicInterface
 
        /// <summary>
        /// Evaluates filter and sort if necessary and returns
        /// a LinqDataView representing the LINQ query this class has collected.
        /// </summary>
        /// <returns>LinqDataView repesenting the LINQ query</returns>
        internal LinqDataView GetLinqDataView() //Called by AsLinqDataView
        {
            if ((null == _table) || !typeof(DataRow).IsAssignableFrom(typeof(TRow)))
            {
                throw DataSetUtil.NotSupported(Strings.ToLDVUnsupported);
            }
 
            LinqDataView view = null;
 
            #region BuildSinglePredicate
 
            Func<DataRow, bool> finalPredicate = null; //Conjunction of all .Where(..) predicates
            if ((null != _selector) && (0 < _listOfPredicates.Count))
            {
                // Hook up all individual predicates into one predicate
                // This delegate is a conjunction of multiple predicates set by the user
                // Note: This is a Short-Circuit Conjunction
                finalPredicate =
                    delegate(DataRow row)
                    {
                        if (!Object.ReferenceEquals(row, _selector((TRow)(object)row)))
                        {
                            throw DataSetUtil.NotSupported(Strings.ToLDVUnsupported);
                        }
                        foreach (Func<TRow, bool> pred in _listOfPredicates)
                        {
                            if (!pred((TRow)(object)row))
                            {
                                return false;
                            }
                        }
                        return true;
                    };
            }
            else if (null != _selector)
            {
                finalPredicate =
                    delegate(DataRow row)
                    {
                        if (!Object.ReferenceEquals(row, _selector((TRow)(object)row)))
                        {
                            throw DataSetUtil.NotSupported(Strings.ToLDVUnsupported);
                        }
                        return true;
                    };
            }
            else if (0 < _listOfPredicates.Count)
            {
                finalPredicate =
                    delegate(DataRow row)
                    {
                        foreach (Func<TRow, bool> pred in _listOfPredicates)
                        {
                            if (!pred((TRow)(object)row))
                            {
                                return false;
                            }
                        }
                        return true;
                    };
            }
            #endregion BuildSinglePredicate
 
            #region Evaluate Filter/Sort
            //  All of this mess below is because we want to create index only once.
            //
            //  If we only have filter, we set _view.Predicate       - 1 index creation
            //  If we only have sort, we set _view.SortExpression()  - 1 index creation
            //  If we have BOTH, we set them through the constructor - 1 index creation
            //
 
            // Filter AND Sort
            if ((null != finalPredicate) && (0 < _sortExpression.Count))
            {
                // A lot more work here because constructor does not know type K,
                // so the responsibility to create appropriate delegate comparers
                // is outside of the constructor.
 
                view = new LinqDataView(
                               _table,
                               finalPredicate,                      //Func() Predicate
                               delegate(DataRow row)                //System.Predicate
                               {
                                   return finalPredicate(row);
                               },
                               delegate(DataRow a, DataRow b)       //Comparison for DV for Index creation
                               {
                                   return _sortExpression.Compare(
                                            _sortExpression.Select((TRow)(object)a),
                                            _sortExpression.Select((TRow)(object)b)
                                       );
                               },
                               delegate(object key, DataRow row)    //Comparison_K_T for DV's Find()
                               {
                                   return _sortExpression.Compare(
                                        (List<object>)key,
                                        _sortExpression.Select((TRow)(object)row)
                                      );
                               },
                                _sortExpression.CloneCast<DataRow>());
            }
            else if (null != finalPredicate)
            {
                //Only Filtering
                view = new LinqDataView(
                                    _table,
                                    finalPredicate,
                                    delegate(DataRow row)                //System.Predicate
                                    {
                                        return finalPredicate(row);
                                    },
                                    null,
                                    null,
                                    _sortExpression.CloneCast<DataRow>());
            }
            else if (0 < _sortExpression.Count)
            {
                //Only Sorting
                view = new LinqDataView(
                            _table,
                            null,
                            null,
                            delegate(DataRow a, DataRow b)
                            {
                                return _sortExpression.Compare(_sortExpression.Select((TRow)(object)a), _sortExpression.Select((TRow)(object)b));
                            },
                            delegate(object key, DataRow row)
                            {
                                return _sortExpression.Compare((List<object>)key, _sortExpression.Select((TRow)(object)row));
                            },
                            _sortExpression.CloneCast<DataRow>());
            }
            else
            {
                view = new LinqDataView(_table, _sortExpression.CloneCast<DataRow>());
            }
            #endregion Evaluate Filter and Sort
 
            return view;
        }
 
 
        #region Add Single Filter/Sort Expression
 
        /// <summary>
        /// Used to add a filter predicate.
        /// A conjunction of all predicates are evaluated in LinqDataView
        /// </summary>
        internal void AddPredicate(Func<TRow, bool> pred)
        {
            Debug.Assert(pred != null);
            _listOfPredicates.Add(pred);
        }
 
        /// <summary>
        /// Adds a sort expression when Keyselector is provided but not Comparer
        /// </summary>
        internal void AddSortExpression<TKey>(Func<TRow, TKey> keySelector, bool isDescending, bool isOrderBy)
        {
            AddSortExpression<TKey>(keySelector, Comparer<TKey>.Default, isDescending, isOrderBy);
        }
 
        /// <summary>
        /// Adds a sort expression when Keyselector and Comparer are provided.
        /// </summary>
        internal void AddSortExpression<TKey>(
                            Func<TRow, TKey> keySelector,
                            IComparer<TKey> comparer,
                            bool isDescending,
                            bool isOrderBy)
        {
            DataSetUtil.CheckArgumentNull(keySelector, "keySelector");
            DataSetUtil.CheckArgumentNull(comparer, "comparer");
 
            _sortExpression.Add(
                    delegate(TRow input)
                    {
                        return (object)keySelector(input);
                    },
                    delegate(object val1, object val2)
                    {
                        return (isDescending ? -1 : 1) * comparer.Compare((TKey)val1, (TKey)val2);
                    },
                      isOrderBy);
        }
 
        #endregion Add Single Filter/Sort Expression
 
    }
 
}