|
//------------------------------------------------------------------------------
// <copyright file="ObjectDataSourceView.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web.UI.WebControls {
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Globalization;
using System.Reflection;
using System.Security;
using System.Text;
using System.Web.Compilation;
using System.Web.UI;
using System.Web.Util;
using ConflictOptions = System.Web.UI.ConflictOptions;
/// <devdoc>
/// Represents a single view of an ObjectDataSource.
/// </devdoc>
public class ObjectDataSourceView : DataSourceView, IStateManager {
private static readonly object EventDeleted = new object();
private static readonly object EventDeleting = new object();
private static readonly object EventFiltering = new object();
private static readonly object EventInserted = new object();
private static readonly object EventInserting = new object();
private static readonly object EventObjectCreated = new object();
private static readonly object EventObjectCreating = new object();
private static readonly object EventObjectDisposing = new object();
private static readonly object EventSelected = new object();
private static readonly object EventSelecting = new object();
private static readonly object EventUpdated = new object();
private static readonly object EventUpdating = new object();
private HttpContext _context;
private ObjectDataSource _owner;
private bool _tracking;
private ConflictOptions _conflictDetection = ConflictOptions.OverwriteChanges;
private bool _convertNullToDBNull;
private string _dataObjectTypeName;
private string _deleteMethod;
private ParameterCollection _deleteParameters;
private bool _enablePaging;
private string _filterExpression;
private ParameterCollection _filterParameters;
private string _insertMethod;
private ParameterCollection _insertParameters;
private string _maximumRowsParameterName;
private string _oldValuesParameterFormatString;
private string _selectCountMethod;
private string _selectMethod;
private ParameterCollection _selectParameters;
private string _sortParameterName;
private string _startRowIndexParameterName;
private string _typeName;
private string _updateMethod;
private ParameterCollection _updateParameters;
/// <devdoc>
/// Creates a new ObjectDataSourceView.
/// </devdoc>
public ObjectDataSourceView(ObjectDataSource owner, string name, HttpContext context)
: base(owner, name) {
Debug.Assert(owner != null);
_owner = owner;
_context = context;
}
/// <devdoc>
/// Indicates that the view can delete rows.
/// </devdoc>
public override bool CanDelete {
get {
return (DeleteMethod.Length != 0);
}
}
/// <devdoc>
/// Indicates that the view can add new rows.
/// </devdoc>
public override bool CanInsert {
get {
return (InsertMethod.Length != 0);
}
}
/// <devdoc>
/// Indicates that the view can do server paging.
/// </devdoc>
public override bool CanPage {
get {
return EnablePaging;
}
}
/// <devdoc>
/// Indicates that the view can return the total number of rows returned by the query.
/// </devdoc>
public override bool CanRetrieveTotalRowCount {
get {
// We don't necessarily know if the data source can get the total row count until after the SelectMethod has
// been executed (e.g. it might return a DataSet). If the SelectCountMethod is set, we can definitely
// get the count. However, if it is not, we can only get the row count from the Selected data if it was not
// paged by the user's object, and it happened to be a DataView, DataSet, DataTable, ICollection, or object.
return ((SelectCountMethod.Length > 0) || (!EnablePaging));
}
}
/// <devdoc>
/// Indicates that the view can sort rows.
/// </devdoc>
public override bool CanSort {
get {
// We don't really know if the data source can sort until after the SelectMethod has
// been executed (e.g. it might return a DataSet), so we just assume it's true.
return true;
//return (SortParameterName.Length > 0);
}
}
/// <devdoc>
/// Indicates that the view can update rows.
/// </devdoc>
public override bool CanUpdate {
get {
return (UpdateMethod.Length != 0);
}
}
/// <devdoc>
/// Whether the delete command passes old values in the parameter collection.
/// </devdoc>
public ConflictOptions ConflictDetection {
get {
return _conflictDetection;
}
set {
if ((value < ConflictOptions.OverwriteChanges) || (value > ConflictOptions.CompareAllValues)) {
throw new ArgumentOutOfRangeException("value");
}
_conflictDetection = value;
OnDataSourceViewChanged(EventArgs.Empty);
}
}
/// <devdoc>
/// Whether null values passed into insert/update/delete operations
/// will be converted to System.DbNull.
/// </devdoc>
public bool ConvertNullToDBNull {
get {
return _convertNullToDBNull;
}
set {
_convertNullToDBNull = value;
}
}
/// <devdoc>
/// An optional type that is used for update, insert, and delete
/// scenarios where the object's methods take in an aggregate object
/// rather than one parameter for each property in the selected data.
/// </devdoc>
public string DataObjectTypeName {
get {
if (_dataObjectTypeName == null) {
return String.Empty;
}
return _dataObjectTypeName;
}
set {
if (DataObjectTypeName != value) {
_dataObjectTypeName = value;
OnDataSourceViewChanged(EventArgs.Empty);
}
}
}
/// <devdoc>
/// The method to execute when Delete() is called.
/// </devdoc>
public string DeleteMethod {
get {
if (_deleteMethod == null) {
return String.Empty;
}
return _deleteMethod;
}
set {
_deleteMethod = value;
}
}
/// <devdoc>
// Collection of parameters used when calling the DeleteMethod. These parameters are merged with the parameters provided by data-bound controls.
/// </devdoc>
public ParameterCollection DeleteParameters {
get {
if (_deleteParameters == null) {
_deleteParameters = new ParameterCollection();
}
return _deleteParameters;
}
}
/// <devdoc>
/// Indicates whether the Select method supports paging. If this is set to true, the
/// StartRowIndexParameterName and MaximumRowsParameterName properties must be set to the
/// names of the parameters of the Select method that accept the values for the starting
/// record to retrieve and the number of records to retrieve.
/// </devdoc>
public bool EnablePaging {
get {
return _enablePaging;
}
set {
if (EnablePaging != value) {
_enablePaging = value;
OnDataSourceViewChanged(EventArgs.Empty);
}
}
}
/// <devdoc>
/// Filter expression used when Select() is called. Filtering is only available when the SelectMethod returns a DataSet or a DataTable.
/// </devdoc>
public string FilterExpression {
get {
if (_filterExpression == null) {
return String.Empty;
}
return _filterExpression;
}
set {
if (FilterExpression != value) {
_filterExpression = value;
OnDataSourceViewChanged(EventArgs.Empty);
}
}
}
/// <devdoc>
/// Collection of parameters used in the FilterExpression property. Filtering is only available when the SelectMethod returns a DataSet or a DataTable.
/// </devdoc>
public ParameterCollection FilterParameters {
get {
if (_filterParameters == null) {
_filterParameters = new ParameterCollection();
_filterParameters.ParametersChanged += new EventHandler(SelectParametersChangedEventHandler);
if (_tracking) {
((IStateManager)_filterParameters).TrackViewState();
}
}
return _filterParameters;
}
}
/// <devdoc>
/// The method to execute when Insert() is called.
/// </devdoc>
public string InsertMethod {
get {
if (_insertMethod == null) {
return String.Empty;
}
return _insertMethod;
}
set {
_insertMethod = value;
}
}
/// <devdoc>
/// Collection of values used when calling the InsertMethod. These parameters are merged with the parameters provided by data-bound controls.
/// </devdoc>
public ParameterCollection InsertParameters {
get {
if (_insertParameters == null) {
_insertParameters = new ParameterCollection();
}
return _insertParameters;
}
}
/// <devdoc>
/// Returns whether this object is tracking view state.
/// </devdoc>
protected bool IsTrackingViewState {
get {
return _tracking;
}
}
/// <devdoc>
/// When EnablePaging is set to true, this property indicates the parameter of the Select
/// method that accepts the value for the number of records to retrieve.
/// </devdoc>
public string MaximumRowsParameterName {
get {
if (_maximumRowsParameterName == null) {
return "maximumRows";
}
return _maximumRowsParameterName;
}
set {
if (MaximumRowsParameterName != value) {
_maximumRowsParameterName = value;
OnDataSourceViewChanged(EventArgs.Empty);
}
}
}
/// <devdoc>
/// The format string applied to the names of the old values parameters
/// </devdoc>
[
DefaultValue("{0}"),
WebCategory("Data"),
WebSysDescription(SR.DataSource_OldValuesParameterFormatString),
]
public string OldValuesParameterFormatString {
get {
if (_oldValuesParameterFormatString == null) {
return "{0}";
}
return _oldValuesParameterFormatString;
}
set {
_oldValuesParameterFormatString = value;
OnDataSourceViewChanged(EventArgs.Empty);
}
}
/// <devdoc>
/// The method to execute when the total row count is needed. This method is only
/// used when EnablePaging is true and is optional.
/// </devdoc>
public string SelectCountMethod {
get {
if (_selectCountMethod == null) {
return String.Empty;
}
return _selectCountMethod;
}
set {
if (SelectCountMethod != value) {
_selectCountMethod = value;
OnDataSourceViewChanged(EventArgs.Empty);
}
}
}
/// <devdoc>
/// The method to execute when Select() is called.
/// </devdoc>
public string SelectMethod {
get {
if (_selectMethod == null) {
return String.Empty;
}
return _selectMethod;
}
set {
if (SelectMethod != value) {
_selectMethod = value;
OnDataSourceViewChanged(EventArgs.Empty);
}
}
}
/// <devdoc>
/// Collection of parameters used when calling the SelectMethod.
/// </devdoc>
public ParameterCollection SelectParameters {
get {
if (_selectParameters == null) {
_selectParameters = new ParameterCollection();
_selectParameters.ParametersChanged += new EventHandler(SelectParametersChangedEventHandler);
if (_tracking) {
((IStateManager)_selectParameters).TrackViewState();
}
}
return _selectParameters;
}
}
/// <devdoc>
/// The name of the parameter in the SelectMethod that specifies the
/// sort expression. This parameter's value will be automatically set
/// at runtime with the appropriate sort expression.
/// </devdoc>
public string SortParameterName {
get {
if (_sortParameterName == null) {
return String.Empty;
}
return _sortParameterName;
}
set {
if (SortParameterName != value) {
_sortParameterName = value;
OnDataSourceViewChanged(EventArgs.Empty);
}
}
}
/// <devdoc>
/// When EnablePaging is set to true, this property indicates the parameter of the Select
/// method that accepts the value for the number of first record to retrieve.
/// </devdoc>
public string StartRowIndexParameterName {
get {
if (_startRowIndexParameterName == null) {
return "startRowIndex";
}
return _startRowIndexParameterName;
}
set {
if (StartRowIndexParameterName != value) {
_startRowIndexParameterName = value;
OnDataSourceViewChanged(EventArgs.Empty);
}
}
}
/// <devdoc>
/// The type that contains the methods specified in this control.
/// </devdoc>
public string TypeName {
get {
if (_typeName == null) {
return String.Empty;
}
return _typeName;
}
set {
if (TypeName != value) {
_typeName = value;
OnDataSourceViewChanged(EventArgs.Empty);
}
}
}
/// <devdoc>
/// The method to execute when Update() is called.
/// </devdoc>
public string UpdateMethod {
get {
if (_updateMethod == null) {
return String.Empty;
}
return _updateMethod;
}
set {
_updateMethod = value;
}
}
/// <devdoc>
/// Collection of parameters and values used when calling the UpdateMethod. These parameters are merged with the parameters provided by data-bound controls.
/// </devdoc>
public ParameterCollection UpdateParameters {
get {
if (_updateParameters == null) {
_updateParameters = new ParameterCollection();
}
return _updateParameters;
}
}
/// <summary>
/// Indicates which <see cref='System.Globalization.CultureInfo'/> is used by ObjectDataSourceView
/// when converting string values to actual types of properties of data object.
/// </summary>
public ParsingCulture ParsingCulture {
get;
set;
}
/// <devdoc>
/// This event is raised after the Delete operation has completed.
/// Handle this event if you need to examine the return values of
/// the method call, or examine an exception that may have been thrown.
/// </devdoc>
public event ObjectDataSourceStatusEventHandler Deleted {
add {
Events.AddHandler(EventDeleted, value);
}
remove {
Events.RemoveHandler(EventDeleted, value);
}
}
/// <devdoc>
/// This event is raised before the Delete operation has been executed.
/// Handle this event if you need to validate the values of parameters or
/// change their values.
/// </devdoc>
public event ObjectDataSourceMethodEventHandler Deleting {
add {
Events.AddHandler(EventDeleting, value);
}
remove {
Events.RemoveHandler(EventDeleting, value);
}
}
public event ObjectDataSourceFilteringEventHandler Filtering {
add {
Events.AddHandler(EventFiltering, value);
}
remove {
Events.RemoveHandler(EventFiltering, value);
}
}
/// <devdoc>
/// This event is raised after the Insert operation has completed.
/// Handle this event if you need to examine the return values of
/// the method call, or examine an exception that may have been thrown.
/// </devdoc>
public event ObjectDataSourceStatusEventHandler Inserted {
add {
Events.AddHandler(EventInserted, value);
}
remove {
Events.RemoveHandler(EventInserted, value);
}
}
/// <devdoc>
/// This event is raised before the Insert operation has been executed.
/// Handle this event if you need to validate the values of parameters or
/// change their values.
/// </devdoc>
public event ObjectDataSourceMethodEventHandler Inserting {
add {
Events.AddHandler(EventInserting, value);
}
remove {
Events.RemoveHandler(EventInserting, value);
}
}
/// <devdoc>
/// This event is raised after the instance of the object has been created.
/// Handle this event if you need to set additional properties on the
/// object before any other methods are called. This event will not be
/// raised if a custom instance was provided in the ObjectCreating event.
/// </devdoc>
public event ObjectDataSourceObjectEventHandler ObjectCreated {
add {
Events.AddHandler(EventObjectCreated, value);
}
remove {
Events.RemoveHandler(EventObjectCreated, value);
}
}
/// <devdoc>
/// This event is raised before the instance of the object has been created.
/// Handle this event if you need to call a non-default constructor on the
/// object. Set the ObjectInstance property of the EventArgs with the
/// custom instance. If this is set, the ObjectCreated event will not be
/// raised.
/// </devdoc>
public event ObjectDataSourceObjectEventHandler ObjectCreating {
add {
Events.AddHandler(EventObjectCreating, value);
}
remove {
Events.RemoveHandler(EventObjectCreating, value);
}
}
/// <devdoc>
/// This event is raised before the instance of the object is disposed.
/// Handle this event if you need to retrieve properties on the
/// object before it is disposed. If the object implements the IDispoable
/// interface, then the Dispose() method will be called automatically.
/// Set the Cancel property of the event args to true if you do not want
/// IDisposable.Dispose() to be called automatically.
/// </devdoc>
public event ObjectDataSourceDisposingEventHandler ObjectDisposing {
add {
Events.AddHandler(EventObjectDisposing, value);
}
remove {
Events.RemoveHandler(EventObjectDisposing, value);
}
}
/// <devdoc>
/// This event is raised after the Select operation has completed.
/// Handle this event if you need to examine the return values of
/// the method call, or examine an exception that may have been thrown.
/// </devdoc>
public event ObjectDataSourceStatusEventHandler Selected {
add {
Events.AddHandler(EventSelected, value);
}
remove {
Events.RemoveHandler(EventSelected, value);
}
}
/// <devdoc>
/// This event is raised before the Select operation has been executed.
/// Handle this event if you need to validate the values of parameters or
/// change their values.
/// </devdoc>
public event ObjectDataSourceSelectingEventHandler Selecting {
add {
Events.AddHandler(EventSelecting, value);
}
remove {
Events.RemoveHandler(EventSelecting, value);
}
}
/// <devdoc>
/// This event is raised after the Update operation has completed.
/// Handle this event if you need to examine the return values of
/// the method call, or examine an exception that may have been thrown.
/// </devdoc>
public event ObjectDataSourceStatusEventHandler Updated {
add {
Events.AddHandler(EventUpdated, value);
}
remove {
Events.RemoveHandler(EventUpdated, value);
}
}
/// <devdoc>
/// This event is raised before the Update operation has been executed.
/// Handle this event if you need to validate the values of parameters or
/// change their values.
/// </devdoc>
public event ObjectDataSourceMethodEventHandler Updating {
add {
Events.AddHandler(EventUpdating, value);
}
remove {
Events.RemoveHandler(EventUpdating, value);
}
}
/// <devdoc>
/// Creates a data object and sets the properties specified by name/value
/// pairs in the dictionary.
/// </devdoc>
private object BuildDataObject(Type dataObjectType, IDictionary inputParameters) {
Debug.Assert(inputParameters != null, "Did not expect null parameter dictionary");
Debug.Assert(dataObjectType != null, "Did not expect null DataObjectType");
object dataObject = Activator.CreateInstance(dataObjectType);
Debug.Assert(dataObject != null, "We should never get back a null instance of the DataObject if the creation succeeded.");
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(dataObject);
foreach (DictionaryEntry de in inputParameters) {
//
string propName = (de.Key == null ? String.Empty : de.Key.ToString());
PropertyDescriptor pd = props.Find(propName, true);
if (pd == null) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_DataObjectPropertyNotFound, propName, _owner.ID));
}
if (pd.IsReadOnly) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_DataObjectPropertyReadOnly, propName, _owner.ID));
}
object value = BuildObjectValue(de.Value, pd.PropertyType, propName, ParsingCulture);
pd.SetValue(dataObject, value);
}
return dataObject;
}
/// <devdoc>
/// Builds a strongly typed value to be passed into a method either as a parameter
/// or as part of a data object. This will attempt to perform appropriate type
/// conversions based on the destination type and throw exceptions on fatal errors.
/// </devdoc>
private static object BuildObjectValue(object value, Type destinationType, string paramName, ParsingCulture parsingCulture) {
// Only consider converting the type if the value is non-null and the types don't match
if (value != null && (!destinationType.IsInstanceOfType(value))) {
Type innerDestinationType = destinationType;
bool isNullable = false;
if (destinationType.IsGenericType && (destinationType.GetGenericTypeDefinition() == typeof(Nullable<>))) {
innerDestinationType = destinationType.GetGenericArguments()[0];
isNullable = true;
}
else {
if (destinationType.IsByRef) {
innerDestinationType = destinationType.GetElementType();
}
}
// Try to convert from for example string to DateTime, so that
// afterwards we can convert DateTime to Nullable<DateTime>
// If the value is a string, we attempt to use a TypeConverter to convert it
value = ConvertType(value, innerDestinationType, paramName, parsingCulture);
// Special-case the value when the destination is Nullable<T>
if (isNullable) {
Type paramValueType = value.GetType();
if (innerDestinationType != paramValueType) {
// Throw if for example, we are trying to convert from int to Nullable<bool>
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_CannotConvertType, paramName, paramValueType.FullName, String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", destinationType.GetGenericArguments()[0].FullName)));
}
}
}
return value;
}
/// <devdoc>
/// Converts a type from a string representation to a strong type using
/// an appropriate TypeConverter. If the value is not a string, the value
/// is left unchanged.
/// </devdoc>
private static object ConvertType(object value, Type type, string paramName, ParsingCulture parsingCulture) {
string s = value as string;
if (s != null) {
// Get the type converter for the destination type
TypeConverter converter = TypeDescriptor.GetConverter(type);
Debug.Assert(converter != null);
if (converter != null) {
// Perform the conversion
try {
if (parsingCulture == ParsingCulture.Current) {
value = converter.ConvertFromString(null, CultureInfo.CurrentCulture, s);
}
else {
value = converter.ConvertFromInvariantString(s);
}
}
catch (NotSupportedException) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_CannotConvertType, paramName, typeof(string).FullName, type.FullName));
}
catch (FormatException) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_CannotConvertType, paramName, typeof(string).FullName, type.FullName));
}
}
}
return value;
}
/// <devdoc>
/// Creates an IEnumerable from the given data object and validates that the
/// DataSourceSelectArguments are valid for the conditions.
/// </devdoc>
private IEnumerable CreateEnumerableData(object dataObject, DataSourceSelectArguments arguments) {
if (FilterExpression.Length > 0) {
// Since this type is not valid for filtering, throw if there is a filter
throw new NotSupportedException(SR.GetString(SR.ObjectDataSourceView_FilterNotSupported, _owner.ID));
}
if (!String.IsNullOrEmpty(arguments.SortExpression)) {
throw new NotSupportedException(SR.GetString(SR.ObjectDataSourceView_SortNotSupportedOnIEnumerable, _owner.ID));
}
IEnumerable enumerable = dataObject as IEnumerable;
if (enumerable != null) {
// If object is an IEnumerable, return it as it is
if (!EnablePaging && arguments.RetrieveTotalRowCount && SelectCountMethod.Length == 0) {
ICollection collection = enumerable as ICollection;
if (collection != null) {
arguments.TotalRowCount = collection.Count;
}
}
return enumerable;
}
else {
// The result is neither a DataView, DataSet, DataTable, nor an IEnumerable, so we just wrap it in an IEnumerable
if (arguments.RetrieveTotalRowCount && SelectCountMethod.Length == 0) {
arguments.TotalRowCount = 1;
}
return new object[1] { dataObject };
}
}
/// <devdoc>
/// Creates a filtered DataView with optional filtering.
/// </devdoc>
private IEnumerable CreateFilteredDataView(DataTable dataTable, string sortExpression, string filterExpression) {
IOrderedDictionary parameterValues = FilterParameters.GetValues(_context, _owner);
if (filterExpression.Length > 0) {
ObjectDataSourceFilteringEventArgs filterArgs = new ObjectDataSourceFilteringEventArgs(parameterValues);
OnFiltering(filterArgs);
if (filterArgs.Cancel) {
return null;
}
}
return FilteredDataSetHelper.CreateFilteredDataView(dataTable, sortExpression, filterExpression, parameterValues);
}
public int Delete(IDictionary keys, IDictionary oldValues) {
return ExecuteDelete(keys, oldValues);
}
/// <devdoc>
/// </devdoc>
protected override int ExecuteDelete(IDictionary keys, IDictionary oldValues) {
if (!CanDelete) {
throw new NotSupportedException(SR.GetString(SR.ObjectDataSourceView_DeleteNotSupported, _owner.ID));
}
Type type = GetType(TypeName);
Debug.Assert(type != null, "Should not have a null type at this point");
// Try to get the DataObject type. If we do get the type then we will construct
// DataObjects representing the old values.
Type dataObjectType = TryGetDataObjectType();
ObjectDataSourceMethod method;
if (dataObjectType != null) {
// Build DataObject (Old)
IDictionary caseInsensitiveOldValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
MergeDictionaries(DeleteParameters, keys, caseInsensitiveOldValues);
if (ConflictDetection == ConflictOptions.CompareAllValues) {
if (oldValues == null) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_Pessimistic, SR.GetString(SR.DataSourceView_delete), _owner.ID, "oldValues"));
}
MergeDictionaries(DeleteParameters, oldValues, caseInsensitiveOldValues);
}
object oldDataObject = BuildDataObject(dataObjectType, caseInsensitiveOldValues);
// Resolve and invoke the method
method = GetResolvedMethodData(type, DeleteMethod, dataObjectType, oldDataObject, null, DataSourceOperation.Delete);
ObjectDataSourceMethodEventArgs eventArgs = new ObjectDataSourceMethodEventArgs(method.Parameters);
OnDeleting(eventArgs);
if (eventArgs.Cancel) {
return 0;
}
}
else {
// Build parameter list that contains old values
IOrderedDictionary caseInsensitiveAllValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
string oldValuesParameterFormatString = OldValuesParameterFormatString;
MergeDictionaries(DeleteParameters, DeleteParameters.GetValues(_context, _owner), caseInsensitiveAllValues);
MergeDictionaries(DeleteParameters, keys, caseInsensitiveAllValues, oldValuesParameterFormatString);
if (ConflictDetection == ConflictOptions.CompareAllValues) {
if (oldValues == null) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_Pessimistic, SR.GetString(SR.DataSourceView_delete), _owner.ID, "oldValues"));
}
MergeDictionaries(DeleteParameters, oldValues, caseInsensitiveAllValues, oldValuesParameterFormatString);
}
ObjectDataSourceMethodEventArgs eventArgs = new ObjectDataSourceMethodEventArgs(caseInsensitiveAllValues);
OnDeleting(eventArgs);
if (eventArgs.Cancel) {
return 0;
}
// Resolve and invoke the method
method = GetResolvedMethodData(type, DeleteMethod, caseInsensitiveAllValues, DataSourceOperation.Delete);
}
ObjectDataSourceResult result = InvokeMethod(method);
// Invalidate the cache and raise the change event to notify bound controls
if (_owner.Cache.Enabled) {
_owner.InvalidateCacheEntry();
}
OnDataSourceViewChanged(EventArgs.Empty);
return result.AffectedRows;
}
/// <devdoc>
/// </devdoc>
protected override int ExecuteInsert(IDictionary values) {
if (!CanInsert) {
throw new NotSupportedException(SR.GetString(SR.ObjectDataSourceView_InsertNotSupported, _owner.ID));
}
Type type = GetType(TypeName);
Debug.Assert(type != null, "Should not have a null type at this point");
// Try to get the DataObject type. If we do get the type then we will construct
// DataObjects representing the new values.
Type dataObjectType = TryGetDataObjectType();
ObjectDataSourceMethod method;
if (dataObjectType != null) {
// Build DataObject (New)
if (values == null || values.Count == 0) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_InsertRequiresValues, _owner.ID));
}
IDictionary caseInsensitiveNewValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
MergeDictionaries(InsertParameters, values, caseInsensitiveNewValues);
object newDataObject = BuildDataObject(dataObjectType, caseInsensitiveNewValues);
// Resolve and invoke the method
method = GetResolvedMethodData(type, InsertMethod, dataObjectType, null, newDataObject, DataSourceOperation.Insert);
ObjectDataSourceMethodEventArgs eventArgs = new ObjectDataSourceMethodEventArgs(method.Parameters);
OnInserting(eventArgs);
if (eventArgs.Cancel) {
return 0;
}
}
else {
// Build parameter list that contains new values
IOrderedDictionary caseInsensitiveAllValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
MergeDictionaries(InsertParameters, InsertParameters.GetValues(_context, _owner), caseInsensitiveAllValues);
MergeDictionaries(InsertParameters, values, caseInsensitiveAllValues);
ObjectDataSourceMethodEventArgs eventArgs = new ObjectDataSourceMethodEventArgs(caseInsensitiveAllValues);
OnInserting(eventArgs);
if (eventArgs.Cancel) {
return 0;
}
// Resolve and invoke the method
method = GetResolvedMethodData(type, InsertMethod, caseInsensitiveAllValues, DataSourceOperation.Insert);
}
ObjectDataSourceResult result = InvokeMethod(method);
// Invalidate the cache and raise the change event to notify bound controls
if (_owner.Cache.Enabled) {
_owner.InvalidateCacheEntry();
}
OnDataSourceViewChanged(EventArgs.Empty);
return result.AffectedRows;
}
/// <devdoc>
/// </devdoc>
protected internal override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments) {
if (SelectMethod.Length == 0) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_SelectNotSupported, _owner.ID));
}
if (CanSort) {
arguments.AddSupportedCapabilities(DataSourceCapabilities.Sort);
}
if (CanPage) {
arguments.AddSupportedCapabilities(DataSourceCapabilities.Page);
}
if (CanRetrieveTotalRowCount) {
arguments.AddSupportedCapabilities(DataSourceCapabilities.RetrieveTotalRowCount);
}
arguments.RaiseUnsupportedCapabilitiesError(this);
// Copy the parameters into a case insensitive dictionary
IOrderedDictionary mergedParameters = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
IDictionary selectParameters = SelectParameters.GetValues(_context, _owner);
foreach (DictionaryEntry de in selectParameters) {
mergedParameters[de.Key] = de.Value;
}
// If caching is enabled, load DataView, DataSet, DataTable, or IEnumerable from cache
bool cacheEnabled = _owner.Cache.Enabled;
if (cacheEnabled) {
object cachedData = _owner.LoadDataFromCache(arguments.StartRowIndex, arguments.MaximumRows);
if (cachedData != null) {
DataView dataView = cachedData as DataView;
if (dataView != null) {
if (arguments.RetrieveTotalRowCount && SelectCountMethod.Length == 0) {
arguments.TotalRowCount = dataView.Count;
}
if (FilterExpression.Length > 0) {
// Since this type is not valid for filtering, throw if there is a filter
throw new NotSupportedException(SR.GetString(SR.ObjectDataSourceView_FilterNotSupported, _owner.ID));
}
if (String.IsNullOrEmpty(arguments.SortExpression)) {
// If there is no sort expression, we can return the cached DataView
// If sorting is requested, we don't use the cached one and we go create a new one (and we'll
// most likely get exception, since caching+sorting+DataView=exception).
return dataView;
}
// Fall through as though the data wasn't in cache
}
else {
DataTable dataTable = FilteredDataSetHelper.GetDataTable(_owner, cachedData);
if (dataTable != null) {
// If we got back a DataTable from cache, return that
ProcessPagingData(arguments, mergedParameters);
return CreateFilteredDataView(dataTable, arguments.SortExpression, FilterExpression);
}
else {
// If we got back an IEnumerable from cache, return that
IEnumerable enumerableReturnValue = CreateEnumerableData(cachedData, arguments);
ProcessPagingData(arguments, mergedParameters);
return enumerableReturnValue;
}
}
}
}
// We have to raise the Selecting event early on so that we respect
// any changes the user has made to the DataSourceSelectArguments.
ObjectDataSourceSelectingEventArgs eventArgs = new ObjectDataSourceSelectingEventArgs(mergedParameters, arguments, false);
OnSelecting(eventArgs);
if (eventArgs.Cancel) {
return null;
}
// Create a copy of mergedParameters for queryRowCount that doesn't get all the Select, Sort, paging parameters
OrderedDictionary queryRowCountParameters = new OrderedDictionary(mergedParameters.Count);
foreach (DictionaryEntry entry in mergedParameters) {
queryRowCountParameters.Add(entry.Key, entry.Value);
}
// NOTE: These special parameters are only added here so that they don't show up
// for the SelectCount operation.
// Add the sort expression as a parameter if necessary
string sortParameterName = SortParameterName;
if (sortParameterName.Length > 0) {
mergedParameters[sortParameterName] = arguments.SortExpression;
// We reset the sort expression here so that we pretend as
// though we're not really sorting (since the developer is
// worrying about it instead of us).
arguments.SortExpression = String.Empty;
}
// Add the paging arguments as parameters if necessary
if (EnablePaging) {
string maximumRowsParameterName = MaximumRowsParameterName;
string startRowIndexParameterName = StartRowIndexParameterName;
if (String.IsNullOrEmpty(maximumRowsParameterName) ||
String.IsNullOrEmpty(startRowIndexParameterName)) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_MissingPagingSettings, _owner.ID));
}
// Create a new dictionary with the paging information and merge it in (so we get type conversions)
IDictionary pagingParameters = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
pagingParameters[maximumRowsParameterName] = arguments.MaximumRows;
pagingParameters[startRowIndexParameterName] = arguments.StartRowIndex;
MergeDictionaries(SelectParameters, pagingParameters, mergedParameters);
}
Type type = GetType(TypeName);
Debug.Assert(type != null, "Should not have a null type at this point");
object instance = null;
ObjectDataSourceResult result = null;
try {
// Resolve and invoke the method
ObjectDataSourceMethod method = GetResolvedMethodData(type, SelectMethod, mergedParameters, DataSourceOperation.Select);
result = InvokeMethod(method, false, ref instance);
// If the return value is null, there is no more processing to be done
if (result.ReturnValue == null) {
return null;
}
// Get the total row count if it is requested
if (arguments.RetrieveTotalRowCount && SelectCountMethod.Length > 0) {
int cachedTotalRowCount = -1;
if (cacheEnabled) {
cachedTotalRowCount = _owner.LoadTotalRowCountFromCache();
if (cachedTotalRowCount >= 0) {
arguments.TotalRowCount = cachedTotalRowCount;
}
}
if (cachedTotalRowCount < 0) {
cachedTotalRowCount = QueryTotalRowCount(queryRowCountParameters, arguments, true, ref instance);
arguments.TotalRowCount = cachedTotalRowCount;
if (cacheEnabled) {
_owner.SaveTotalRowCountToCache(cachedTotalRowCount);
}
}
}
}
finally {
if (instance != null) {
ReleaseInstance(instance);
}
}
// Process the return value
// Order of precedence: DataView, DataTable, DataSet, IEnumerable, <everything else>
{
// Check if the return value was a DataView
DataView dataView = result.ReturnValue as DataView;
if (dataView != null) {
if (arguments.RetrieveTotalRowCount && SelectCountMethod.Length == 0) {
arguments.TotalRowCount = dataView.Count;
}
if (FilterExpression.Length > 0) {
// Since this type is not valid for filtering, throw if there is a filter
throw new NotSupportedException(SR.GetString(SR.ObjectDataSourceView_FilterNotSupported, _owner.ID));
}
if (!String.IsNullOrEmpty(arguments.SortExpression)) {
if (cacheEnabled) {
// Since this type is not valid for caching, throw if caching is enabled
throw new NotSupportedException(SR.GetString(SR.ObjectDataSourceView_CacheNotSupportedOnSortedDataView, _owner.ID));
}
dataView.Sort = arguments.SortExpression;
}
// If caching is enabled, save data to cache
if (cacheEnabled) {
SaveDataAndRowCountToCache(arguments, result.ReturnValue);
}
return dataView;
}
else {
// Check if the return value was a DataSet or DataTable
DataTable dataTable = FilteredDataSetHelper.GetDataTable(_owner, result.ReturnValue);
if (dataTable != null) {
if (arguments.RetrieveTotalRowCount && SelectCountMethod.Length == 0) {
arguments.TotalRowCount = dataTable.Rows.Count;
}
// If caching is enabled, save data to cache
if (cacheEnabled) {
SaveDataAndRowCountToCache(arguments, result.ReturnValue);
}
// If we got a DataTable from the result, create a view with filtering and sorting
return CreateFilteredDataView(dataTable, arguments.SortExpression, FilterExpression);
}
else {
// CreateEnumerableData will get an appropriate IEnumerable from the data, and also
// validate that filtering and sorting are not used.
IEnumerable enumerableReturnValue = CreateEnumerableData(result.ReturnValue, arguments);
// If caching is enabled, save data to cache
if (cacheEnabled) {
if (enumerableReturnValue is IDataReader) {
// IDataReader is specifically not supported with caching since the contract
// is that they are forward-only (i.e. not resettable).
throw new NotSupportedException(SR.GetString(SR.ObjectDataSourceView_CacheNotSupportedOnIDataReader, _owner.ID));
}
SaveDataAndRowCountToCache(arguments, enumerableReturnValue);
}
return enumerableReturnValue;
}
}
}
}
/// <devdoc>
/// </devdoc>
protected override int ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues) {
if (!CanUpdate) {
throw new NotSupportedException(SR.GetString(SR.ObjectDataSourceView_UpdateNotSupported, _owner.ID));
}
Type type = GetType(TypeName);
Debug.Assert(type != null, "Should not have a null type at this point");
// Try to get the DataObject type. If we do get the type then we will construct
// DataObjects representing the new (and possibly old) values.
Type dataObjectType = TryGetDataObjectType();
ObjectDataSourceMethod method;
if (dataObjectType != null) {
if (ConflictDetection == ConflictOptions.CompareAllValues) {
// Build two DataObjects (Old + New)
if (oldValues == null) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_Pessimistic, SR.GetString(SR.DataSourceView_update), _owner.ID, "oldValues"));
}
IDictionary caseInsensitiveNewValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
IDictionary caseInsensitiveOldValues = null;
// 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.
MergeDictionaries(UpdateParameters, oldValues, caseInsensitiveNewValues);
MergeDictionaries(UpdateParameters, keys, caseInsensitiveNewValues);
MergeDictionaries(UpdateParameters, values, caseInsensitiveNewValues);
// For optimistic updates we require that there be old values, and then
// we move them into a case-insensitive dictionary for merging.
if (oldValues == null) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_Pessimistic, SR.GetString(SR.DataSourceView_update), _owner.ID, "oldValues"));
}
caseInsensitiveOldValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
MergeDictionaries(UpdateParameters, oldValues, caseInsensitiveOldValues);
MergeDictionaries(UpdateParameters, keys, caseInsensitiveOldValues);
object newDataObject = BuildDataObject(dataObjectType, caseInsensitiveNewValues);
object oldDataObject = BuildDataObject(dataObjectType, caseInsensitiveOldValues);
// Resolve and invoke the method
method = GetResolvedMethodData(type, UpdateMethod, dataObjectType, oldDataObject, newDataObject, DataSourceOperation.Update);
}
else {
// Build one DataObject (New)
IDictionary caseInsensitiveNewValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
// 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.
MergeDictionaries(UpdateParameters, oldValues, caseInsensitiveNewValues);
MergeDictionaries(UpdateParameters, keys, caseInsensitiveNewValues);
MergeDictionaries(UpdateParameters, values, caseInsensitiveNewValues);
object newDataObject = BuildDataObject(dataObjectType, caseInsensitiveNewValues);
// Resolve and invoke the method
method = GetResolvedMethodData(type, UpdateMethod, dataObjectType, null, newDataObject, DataSourceOperation.Update);
}
ObjectDataSourceMethodEventArgs eventArgs = new ObjectDataSourceMethodEventArgs(method.Parameters);
OnUpdating(eventArgs);
if (eventArgs.Cancel) {
return 0;
}
}
else {
// Build parameter list that contains old values (optionally), new values, and keys
IOrderedDictionary caseInsensitiveAllValues = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
string oldValuesParameterFormatString = OldValuesParameterFormatString;
// Add UpdateParameters, but exclude params that are keys (they will be added later)
IDictionary updateParams = UpdateParameters.GetValues(_context, _owner);
if (keys != null) {
foreach (DictionaryEntry de in keys) {
if (updateParams.Contains(de.Key)) {
updateParams.Remove(de.Key);
}
}
}
MergeDictionaries(UpdateParameters, updateParams, caseInsensitiveAllValues);
MergeDictionaries(UpdateParameters, values, caseInsensitiveAllValues);
if (ConflictDetection == ConflictOptions.CompareAllValues) {
MergeDictionaries(UpdateParameters, oldValues, caseInsensitiveAllValues, oldValuesParameterFormatString);
}
MergeDictionaries(UpdateParameters, keys, caseInsensitiveAllValues, oldValuesParameterFormatString);
ObjectDataSourceMethodEventArgs eventArgs = new ObjectDataSourceMethodEventArgs(caseInsensitiveAllValues);
OnUpdating(eventArgs);
if (eventArgs.Cancel) {
return 0;
}
// Resolve and invoke the method
method = GetResolvedMethodData(type, UpdateMethod, caseInsensitiveAllValues, DataSourceOperation.Update);
}
ObjectDataSourceResult result = InvokeMethod(method);
// Invalidate the cache and raise the change event to notify bound controls
if (_owner.Cache.Enabled) {
_owner.InvalidateCacheEntry();
}
OnDataSourceViewChanged(EventArgs.Empty);
return result.AffectedRows;
}
/// <devdoc>
/// Returns true if the two parameters represent the same type of operation.
/// </devdoc>
private static DataObjectMethodType GetMethodTypeFromOperation(DataSourceOperation operation) {
switch (operation) {
case DataSourceOperation.Delete:
return DataObjectMethodType.Delete;
case DataSourceOperation.Insert:
return DataObjectMethodType.Insert;
case DataSourceOperation.Select:
return DataObjectMethodType.Select;
case DataSourceOperation.Update:
return DataObjectMethodType.Update;
}
throw new ArgumentOutOfRangeException("operation");
}
/// <devdoc>
/// Extracts the values of all output (out and ref) parameters given a list of parameters and their respective values.
/// </devdoc>
private IDictionary GetOutputParameters(ParameterInfo[] parameters, object[] values) {
IDictionary outputParameters = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < parameters.Length; i++) {
ParameterInfo parameter = parameters[i];
if (parameter.ParameterType.IsByRef) {
outputParameters[parameter.Name] = values[i];
}
}
return outputParameters;
}
private ObjectDataSourceMethod GetResolvedMethodData(Type type, string methodName, Type dataObjectType, object oldDataObject, object newDataObject, DataSourceOperation operation) {
Debug.Assert(dataObjectType != null, "This overload of GetResolvedMethodData should only be called when using a DataObject");
Debug.Assert(oldDataObject != null || newDataObject != null, "Did not expect both oldDataObject and newDataObject to be null");
// Get a list of all the overloads of the requested method
MethodInfo[] methods = type.GetMethods(
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.FlattenHierarchy);
MethodInfo matchedMethod = null;
ParameterInfo[] matchedMethodParameters = null;
int requiredParameterCount;
if (oldDataObject == null) {
requiredParameterCount = 1;
}
else {
if (newDataObject == null) {
requiredParameterCount = 1;
}
else {
requiredParameterCount = 2;
}
}
foreach (MethodInfo mi in methods) {
if (String.Equals(methodName, mi.Name, StringComparison.OrdinalIgnoreCase)) {
if (mi.IsGenericMethodDefinition) {
// We do not support binding to generic methods, e.g. public void DoSomething<T>(T t)
continue;
}
ParameterInfo[] methodParameters = mi.GetParameters();
int methodParametersCount = methodParameters.Length;
if (methodParametersCount == requiredParameterCount) {
if (requiredParameterCount == 1 &&
methodParameters[0].ParameterType == dataObjectType) {
// Only one parameter, of proper type
// This is only valid for insert, delete, and non-optimistic update
matchedMethod = mi;
matchedMethodParameters = methodParameters;
break;
}
if (requiredParameterCount == 2 &&
methodParameters[0].ParameterType == dataObjectType &&
methodParameters[1].ParameterType == dataObjectType) {
// Two parameters of proper type in Update
// This is only valid for optimistic update
matchedMethod = mi;
matchedMethodParameters = methodParameters;
break;
}
}
}
}
if (matchedMethod == null) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_MethodNotFoundForDataObject, _owner.ID, methodName, dataObjectType.FullName));
}
Debug.Assert(matchedMethodParameters != null, "Method parameters should not be null if a method was found");
// Set up parameter array for method call
OrderedDictionary parameters = new OrderedDictionary(2, StringComparer.OrdinalIgnoreCase);
if (oldDataObject == null) {
parameters.Add(matchedMethodParameters[0].Name, newDataObject);
}
else {
if (newDataObject == null) {
parameters.Add(matchedMethodParameters[0].Name, oldDataObject);
}
else {
// We know that we matched on 2 objects for a optimistic update.
// Match the parameters based on the format string so we know which one is the old
// object and which is the new, then pass objects into the method in the correct order.
string param0Name = matchedMethodParameters[0].Name;
string param1Name = matchedMethodParameters[1].Name;
string formattedParamName = String.Format(CultureInfo.InvariantCulture, OldValuesParameterFormatString, param0Name);
if (String.Equals(param1Name, formattedParamName, StringComparison.OrdinalIgnoreCase)) {
parameters.Add(param0Name, newDataObject);
parameters.Add(param1Name, oldDataObject);
}
else {
formattedParamName = String.Format(CultureInfo.InvariantCulture, OldValuesParameterFormatString, param1Name);
if (String.Equals(param0Name, formattedParamName, StringComparison.OrdinalIgnoreCase)) {
parameters.Add(param0Name, oldDataObject);
parameters.Add(param1Name, newDataObject);
}
else {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_NoOldValuesParams, _owner.ID));
}
}
}
}
// The parameters collection is always readonly in this case since we
// do not want the user adding/removing the known objects.
return new ObjectDataSourceMethod(operation, type, matchedMethod, parameters.AsReadOnly());
}
/// <devdoc>
/// Resolves a method based on a type and the name of the method. Overload resolution is
/// performed primarily based on the names of parameters passed in the two dictionary
/// parameters. Conflict resolution is done based on a rating scale of confidence levels
/// (see comments in this method).
/// The return value is the MethodInfo that was found along with an array of parameter
/// values to be passed in for the invocation.
/// </devdoc>
private ObjectDataSourceMethod GetResolvedMethodData(Type type, string methodName, IDictionary allParameters, DataSourceOperation operation) {
Debug.Assert(allParameters != null, "The 'allParameters' dictionary should never be null");
// Since there is no method type for SelectCount, we special case it
bool isSelectCount = (operation == DataSourceOperation.SelectCount);
DataObjectMethodType methodType = DataObjectMethodType.Select;
if (!isSelectCount) {
methodType = GetMethodTypeFromOperation(operation);
}
// Get a list of all the overloads of the requested method
MethodInfo[] methods = type.GetMethods(
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.FlattenHierarchy);
MethodInfo matchedMethod = null;
ParameterInfo[] matchedMethodParameters = null;
// Indicates how confident we are that a method overload is a good match
// -1 - indicates no confidence - no appropriate methods have been found at all
// 0 - indicates low confidence - only parameter names match
// 1 - indicates medium confidence - parameter names match, method is DataObjectMethod
// 2 - indicates high confidence - parameter names match, method is DataObjectMethod, is default method
int highestConfidence = -1;
bool confidenceConflict = false; // Indicates that there is more than one method at the current highest confidence level
int allParameterCount = allParameters.Count;
foreach (MethodInfo mi in methods) {
if (String.Equals(methodName, mi.Name, StringComparison.OrdinalIgnoreCase)) {
if (mi.IsGenericMethodDefinition) {
// We do not support binding to generic methods, e.g. public void DoSomething<T>(T t)
continue;
}
ParameterInfo[] methodParameters = mi.GetParameters();
int methodParametersCount = methodParameters.Length;
// We are not using DataObject. There is either a Select operation, or it is an
// Update/Insert/Delete operation that does not use DataObjects.
// First check if the parameter counts match
if (methodParametersCount != allParameterCount) {
continue;
}
// Check if all the parameter names match
bool parameterMismatch = false;
foreach (ParameterInfo pi in methodParameters) {
if (!allParameters.Contains(pi.Name)) {
parameterMismatch = true;
break;
}
}
if (parameterMismatch) {
continue;
}
int confidence = 0; // See comment above regarding confidence
if (!isSelectCount) {
DataObjectMethodAttribute attr = Attribute.GetCustomAttribute(mi, typeof(DataObjectMethodAttribute), true) as DataObjectMethodAttribute;
if (attr != null) {
if (attr.MethodType == methodType) {
if (attr.IsDefault) {
// Method is decorated and is default
confidence = 2;
}
else {
// Method is decorated but not default
confidence = 1;
}
}
}
}
// If we found another method
if (confidence == highestConfidence) {
confidenceConflict = true;
}
else {
// If method looks like it's the best match so far, store it
if (confidence > highestConfidence) {
highestConfidence = confidence;
confidenceConflict = false;
matchedMethod = mi;
matchedMethodParameters = methodParameters;
}
}
}
}
if (confidenceConflict) {
// There was more than one method that looked like a good match, but none had
// a higher confidence level than the others, so we fail. See comment above
// regarding confidence levels.
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_MultipleOverloads, _owner.ID));
}
if (matchedMethod == null) {
if (allParameterCount == 0) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_MethodNotFoundNoParams, _owner.ID, methodName));
}
else {
string[] paramNames = new string[allParameterCount];
allParameters.Keys.CopyTo(paramNames, 0);
string paramString = String.Join(", ", paramNames);
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_MethodNotFoundWithParams, _owner.ID, methodName, paramString));
}
}
Debug.Assert(matchedMethodParameters != null, "Method parameters should not be null if a method was found");
OrderedDictionary parameters = null;
// Create the actual parameter list to be passed to the method
int methodParameterCount = matchedMethodParameters.Length;
if (methodParameterCount > 0) {
parameters = new OrderedDictionary(methodParameterCount, StringComparer.OrdinalIgnoreCase);
bool convertNullToDBNull = ConvertNullToDBNull;
for (int i = 0; i < matchedMethodParameters.Length; i++) {
ParameterInfo methodParameter = matchedMethodParameters[i];
string paramName = methodParameter.Name;
// Check if the required parameter exists in the input parameters
Debug.Assert(allParameters.Contains(paramName));
object parameterValue = allParameters[paramName];
if (convertNullToDBNull && (parameterValue == null)) {
parameterValue = DBNull.Value;
}
else {
parameterValue = BuildObjectValue(parameterValue, methodParameter.ParameterType, paramName, ParsingCulture);
}
parameters.Add(paramName, parameterValue);
}
}
return new ObjectDataSourceMethod(operation, type, matchedMethod, parameters);
}
private Type GetType(string typeName) {
if (TypeName.Length == 0) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_TypeNotSpecified, _owner.ID));
}
// Load the type using BuildManager (do not throw on fail, ignore case)
Type type = BuildManager.GetType(TypeName, false, true);
// If the type was not found, throw
if (type == null) {
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_TypeNotFound, _owner.ID));
}
return type;
}
public int Insert(IDictionary values) {
return ExecuteInsert(values);
}
private ObjectDataSourceResult InvokeMethod(ObjectDataSourceMethod method) {
object instance = null;
return InvokeMethod(method, true, ref instance);
}
/// <devdoc>
/// Invokes a given method with the specified parameters. This will also raise
/// the appropriate post event after execution.
/// </devdoc>
private ObjectDataSourceResult InvokeMethod(ObjectDataSourceMethod method, bool disposeInstance, ref object instance) {
// If the method is not static, we need to create an instance of the type
if (method.MethodInfo.IsStatic) {
if (instance != null) {
ReleaseInstance(instance);
}
instance = null;
}
else {
if (instance == null) {
// Raise event to allow page developer to supply a custom instance of the type
ObjectDataSourceEventArgs objectEventArgs = new ObjectDataSourceEventArgs(null);
OnObjectCreating(objectEventArgs);
// If page developer did not create a custom instance, we attempt to instantiate
// the object ourselves, and raise the ObjectCreated event
if (objectEventArgs.ObjectInstance == null) {
objectEventArgs.ObjectInstance = Activator.CreateInstance(method.Type);
// Raise ObjectCreated event with the same event args
OnObjectCreated(objectEventArgs);
}
instance = objectEventArgs.ObjectInstance;
}
}
// Call the method and if an exception is thrown, hold on to it to let the page developer attempt to handle it
object returnValue = null;
int affectedRows = -1;
bool eventFired = false;
object[] parameterValues = null;
if (method.Parameters != null && method.Parameters.Count > 0) {
parameterValues = new object[method.Parameters.Count];
for (int i = 0; i < method.Parameters.Count; i++) {
parameterValues[i] = method.Parameters[i];
}
}
try {
returnValue = method.MethodInfo.Invoke(instance, parameterValues);
}
catch (Exception ex) {
// Collect output parameters
IDictionary outputParameters = GetOutputParameters(method.MethodInfo.GetParameters(), parameterValues);
ObjectDataSourceStatusEventArgs statusEventArgs = new ObjectDataSourceStatusEventArgs(returnValue, outputParameters, ex);
eventFired = true;
switch (method.Operation) {
case DataSourceOperation.Delete:
OnDeleted(statusEventArgs);
break;
case DataSourceOperation.Insert:
OnInserted(statusEventArgs);
break;
case DataSourceOperation.Select:
OnSelected(statusEventArgs);
break;
case DataSourceOperation.SelectCount:
OnSelected(statusEventArgs);
break;
case DataSourceOperation.Update:
OnUpdated(statusEventArgs);
break;
}
affectedRows = statusEventArgs.AffectedRows;
if (!statusEventArgs.ExceptionHandled) {
throw;
}
}
finally {
// This block is to ensure that we always at least try to raise
// the Disposing event, even if the other event handlers threw
// exceptions.
try {
if (eventFired == false) {
// Collect output parameters
IDictionary outputParameters = GetOutputParameters(method.MethodInfo.GetParameters(), parameterValues);
ObjectDataSourceStatusEventArgs statusEventArgs = new ObjectDataSourceStatusEventArgs(returnValue, outputParameters);
switch (method.Operation) {
case DataSourceOperation.Delete:
OnDeleted(statusEventArgs);
break;
case DataSourceOperation.Insert:
OnInserted(statusEventArgs);
break;
case DataSourceOperation.Select:
OnSelected(statusEventArgs);
break;
case DataSourceOperation.SelectCount:
OnSelected(statusEventArgs);
break;
case DataSourceOperation.Update:
OnUpdated(statusEventArgs);
break;
}
affectedRows = statusEventArgs.AffectedRows;
}
}
finally {
// Raise ObjectDisposing event
if (instance != null && disposeInstance) {
ReleaseInstance(instance);
instance = null;
}
}
}
return new ObjectDataSourceResult(returnValue, affectedRows);
}
/// <devdoc>
/// Loads view state.
/// </devdoc>
protected virtual void LoadViewState(object savedState) {
if (savedState == null)
return;
Pair myState = (Pair)savedState;
if (myState.First != null)
((IStateManager)SelectParameters).LoadViewState(myState.First);
if (myState.Second != null)
((IStateManager)FilterParameters).LoadViewState(myState.Second);
}
private static void MergeDictionaries(ParameterCollection reference, IDictionary source, IDictionary destination) {
MergeDictionaries(reference, source, destination, null);
}
/// <devdoc>
/// Merges new values in the source dictionary with old values in the destination dictionary.
/// The reference parameter is used to assign types to values that got merged.
/// If a format string is specified, it will be applied to the parameter name when it is
/// added to the destination dictionary.
/// </devdoc>
private static void MergeDictionaries(ParameterCollection reference, IDictionary source, IDictionary destination, string parameterNameFormatString) {
Debug.Assert(destination != null);
Debug.Assert(reference != null);
if (source != null) {
foreach (DictionaryEntry de in source) {
object value = de.Value;
// If the reference collection contains this parameter, we will convert its type to match it
Parameter referenceParameter = null;
string parameterName = (string)de.Key;
if (parameterNameFormatString != null) {
parameterName = String.Format(CultureInfo.InvariantCulture, parameterNameFormatString, parameterName);
}
foreach (Parameter p in reference) {
if (String.Equals(p.Name, parameterName, StringComparison.OrdinalIgnoreCase)) {
referenceParameter = p;
break;
}
}
if (referenceParameter != null) {
value = referenceParameter.GetValue(value, true);
}
destination[parameterName] = value;
}
}
}
/// <devdoc>
/// Raises the Deleted event.
/// </devdoc>
protected virtual void OnDeleted(ObjectDataSourceStatusEventArgs e) {
ObjectDataSourceStatusEventHandler handler = Events[EventDeleted] as ObjectDataSourceStatusEventHandler;
if (handler != null) {
handler(this, e);
}
}
/// <devdoc>
/// Raises the Deleting event.
/// </devdoc>
protected virtual void OnDeleting(ObjectDataSourceMethodEventArgs e) {
ObjectDataSourceMethodEventHandler handler = Events[EventDeleting] as ObjectDataSourceMethodEventHandler;
if (handler != null) {
handler(this, e);
}
}
protected virtual void OnFiltering(ObjectDataSourceFilteringEventArgs e) {
ObjectDataSourceFilteringEventHandler handler = Events[EventFiltering] as ObjectDataSourceFilteringEventHandler;
if (handler != null) {
handler(this, e);
}
}
/// <devdoc>
/// Raises the Inserted event.
/// </devdoc>
protected virtual void OnInserted(ObjectDataSourceStatusEventArgs e) {
ObjectDataSourceStatusEventHandler handler = Events[EventInserted] as ObjectDataSourceStatusEventHandler;
if (handler != null) {
handler(this, e);
}
}
/// <devdoc>
/// Raises the Inserting event.
/// </devdoc>
protected virtual void OnInserting(ObjectDataSourceMethodEventArgs e) {
ObjectDataSourceMethodEventHandler handler = Events[EventInserting] as ObjectDataSourceMethodEventHandler;
if (handler != null) {
handler(this, e);
}
}
/// <devdoc>
/// Raises the ObjectCreated event.
/// </devdoc>
protected virtual void OnObjectCreated(ObjectDataSourceEventArgs e) {
ObjectDataSourceObjectEventHandler handler = Events[EventObjectCreated] as ObjectDataSourceObjectEventHandler;
if (handler != null) {
handler(this, e);
}
}
/// <devdoc>
/// Raises the ObjectCreating event.
/// </devdoc>
protected virtual void OnObjectCreating(ObjectDataSourceEventArgs e) {
ObjectDataSourceObjectEventHandler handler = Events[EventObjectCreating] as ObjectDataSourceObjectEventHandler;
if (handler != null) {
handler(this, e);
}
}
/// <devdoc>
/// Raises the ObjectDisposing event.
/// </devdoc>
protected virtual void OnObjectDisposing(ObjectDataSourceDisposingEventArgs e) {
ObjectDataSourceDisposingEventHandler handler = Events[EventObjectDisposing] as ObjectDataSourceDisposingEventHandler;
if (handler != null) {
handler(this, e);
}
}
/// <devdoc>
/// Raises the Selected event.
/// </devdoc>
protected virtual void OnSelected(ObjectDataSourceStatusEventArgs e) {
ObjectDataSourceStatusEventHandler handler = Events[EventSelected] as ObjectDataSourceStatusEventHandler;
if (handler != null) {
handler(this, e);
}
}
/// <devdoc>
/// Raises the Selecting event.
/// </devdoc>
protected virtual void OnSelecting(ObjectDataSourceSelectingEventArgs e) {
ObjectDataSourceSelectingEventHandler handler = Events[EventSelecting] as ObjectDataSourceSelectingEventHandler;
if (handler != null) {
handler(this, e);
}
}
/// <devdoc>
/// Raises the Updated event.
/// </devdoc>
protected virtual void OnUpdated(ObjectDataSourceStatusEventArgs e) {
ObjectDataSourceStatusEventHandler handler = Events[EventUpdated] as ObjectDataSourceStatusEventHandler;
if (handler != null) {
handler(this, e);
}
}
/// <devdoc>
/// Raises the Updating event.
/// </devdoc>
protected virtual void OnUpdating(ObjectDataSourceMethodEventArgs e) {
ObjectDataSourceMethodEventHandler handler = Events[EventUpdating] as ObjectDataSourceMethodEventHandler;
if (handler != null) {
handler(this, e);
}
}
/// <devdoc>
/// Populates the paging information in the DataSourceSelectArguments and
/// saves the data to cache if necessary.
/// </devdoc>
private void ProcessPagingData(DataSourceSelectArguments arguments, IOrderedDictionary parameters) {
if (arguments.RetrieveTotalRowCount) {
int cachedTotalRowCount = _owner.LoadTotalRowCountFromCache();
if (cachedTotalRowCount >= 0) {
arguments.TotalRowCount = cachedTotalRowCount;
}
else {
// query for row count and then save it in cache
object dummyInstance = null;
cachedTotalRowCount = QueryTotalRowCount(parameters, arguments, true, ref dummyInstance);
arguments.TotalRowCount = cachedTotalRowCount;
_owner.SaveTotalRowCountToCache(cachedTotalRowCount);
}
}
}
/// <devdoc>
/// Executes the SelectCountMethod to retrieve the total row count.
/// </devdoc>
private int QueryTotalRowCount(IOrderedDictionary mergedParameters, DataSourceSelectArguments arguments, bool disposeInstance, ref object instance) {
if (SelectCountMethod.Length > 0) {
ObjectDataSourceSelectingEventArgs eventArgs = new ObjectDataSourceSelectingEventArgs(mergedParameters, arguments, true);
OnSelecting(eventArgs);
if (eventArgs.Cancel) {
return -1;
}
Type type = GetType(TypeName);
Debug.Assert(type != null, "Should not have a null type at this point");
ObjectDataSourceMethod method = GetResolvedMethodData(type, SelectCountMethod, mergedParameters, DataSourceOperation.SelectCount);
ObjectDataSourceResult result = InvokeMethod(method, disposeInstance, ref instance);
if (result.ReturnValue != null && result.ReturnValue is int) {
return (int)result.ReturnValue;
}
}
// Unknown total row count
return -1;
}
private void ReleaseInstance(object instance) {
Debug.Assert(instance != null, "ReleaseInstance: Instance shouldn't be null");
ObjectDataSourceDisposingEventArgs disposingEventArgs = new ObjectDataSourceDisposingEventArgs(instance);
OnObjectDisposing(disposingEventArgs);
// Only call IDisposable.Dispose() if the page developer did not cancel
if (!disposingEventArgs.Cancel) {
// If the object implement IDisposable, call Dispose()
IDisposable disposableObject = instance as IDisposable;
if (disposableObject != null) {
disposableObject.Dispose();
}
}
}
private void SaveDataAndRowCountToCache(DataSourceSelectArguments arguments, object data) {
// Make sure total row count is saved first, since this is the parent key.
// If it's saved after the data, saving the total row count will invalidate
// the data cache.
if (arguments.RetrieveTotalRowCount) {
int totalRowCount = _owner.LoadTotalRowCountFromCache();
if (totalRowCount != arguments.TotalRowCount) {
_owner.SaveTotalRowCountToCache(arguments.TotalRowCount);
}
}
_owner.SaveDataToCache(arguments.StartRowIndex, arguments.MaximumRows, data);
}
/// <devdoc>
/// Saves view state.
/// </devdoc>
protected virtual object SaveViewState() {
Pair myState = new Pair();
myState.First = (_selectParameters != null) ? ((IStateManager)_selectParameters).SaveViewState() : null;
myState.Second = (_filterParameters != null) ? ((IStateManager)_filterParameters).SaveViewState() : null;
if ((myState.First == null) &&
(myState.Second == null)) {
return null;
}
return myState;
}
public IEnumerable Select(DataSourceSelectArguments arguments) {
return ExecuteSelect(arguments);
}
/// <devdoc>
/// Event handler for SelectParametersChanged event.
/// </devdoc>
private void SelectParametersChangedEventHandler(object o, EventArgs e) {
OnDataSourceViewChanged(EventArgs.Empty);
}
/// <devdoc>
/// Starts tracking view state.
/// </devdoc>
protected virtual void TrackViewState() {
_tracking = true;
if (_selectParameters != null) {
((IStateManager)_selectParameters).TrackViewState();
}
if (_filterParameters != null) {
((IStateManager)_filterParameters).TrackViewState();
}
}
private Type TryGetDataObjectType() {
// Load the data object type using BuildManager (do not throw on fail, ignore case)
// We only care about it for Update/Insert/Delete operations since it's not supported for Select
string dataObjectTypeName = DataObjectTypeName;
if (dataObjectTypeName.Length == 0) {
return null;
}
Type dataObjectType = BuildManager.GetType(dataObjectTypeName, false, true);
if (dataObjectType == null) {
// If the type was not found, throw
throw new InvalidOperationException(SR.GetString(SR.ObjectDataSourceView_DataObjectTypeNotFound, _owner.ID));
}
return dataObjectType;
}
public int Update(IDictionary keys, IDictionary values, IDictionary oldValues) {
return ExecuteUpdate(keys, values, oldValues);
}
#region IStateManager implementation
/// <internalonly/>
bool IStateManager.IsTrackingViewState {
get {
return IsTrackingViewState;
}
}
/// <internalonly/>
void IStateManager.LoadViewState(object savedState) {
LoadViewState(savedState);
}
/// <internalonly/>
object IStateManager.SaveViewState() {
return SaveViewState();
}
/// <internalonly/>
void IStateManager.TrackViewState() {
TrackViewState();
}
#endregion
private struct ObjectDataSourceMethod {
internal DataSourceOperation Operation;
internal Type Type;
internal OrderedDictionary Parameters;
internal MethodInfo MethodInfo;
internal ObjectDataSourceMethod(DataSourceOperation operation, Type type, MethodInfo methodInfo, OrderedDictionary parameters) {
Operation = operation;
Type = type;
Parameters = parameters;
MethodInfo = methodInfo;
}
}
private class ObjectDataSourceResult {
internal object ReturnValue;
internal int AffectedRows;
internal ObjectDataSourceResult(object returnValue, int affectedRows) {
ReturnValue = returnValue;
AffectedRows = affectedRows;
}
}
}
}
|