File: UI\WebControls\QueryableDataSourceView.cs
Project: ndp\fx\src\xsp\system\Extensions\System.Web.Extensions.csproj (System.Web.Extensions)
namespace System.Web.UI.WebControls {
    using System.Web.UI.WebControls.Expressions;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Globalization;
    using System.Linq;
    using System.Security.Permissions;
    using System.Web;
    using System.Web.Resources;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Diagnostics.CodeAnalysis;
    using System.ComponentModel;
 
 
    public abstract class QueryableDataSourceView : DataSourceView, IStateManager {
        //basic query parameters for any Queryable source
        private ParameterCollection _whereParameters;
        private ParameterCollection _orderByParameters;
        private ParameterCollection _orderGroupsByParameters;
        private ParameterCollection _selectNewParameters;
        private ParameterCollection _groupByParameters;
 
        // CUD operations
        private ParameterCollection _deleteParameters;
        private ParameterCollection _updateParameters;
        private ParameterCollection _insertParameters;
 
        private HttpContext _context;
        private DataSourceControl _owner;
        private IDynamicQueryable _queryable;
 
        private string _groupBy;
        private string _orderBy;
        private string _orderGroupsBy;
        private string _selectNew;
        private string _where;
 
        private bool _autoGenerateOrderByClause;
        private bool _autoGenerateWhereClause;
        private bool _autoPage = true;
        private bool _autoSort = true;
        private bool _isTracking;
 
        protected static readonly object EventSelected = new object();
        protected static readonly object EventSelecting = new object();
        private static readonly object EventQueryCreated = new object();
 
        // using Hashtable for original values so that ObjectStateFormatter will serialize it properly in ViewState.
        private Hashtable _originalValues;
 
 
        protected QueryableDataSourceView(DataSourceControl owner, string viewName, HttpContext context)
            : this(owner, viewName, context, new DynamicQueryableWrapper()) {
            _context = context;
            _owner = owner;
        }
 
        internal QueryableDataSourceView(DataSourceControl owner, string viewName, HttpContext context, IDynamicQueryable queryable)
            : base(owner, viewName) {
            _context = context;
            _queryable = queryable;
            _owner = owner;
        }
 
        public bool AutoGenerateOrderByClause {
            get {
                return _autoGenerateOrderByClause;
            }
            set {
                if (_autoGenerateOrderByClause != value) {
                    _autoGenerateOrderByClause = value;
                    OnDataSourceViewChanged(EventArgs.Empty);
                }
            }
        }
 
        public bool AutoGenerateWhereClause {
            get {
                return _autoGenerateWhereClause;
            }
            set {
                if (_autoGenerateWhereClause != value) {
                    _autoGenerateWhereClause = value;
                    OnDataSourceViewChanged(EventArgs.Empty);
                }
            }
        }
 
        public virtual bool AutoPage {
            get {
                return _autoPage;
            }
            set {
                if (_autoPage != value) {
                    _autoPage = value;
                    OnDataSourceViewChanged(EventArgs.Empty);
                }
            }
        }
 
        public virtual bool AutoSort {
            get {
                return _autoSort;
            }
            set {
                if (_autoSort != value) {
                    _autoSort = value;
                    OnDataSourceViewChanged(EventArgs.Empty);
                }
            }
        }
 
        public override bool CanDelete {
            get {
                return false;
            }
        }
 
        public override bool CanInsert {
            get {
                return false;
            }
        }
 
        // When AutoPage is false the user should manually page in the Selecting event.
        public override bool CanPage {
            get {
                return true;
            }
        }
 
        // When AutoPage is false the user must set the total row count in the Selecting event.
        public override bool CanRetrieveTotalRowCount {
            get {
                return true;
            }
        }
 
        // When AutoSort is false the user should manually sort in the Selecting event.
        public override bool CanSort {
            get {
                return true;
            }
        }
 
        public override bool CanUpdate {
            get {
                return false;
            }
        }
 
        public virtual ParameterCollection DeleteParameters {
            get {
                if (_deleteParameters == null) {
                    _deleteParameters = new ParameterCollection();
                }
                return _deleteParameters;
            }
        }
 
        protected abstract Type EntityType {
            get;            
        }
 
        public virtual ParameterCollection GroupByParameters {
            get {
                if (_groupByParameters == null) {
                    _groupByParameters = new ParameterCollection();
                    _groupByParameters.ParametersChanged += new EventHandler(OnQueryParametersChanged);
                    if (_isTracking) {
                        DataSourceHelper.TrackViewState(_groupByParameters);
                    }
                }
                return _groupByParameters;
            }
        }
 
        protected bool IsTrackingViewState {
            get {
                return _isTracking;
            }
        }
 
        public virtual ParameterCollection InsertParameters {
            get {
                if (_insertParameters == null) {
                    _insertParameters = new ParameterCollection();
                }
                return _insertParameters;
            }
        }
 
        public virtual ParameterCollection OrderByParameters {
            get {
                if (_orderByParameters == null) {
                    _orderByParameters = new ParameterCollection();
                    _orderByParameters.ParametersChanged += new EventHandler(OnQueryParametersChanged);
                    if (_isTracking) {
                        DataSourceHelper.TrackViewState(_orderByParameters);
                    }
                }
                return _orderByParameters;
            }
        }
 
        public virtual ParameterCollection OrderGroupsByParameters {
            get {
                if (_orderGroupsByParameters == null) {
                    _orderGroupsByParameters = new ParameterCollection();
                    _orderGroupsByParameters.ParametersChanged += new EventHandler(OnQueryParametersChanged);
                    if (_isTracking) {
                        DataSourceHelper.TrackViewState(_orderGroupsByParameters);
                    }
                }
                return _orderGroupsByParameters;
            }
        }
 
 
        public virtual string OrderBy {
            get {
                return _orderBy ?? String.Empty;
            }
            set {
                if (_orderBy != value) {
                    _orderBy = value;
                    OnDataSourceViewChanged(EventArgs.Empty);
                }
            }
        }
 
 
        public virtual string OrderGroupsBy {
            get {
                return _orderGroupsBy ?? String.Empty;
            }
            set {
                if (_orderGroupsBy != value) {
                    _orderGroupsBy = value;
                    OnDataSourceViewChanged(EventArgs.Empty);
                }
            }
        }
 
        public virtual string GroupBy {
            get {
                return _groupBy ?? String.Empty;
            }
            set {
                if (_groupBy != value) {
                    _groupBy = value;
                    OnDataSourceViewChanged(EventArgs.Empty);
                }
            }
        }
 
        public virtual ParameterCollection SelectNewParameters {
            get {
                if (_selectNewParameters == null) {
                    _selectNewParameters = new ParameterCollection();
                    _selectNewParameters.ParametersChanged += new EventHandler(OnQueryParametersChanged);
                    if (_isTracking) {
                        DataSourceHelper.TrackViewState(_selectNewParameters);
                    }
                }
                return _selectNewParameters;
            }
        }
 
        [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix",
            Justification = "SelectNew refers to a projection and 'New' is not used as a suffix in this case")]
        public virtual string SelectNew {
            get {
                return _selectNew ?? String.Empty;
            }
            set {
                if (_selectNew != value) {
                    _selectNew = value;
                    OnDataSourceViewChanged(EventArgs.Empty);
                }
            }
        }
 
        public virtual ParameterCollection WhereParameters {
            get {
                if (_whereParameters == null) {
                    _whereParameters = new ParameterCollection();
                    _whereParameters.ParametersChanged += new EventHandler(OnQueryParametersChanged);
                    if (_isTracking) {
                        DataSourceHelper.TrackViewState(_whereParameters);
                    }
                }
                return _whereParameters;
            }
        }
 
        public virtual string Where {
            get {
                return _where ?? String.Empty;
            }
            set {
                if (_where != value) {
                    _where = value;
                    OnDataSourceViewChanged(EventArgs.Empty);
                }
            }
        }
 
 
        public virtual ParameterCollection UpdateParameters {
            get {
                if (_updateParameters == null) {
                    _updateParameters = new ParameterCollection();
                }
                return _updateParameters;
            }
        }
 
 
        [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", Justification = "Derived classes will use this to as the ParametersChanged EventHandler")]
        protected void OnQueryParametersChanged(object sender, EventArgs e) {            
            RaiseViewChanged();
        }
 
        [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "An event exists already and it it protected")]
        public void RaiseViewChanged() {
            OnDataSourceViewChanged(EventArgs.Empty);
        }
 
        /// <summary>
        /// Gets the result to apply query
        /// </summary>
        /// <returns></returns>
        protected abstract object GetSource(QueryContext context);
 
        protected QueryContext CreateQueryContext(DataSourceSelectArguments arguments) {
            IDictionary<string, object> whereParameters = WhereParameters.ToDictionary(_context, _owner);
            IOrderedDictionary orderByParameters = OrderByParameters.GetValues(_context, _owner).ToCaseInsensitiveDictionary();
            IDictionary<string, object> orderGroupsByParameters = OrderGroupsByParameters.ToDictionary(_context, _owner);
            IDictionary<string, object> selectNewParameters = SelectNewParameters.ToDictionary(_context, _owner);
            IDictionary<string, object> groupByParameters = GroupByParameters.ToDictionary(_context, _owner);
 
            return new QueryContext(
                whereParameters,
                orderGroupsByParameters,
                orderByParameters,
                groupByParameters,
                selectNewParameters,
                arguments);
        }
 
        /// <summary>
        /// Creates a select expression based on parameters and properties
        /// </summary>       
        protected virtual IQueryable BuildQuery(DataSourceSelectArguments arguments) {
            if (arguments == null) {
                throw new ArgumentNullException("arguments");
            }
 
            // Create the query context
            QueryContext context = CreateQueryContext(arguments);
 
            // Clear out old values before selecting new data
            _originalValues = null;
 
            // Get the source of the query(root IQueryable)
            object result = GetSource(context);
 
            if (result != null) {
                IQueryable source = QueryableDataSourceHelper.AsQueryable(result);
                // Apply additional filterting
                return ExecuteQuery(source, context);
            }
            return null;
        }
 
        protected virtual IQueryable ExecuteQuery(IQueryable source, QueryContext context) {
            // Execute Query
            source = ExecuteQueryExpressions(source, context);
 
            // Execute Sorting
            source = ExecuteSorting(source, context);
 
            // Execute Paging
            source = ExecutePaging(source, context);
 
            return source;
        }
 
        protected IQueryable ExecuteQueryExpressions(IQueryable source, QueryContext context) {
            if (source != null) {                
                QueryCreatedEventArgs queryArgs = new QueryCreatedEventArgs(source);
                OnQueryCreated(queryArgs);
                source = queryArgs.Query ?? source;
 
                // Support the Dynamic Expression language used by LinqDataSource
                if (AutoGenerateWhereClause) {
                    if (!String.IsNullOrEmpty(Where)) {
                        throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
                        AtlasWeb.LinqDataSourceView_WhereAlreadySpecified, _owner.ID));
                    }
                    source = QueryableDataSourceHelper.CreateWhereExpression(context.WhereParameters, source, _queryable);
                }
                else if (!String.IsNullOrEmpty(Where)) {
                    source = _queryable.Where(source, Where, context.WhereParameters.ToEscapedParameterKeys(_owner));
                }
 
                if (AutoGenerateOrderByClause) {
                    if (!String.IsNullOrEmpty(OrderBy)) {
                        throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
                        AtlasWeb.LinqDataSourceView_OrderByAlreadySpecified, _owner.ID));
                    }
                    source = QueryableDataSourceHelper.CreateOrderByExpression(context.OrderByParameters, source, _queryable);
                }
                else if (!String.IsNullOrEmpty(OrderBy)) {
                    source = _queryable.OrderBy(source, OrderBy, context.OrderByParameters.ToEscapedParameterKeys(_owner));
                }
 
 
                string groupBy = GroupBy;
                if (String.IsNullOrEmpty(groupBy)) {
                    if (!String.IsNullOrEmpty(OrderGroupsBy)) {
                        throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
                            AtlasWeb.LinqDataSourceView_OrderGroupsByRequiresGroupBy, _owner.ID));
                    }
                }
                else {
                    source = _queryable.GroupBy(source, groupBy, "it", context.GroupByParameters.ToEscapedParameterKeys(_owner));
                    if (!String.IsNullOrEmpty(OrderGroupsBy)) {
                        source = _queryable.OrderBy(source, OrderGroupsBy, context.OrderGroupsByParameters.ToEscapedParameterKeys(_owner));
                    }
                }
 
                if (!String.IsNullOrEmpty(SelectNew)) {
                    source = _queryable.Select(source, SelectNew, context.SelectParameters.ToEscapedParameterKeys(_owner));
                }
 
                return source;
            }
 
            return source;
        }
 
        protected IQueryable ExecuteSorting(IQueryable source, QueryContext context) {
            string sortExpression = context.Arguments.SortExpression;
 
            if (CanSort && AutoSort && !String.IsNullOrEmpty(sortExpression)) {
                source = _queryable.OrderBy(source, sortExpression);
            }
            return source;
        }
 
        protected IQueryable ExecutePaging(IQueryable source, QueryContext context) {
            if (CanPage && AutoPage) {
                if (CanRetrieveTotalRowCount && context.Arguments.RetrieveTotalRowCount) {
                    context.Arguments.TotalRowCount = _queryable.Count(source);
                }
 
                if ((context.Arguments.MaximumRows > 0) && (context.Arguments.StartRowIndex >= 0)) {
                    source = _queryable.Skip(source, context.Arguments.StartRowIndex);
                    source = _queryable.Take(source, context.Arguments.MaximumRows);
                }
            }
            else if (context.Arguments.RetrieveTotalRowCount && (context.Arguments.TotalRowCount == -1)) {
                throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
                                    AtlasWeb.LinqDataSourceView_PagingNotHandled, _owner.ID));
            }
            return source;
        }
 
        protected virtual void LoadViewState(object savedState) {
            if (savedState != null) {
                object[] myState = (object[])savedState;
 
                if (myState[0] != null) {
                    ((IStateManager)WhereParameters).LoadViewState(myState[0]);
                }
                if (myState[1] != null) {
                    ((IStateManager)OrderByParameters).LoadViewState(myState[1]);
                }
                if (myState[2] != null) {
                    ((IStateManager)GroupByParameters).LoadViewState(myState[2]);
                }
                if (myState[3] != null) {
                    ((IStateManager)OrderGroupsByParameters).LoadViewState(myState[3]);
                }
                if (myState[4] != null) {
                    ((IStateManager)SelectNewParameters).LoadViewState(myState[4]);
                }
                if (myState[5] != null) {
                    _originalValues = (Hashtable)myState[5];
                }
            }
        }
 
        protected virtual object SaveViewState() {
            object[] myState = new object[6];
            myState[0] = DataSourceHelper.SaveViewState(_whereParameters);
            myState[1] = DataSourceHelper.SaveViewState(_orderByParameters);
            myState[2] = DataSourceHelper.SaveViewState(_groupByParameters);
            myState[3] = DataSourceHelper.SaveViewState(_orderGroupsByParameters);
            myState[4] = DataSourceHelper.SaveViewState(_selectNewParameters);
            if ((_originalValues != null) && (_originalValues.Count > 0)) {
                myState[5] = _originalValues;
            }
 
            return myState;
        }
 
 
        protected virtual void TrackViewState() {
            _isTracking = true;
            DataSourceHelper.TrackViewState(_whereParameters);
            DataSourceHelper.TrackViewState(_orderByParameters);
            DataSourceHelper.TrackViewState(_groupByParameters);
            DataSourceHelper.TrackViewState(_orderGroupsByParameters);
            DataSourceHelper.TrackViewState(_selectNewParameters);
        }
 
        protected internal override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments) {
            ClearOriginalValues();
            
            IQueryable source = BuildQuery(arguments);
 
            IList results = source.ToList(source.ElementType);
 
            // Store original values for concurrency
            StoreOriginalValues(results);
 
            return results;
        }
 
        protected void ClearOriginalValues() {
            _originalValues = null;
        }        
 
        protected virtual IDictionary GetOriginalValues(IDictionary keys) {
            // Table data is stored in a hashtable with column names for keys and an ArrayList of row data for values.
            // i.e, Hashtable { ID = ArrayList { 0, 1, 2 }, Name = ArrayList { "A", "B", "C" } }
            if (_originalValues != null) {
                // matches list keeps track of row indexes which match.
                List<bool> matches = new List<bool>();
                foreach (DictionaryEntry entry in keys) {
                    string propertyName = (String)entry.Key;
                    if (_originalValues.ContainsKey(propertyName)) {
                        object propertyValue = entry.Value;
                        // get the row values for the current column.
                        ArrayList values = (ArrayList)_originalValues[propertyName];
                        for (int i = 0; i < values.Count; i++) {
                            if (matches.Count <= i) { // first column
                                matches.Add(OriginalValueMatches(values[i], propertyValue));
                            }
                            else if (matches[i] == true) { // subsequent columns
                                matches[i] = OriginalValueMatches(values[i], propertyValue);
                            }
                        }
                    }
                }
 
                int rowIndex = matches.IndexOf(true);
                // no rows match or too many rows match.
                if ((rowIndex < 0) || (matches.IndexOf(true, rowIndex + 1) >= 0)) {
                    throw new InvalidOperationException(AtlasWeb.LinqDataSourceView_OriginalValuesNotFound);
                }
                // get original values for the matching row.
                Dictionary<string, object> rowValues = new Dictionary<string, object>(_originalValues.Count,
                    StringComparer.OrdinalIgnoreCase);
                foreach (DictionaryEntry entry in _originalValues) {
                    ArrayList value = (ArrayList)entry.Value;
                    rowValues.Add((string)entry.Key, value[rowIndex]);
                }
                return rowValues;
            }
 
            return null;
        }
 
        protected virtual void StoreOriginalValues(IList results) {
            // Derived classes can override this function and call the overload
            // to determine which columns it should store for optimistic concurrency
        }
 
        protected void StoreOriginalValues(IList results, Func<PropertyDescriptor, bool> include) {
            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(EntityType);
            int numRows = results.Count;
            int maxColumns = props.Count;
            _originalValues = new Hashtable(maxColumns, StringComparer.OrdinalIgnoreCase);
            foreach (PropertyDescriptor p in props) {
                if (include(p) && p.PropertyType.IsSerializable) {
                    ArrayList values = new ArrayList(numRows);
                    _originalValues[p.Name] = values;
                    foreach (object currentRow in results) {
                        values.Add(p.GetValue(currentRow));
                    }
                }
            }
        }
 
 
        public int Update(IDictionary keys, IDictionary values, IDictionary oldValues) {
            return ExecuteUpdate(keys, values, oldValues);
        }
 
        public int Delete(IDictionary keys, IDictionary oldValues) {
            return ExecuteDelete(keys, oldValues);
        }
 
        public int Insert(IDictionary values) {
            return ExecuteInsert(values);
        }
 
        protected QueryableDataSourceEditData BuildDeleteObject(IDictionary keys, IDictionary oldValues, IDictionary<string, Exception> validationErrors) {
            QueryableDataSourceEditData editData = new QueryableDataSourceEditData();
            Type dataObjectType = EntityType;
            IDictionary caseInsensitiveOldValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
            IDictionary originalValues = GetOriginalValues(keys);
 
            ParameterCollection deleteParameters = DeleteParameters;
            if (!DataSourceHelper.MergeDictionaries(dataObjectType,
                                                    deleteParameters,
                                                    keys,
                                                    caseInsensitiveOldValues,
                                                    validationErrors)) {
                return editData;
            }
 
            if (!DataSourceHelper.MergeDictionaries(dataObjectType,
                                                    deleteParameters,
                                                    oldValues,
                                                    caseInsensitiveOldValues,
                                                    validationErrors)) {
                return editData;
 
            }
 
            if (originalValues != null) {
                if (!DataSourceHelper.MergeDictionaries(dataObjectType,
                                                        deleteParameters,
                                                        originalValues,
                                                        caseInsensitiveOldValues,
                                                        validationErrors)) {
                    return editData;
                }
            }
 
            editData.OriginalDataObject = DataSourceHelper.BuildDataObject(dataObjectType, caseInsensitiveOldValues, validationErrors);
            return editData;
        }
 
        protected QueryableDataSourceEditData BuildInsertObject(IDictionary values, IDictionary<string, Exception> validationErrors) {
            QueryableDataSourceEditData editData = new QueryableDataSourceEditData();
            Type dataObjectType = EntityType;
            IDictionary caseInsensitiveNewValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
 
            if (!DataSourceHelper.MergeDictionaries(dataObjectType,
                                                    InsertParameters,
                                                    InsertParameters.GetValues(_context, _owner),
                                                    caseInsensitiveNewValues,
                                                    validationErrors)) {
                return editData;
            }
 
            if (!DataSourceHelper.MergeDictionaries(dataObjectType,
                                                    InsertParameters,
                                                    values,
                                                    caseInsensitiveNewValues, validationErrors)) {
                return editData;
            }
 
            editData.NewDataObject = DataSourceHelper.BuildDataObject(dataObjectType, caseInsensitiveNewValues, validationErrors);
            return editData;
        }
 
        protected QueryableDataSourceEditData BuildUpdateObjects(IDictionary keys, IDictionary values, IDictionary oldValues, IDictionary<string, Exception> validationErrors) {
            QueryableDataSourceEditData editData = new QueryableDataSourceEditData();
            Type dataObjectType = EntityType;
            IDictionary caseInsensitiveNewValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
            IDictionary caseInsensitiveOldValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
            IDictionary originalValues = GetOriginalValues(keys);
 
            // We start out with the old values, just to pre-populate the list with items
            // that might not have corresponding new values. For example if a GridView has
            // a read-only field, there will be an old value, but no new value. The data object
            // still has to have *some* value for a given field, so we just use the old value.
            ParameterCollection updateParameters = UpdateParameters;
 
            // If we have validation errors bail out while merging bailout
 
            if (!DataSourceHelper.MergeDictionaries(dataObjectType,
                                                    updateParameters,
                                                    oldValues,
                                                    caseInsensitiveOldValues,
                                                    caseInsensitiveNewValues,
                                                    validationErrors)) {
                return editData;
            }
 
            if (!DataSourceHelper.MergeDictionaries(dataObjectType,
                                                    updateParameters,
                                                    keys,
                                                    caseInsensitiveOldValues,
                                                    caseInsensitiveNewValues,
                                                    validationErrors)) {
                return editData;
            }
 
            if (originalValues != null) {
                if (!DataSourceHelper.MergeDictionaries(dataObjectType,
                                                        updateParameters,
                                                        originalValues,
                                                        caseInsensitiveOldValues,
                                                        caseInsensitiveNewValues,
                                                        validationErrors)) {
                    return editData;
                }
            }
 
            if (!DataSourceHelper.MergeDictionaries(dataObjectType,
                                                    updateParameters,
                                                    values,
                                                    caseInsensitiveNewValues,
                                                    validationErrors)) {
                return editData;
            }
 
            editData.NewDataObject = DataSourceHelper.BuildDataObject(dataObjectType, caseInsensitiveNewValues, validationErrors);
 
            if (editData.NewDataObject != null) {
                editData.OriginalDataObject = DataSourceHelper.BuildDataObject(dataObjectType, caseInsensitiveOldValues, validationErrors);
            }
            return editData;
        }
 
        protected virtual int DeleteObject(object oldEntity) {
            return 0;
        }
 
        protected virtual int UpdateObject(object oldEntity, object newEntity) {
            return 0;
        }
 
        protected virtual int InsertObject(object newEntity) {
            return 0;
        }
 
        protected abstract void HandleValidationErrors(IDictionary<string, Exception> errors, DataSourceOperation operation);
 
        protected override int ExecuteDelete(IDictionary keys, IDictionary oldValues) {
            IDictionary<string, Exception> errors = new Dictionary<string, Exception>(StringComparer.OrdinalIgnoreCase);
            QueryableDataSourceEditData editData = BuildDeleteObject(keys, oldValues, errors);
 
            if (errors.Any()) {
                HandleValidationErrors(errors, DataSourceOperation.Delete);
            }
            else {
                return DeleteObject(editData.OriginalDataObject);
            }
 
            return -1;
        }
 
        protected override int ExecuteInsert(IDictionary values) {
            IDictionary<string, Exception> errors = new Dictionary<string, Exception>(StringComparer.OrdinalIgnoreCase);
            QueryableDataSourceEditData editData = BuildInsertObject(values, errors);
            if (errors.Any()) {
                HandleValidationErrors(errors, DataSourceOperation.Insert);
            }
            else {
                return InsertObject(editData.NewDataObject);
            }
            return -1;
        }
 
        protected override int ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues) {
            IDictionary<string, Exception> errors = new Dictionary<string, Exception>(StringComparer.OrdinalIgnoreCase);
            QueryableDataSourceEditData editData = BuildUpdateObjects(keys, values, oldValues, errors);
            if (errors.Any()) {
                HandleValidationErrors(errors, DataSourceOperation.Update);
            }
            else {
                return UpdateObject(editData.OriginalDataObject, editData.NewDataObject);
            }
            return -1;
        }
 
        public event EventHandler<QueryCreatedEventArgs> QueryCreated {
            add {
                Events.AddHandler(EventQueryCreated, value);
            }
            remove {
                Events.RemoveHandler(EventQueryCreated, value);
            }
        }
 
        [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#")]
        protected virtual void OnQueryCreated(QueryCreatedEventArgs e) {
            EventHandler<QueryCreatedEventArgs> handler = (EventHandler<QueryCreatedEventArgs>)Events[EventQueryCreated];
            if (handler != null) {
                handler(this, e);
            }
        }
 
        private bool OriginalValueMatches(object originalValue, object value) {
            // NOTE: Comparing IEnumerable contents instead of instances to ensure that
            // timestamp columns (of type byte[]) can be matched appropriately.
            IEnumerable originalValueEnumerable = originalValue as IEnumerable;
            IEnumerable valueEnumerable = value as IEnumerable;
            if ((originalValueEnumerable != null) && (valueEnumerable != null)) {
                return QueryableDataSourceHelper.EnumerableContentEquals(originalValueEnumerable, valueEnumerable);
            }
            return originalValue.Equals(value);
        }
 
        #region IStateManager Members
 
        bool IStateManager.IsTrackingViewState {
            get { return IsTrackingViewState; }
        }
 
        void IStateManager.LoadViewState(object state) {
            LoadViewState(state);
        }
 
        object IStateManager.SaveViewState() {
            return SaveViewState();
        }
 
        void IStateManager.TrackViewState() {
            TrackViewState();
        }
 
        #endregion
    }
}