|
//------------------------------------------------------------------------------
// <copyright file="LinqDataView.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;
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
{
/// <summary>
/// Represents a bindable, queryable DataView of DataRow, that can be created from from LINQ queries over DataTable
/// and from DataTable.
/// </summary>
internal 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>
internal LinqDataView(DataTable table, SortExpressionBuilder<DataRow> sortExpressionBuilder)
: base(table)
{
Debug.Assert(table != null, "null DataTable");
this.sortExpressionBuilder = sortExpressionBuilder ?? new SortExpressionBuilder<DataRow>();
}
//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.
/// <summary>
///
/// </summary>
/// <param name="table">Table from which to create the view</param>
/// <param name="predicate_func">User-provided filter-predicate as a Func<DataRow>, bool>"/></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="isDescending">Whether sorting is ascending or descending.</param>
/// <param name="rowState">Row state filter. For the purpose of LinkDataView it should always be CurrentRows.</param>
internal LinqDataView(
DataTable table,
Func<DataRow, bool> predicate_func,
Predicate<DataRow> predicate_system,
Comparison<DataRow> comparison,
Func<object, DataRow, int> comparerKeyRow,
SortExpressionBuilder<DataRow> sortExpressionBuilder)
//Parent constructor
: base(table,
predicate_system,
comparison,
DataViewRowState.CurrentRows)
{
this.sortExpressionBuilder = (sortExpressionBuilder == null) ? this.sortExpressionBuilder : 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;
}
}
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)
{
/////////////// 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<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 overriden method prevents users calling multi-key find on dataview.
/// </summary>
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<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 overriden method prevents users calling multi-key find on dataview.
/// </summary>
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<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(Strings.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
}
}
|