//------------------------------------------------------------------------------
//
// 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
}
}