536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
1783 lines
78 KiB
C#
1783 lines
78 KiB
C#
//---------------------------------------------------------------------
|
|
// <copyright file="EntityDataSourceView.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//
|
|
// @owner Microsoft
|
|
// @backupOwner Microsoft
|
|
//---------------------------------------------------------------------
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using System.Data;
|
|
using System.Data.Common;
|
|
using System.Data.Metadata.Edm;
|
|
using System.Data.Objects;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Web.DynamicData;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace System.Web.UI.WebControls
|
|
{
|
|
public class EntityDataSourceView : DataSourceView, IStateManager
|
|
{
|
|
/// <summary>
|
|
/// Helper class used to manage EntityDataSourceWrapperCollection collections.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Entities in EntityDataSourceWrapperCollection are wrapped in order to allow for easy access to nested properties (and also
|
|
/// to be able to handle independendent associations - it was especially important in v1 where foreign keys were not supported).
|
|
/// This is handy when we need to get or set property values because we set values the same way regeradless if a
|
|
/// property is nested or not. Additional benefit is that the entities from EntityDataSourceWrapperCollection can be
|
|
/// easily stored and restored into/from the viewstate.
|
|
/// We cache entities in EntityDataSourceWrapperCollection in two cases:
|
|
/// - EntityDataSource.EnableFlattening flag is set to true. In this case the user requested entities to be flattened and wrapping allows
|
|
/// for an easy translation between tabular and hierarchical forms
|
|
/// - EntityDataSource.EnableUpdate flag is set meaning the user allows for updating entities. In this case we use EntityDataSourceWrapperCollection
|
|
/// to cache original values so that we can store them easily in the viewstate. The reason for storing values in the viewstate is that the
|
|
/// update operation can be mapped to a function. If we don't store the original values we would invoke the update function but some parameters
|
|
/// would not be. This would result in data corruption as we would override existing data in the database with default values (nulls) or an exception
|
|
/// if the target column in the database does not allow null values.
|
|
/// When the parameters would not be set? This happens when the dictionary containing oldValues passed to ExecuteUpdate method is missing some
|
|
/// values. This in turn can happen if EnableFlattening is set to false and the entity contains a complex property. The values of child properties of the
|
|
/// complex property are not displayed to the user and as a result the values for columns that were not shown are not passed back to the
|
|
/// ExecuteUpdate method. Another case is when the user uses a GridView bound to EntityDataSource. If the user hides some columns the values
|
|
/// for the hidden columns will not be passed to the ExecuteUpdate method.
|
|
/// Note that when entity flattening is enabled we will always store values returned from Select in the viewstate so we don't need to do anything special
|
|
/// for update. When entity flattening is disabled we always store the values if EnableUpdate is set to true. This is not really needed if the update operation
|
|
/// is not mapped to a function but if you are not inside System.Data.Entity.dll there is no way to tell whether the operation is mapped to a function so we need
|
|
/// to do it always.
|
|
/// </remarks>
|
|
private class WrapperCollectionManager
|
|
{
|
|
/// <summary>
|
|
/// Modes the manager can be working in.
|
|
/// </summary>
|
|
public enum ManagerMode
|
|
{
|
|
/// <summary>
|
|
/// None - entities are not stored. <see cref="_collection"/> is null.
|
|
/// Both <see cref="FlattenedEntityCollection"/> and <see cref="UpdateCache"/> return null.
|
|
/// </summary>
|
|
None,
|
|
|
|
/// <summary>
|
|
/// Entites are stored because <see cref="EntityDataSource.EnableFlattening"/> flag is set to true.
|
|
/// <see cref="FlattenedEntityCollection"/> returns non-null value whiel <see cref="UpdateCache"/> returns null.
|
|
/// </summary>
|
|
FlattenedEntities,
|
|
|
|
/// <summary>
|
|
/// Entites are stored because <see cref="EntityDataSource.EnableFlattening"/> flag is set to false but
|
|
/// <see cref="EntityDataSource.EnableUpdate"/> is set to true.
|
|
/// <see cref="FlattenedEntityCollection"/> returns non-null value whiel <see cref="UpdateCache"/> returns null.
|
|
/// </summary>
|
|
UpdateCache
|
|
}
|
|
|
|
private ManagerMode _collectionMode;
|
|
private EntityDataSourceWrapperCollection _collection;
|
|
|
|
/// <summary>
|
|
/// Mode the WrapperCollectionManager is working in.
|
|
/// </summary>
|
|
public ManagerMode Mode
|
|
{
|
|
get { return _collectionMode; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets collection containing flattened entities. Possibly null.
|
|
/// </summary>
|
|
public EntityDataSourceWrapperCollection FlattenedEntityCollection
|
|
{
|
|
get { return _collectionMode == ManagerMode.FlattenedEntities ? _collection : null; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets collection cached entities. Possibly null.
|
|
/// </summary>
|
|
public EntityDataSourceWrapperCollection UpdateCache
|
|
{
|
|
get { return _collectionMode == ManagerMode.UpdateCache ? _collection : null; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a collection for storing wrapped entities.
|
|
/// </summary>
|
|
/// <param name="context">ObjectContext</param>
|
|
/// <param name="entitySet">Entity set the stored belongs to.</param>
|
|
/// <param name="CSpaceFilteredEntityType">What entity type restrict the collection to. Null if all derived entity types are allowed.</param>
|
|
/// <param name="mode">What mode to store the entities. Never <see cref="ManagerMode.None"/>.</param>
|
|
public void CreateCollection(ObjectContext context, EntitySet entitySet, EntityType CSpaceFilteredEntityType, ManagerMode mode)
|
|
{
|
|
Debug.Assert(context != null);
|
|
Debug.Assert(entitySet != null);
|
|
Debug.Assert(mode != ManagerMode.None);
|
|
Debug.Assert(_collectionMode == ManagerMode.None || _collectionMode == mode, "Cannot reset a collection working in a different mode.");
|
|
|
|
_collectionMode = mode;
|
|
_collection = new EntityDataSourceWrapperCollection(context, entitySet, CSpaceFilteredEntityType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wraps the <paramref name="entity"/> and adds it to the collection.
|
|
/// </summary>
|
|
/// <param name="entity">Entity to add to the collection. Must not be null.</param>
|
|
public void AddWrappedEntity(object entity)
|
|
{
|
|
Debug.Assert(entity != null);
|
|
Debug.Assert(Mode != ManagerMode.None);
|
|
|
|
_collection.AddWrappedEntity(entity);
|
|
}
|
|
}
|
|
|
|
private EntityDataSource _owner;
|
|
private ObjectContext _ctx = null;
|
|
private ReadOnlyMetadataCollection<EdmMember> _keyMembers = null;
|
|
|
|
private static readonly object EventContextCreated = new object();
|
|
private static readonly object EventContextCreating = new object();
|
|
private static readonly object EventContextDisposing = new object();
|
|
private static readonly object EventDeleted = new object();
|
|
private static readonly object EventDeleting = new object();
|
|
private static readonly object EventInserted = new object();
|
|
private static readonly object EventInserting = 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 static readonly object EventException = new object();
|
|
private static readonly object EventQueryCreated = new object();
|
|
|
|
// values saved in ViewState
|
|
private bool _disableUpdates = false;
|
|
private bool _tracking = false;
|
|
|
|
private Dictionary<string, ArrayList> _originalProperties;
|
|
|
|
private WrapperCollectionManager _collectionManager = new WrapperCollectionManager();
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Initialize a new named instance of the EntityDataSourceView class, and
|
|
/// associates the specified EntityDataSource with it.
|
|
/// </summary>
|
|
public EntityDataSourceView(EntityDataSource owner, string viewName)
|
|
: base(owner, viewName)
|
|
{
|
|
_owner = owner;
|
|
}
|
|
#endregion Constructor
|
|
|
|
#region ExecuteSelect
|
|
|
|
private static readonly MethodInfo _executeSelectMethod = typeof(EntityDataSourceView).GetMethod("ExecuteSelectTyped", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
private static readonly MethodInfo _continueSelectMethod = typeof(EntityDataSourceView).GetMethod("ContinueSelectTyped", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
private static readonly Type[] queryBuilderCreatorArgTypes = { typeof(DataSourceSelectArguments), typeof(string), typeof(ObjectParameter[]),
|
|
typeof(string), typeof(ObjectParameter[]), typeof(string),
|
|
typeof(string), typeof(string), typeof(ObjectParameter[]),
|
|
typeof(OrderByBuilder), typeof(string) };
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
|
protected override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)
|
|
{
|
|
// reset collections
|
|
_collectionManager = new WrapperCollectionManager();
|
|
|
|
AddSupportedCapabilities(arguments);
|
|
arguments.RaiseUnsupportedCapabilitiesError(this);
|
|
|
|
ConstructContext();
|
|
|
|
var selectArgs = new EntityDataSourceSelectingEventArgs(_owner, arguments);
|
|
OnSelecting(selectArgs);
|
|
if (selectArgs.Cancel)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
_disableUpdates = _owner.ValidateUpdatableConditions();
|
|
|
|
if (_owner.ValidateWrappable() || CanUpdate)
|
|
{
|
|
_collectionManager.CreateCollection(
|
|
Context,
|
|
EntitySet,
|
|
CSpaceFilteredEntityType,
|
|
_owner.ValidateWrappable() ? WrapperCollectionManager.ManagerMode.FlattenedEntities : WrapperCollectionManager.ManagerMode.UpdateCache);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(_owner.Select))
|
|
{
|
|
return ExecuteSelectTyped<DbDataRecord>(arguments, EntityDataSourceRecordQueryBuilder.Create);
|
|
}
|
|
else if (!string.IsNullOrEmpty(_owner.CommandText))
|
|
{
|
|
return ExecuteSelectTyped<object>(arguments, EntityDataSourceObjectQueryBuilder<object>.Create);
|
|
}
|
|
else
|
|
{
|
|
Type builderType = typeof(EntityDataSourceObjectQueryBuilder<>).MakeGenericType(EntityClrType);
|
|
MethodInfo getCreatorMethod = builderType.GetMethod("GetCreator", BindingFlags.Static | BindingFlags.NonPublic);
|
|
object createDelegate = getCreatorMethod.Invoke(null, null);
|
|
try
|
|
{
|
|
return (IEnumerable)_executeSelectMethod.MakeGenericMethod(EntityClrType).Invoke(this, new object[] { arguments, createDelegate });
|
|
}
|
|
catch (TargetInvocationException e)
|
|
{
|
|
throw e.InnerException;
|
|
}
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
|
private IEnumerable ExecuteSelectTyped<T>(DataSourceSelectArguments arguments, EntityDataSourceQueryBuilder<T>.Creator qbConstructor)
|
|
{
|
|
string whereClause;
|
|
ObjectParameter[] whereParameters;
|
|
GenerateWhereClause(out whereClause, out whereParameters);
|
|
string entitySetQueryExpression = GenerateEntitySetQueryExpression();
|
|
|
|
var orderByBuilder = new OrderByBuilder(arguments.SortExpression, _collectionManager.FlattenedEntityCollection,
|
|
_owner.OrderBy, _owner.AutoGenerateOrderByClause, _owner.OrderByParameters,
|
|
CanPage, //There's no need to generate the default OrderBy clause if paging is disabled. Prevents an unnecessary sort at the server.
|
|
_owner);
|
|
|
|
EntityDataSourceQueryBuilder<T> queryBuilder =
|
|
qbConstructor(
|
|
arguments,
|
|
_owner.CommandText, _owner.GetCommandParameters(),
|
|
whereClause, whereParameters, entitySetQueryExpression,
|
|
_owner.Select, _owner.GroupBy, _owner.GetSelectParameters(),
|
|
orderByBuilder,
|
|
_owner.Include);
|
|
|
|
// We need to keep two copies, the unsorted and sorted because if the event does not
|
|
// modify the query we'll need to revert back to the unsorted one so we can re-apply the
|
|
// ESQL sort criteria as part of skip/take.
|
|
ObjectQuery<T> query = queryBuilder.BuildBasicQuery(Context, arguments.RetrieveTotalRowCount);
|
|
ObjectQuery<T> sortedQuery = queryBuilder.ApplyOrderBy(query);
|
|
|
|
var queryEventArgs = new QueryCreatedEventArgs(sortedQuery);
|
|
OnQueryCreated(queryEventArgs);
|
|
|
|
var queryReturned = queryEventArgs.Query;
|
|
bool wasQueryModified = (queryReturned != sortedQuery);
|
|
if (wasQueryModified)
|
|
{
|
|
// Check that we still have an object query
|
|
if (queryReturned as ObjectQuery == null)
|
|
{
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_QueryCreatedNotAnObjectQuery(queryReturned.GetType().FullName, typeof(T).Name));
|
|
}
|
|
|
|
// Check that new type is at least substitutable
|
|
var elementType = queryReturned.ElementType;
|
|
if (elementType != typeof(T))
|
|
{
|
|
if (!typeof(T).IsAssignableFrom(elementType))
|
|
{
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_QueryCreatedWrongType(elementType.Name, typeof(T).Name));
|
|
}
|
|
|
|
// Recreate the query builder for new type and then run it
|
|
var newQueryBuilderCreateMethod = typeof(EntityDataSourceObjectQueryBuilder<>).MakeGenericType(queryReturned.ElementType).GetMethod("Create", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic, null, queryBuilderCreatorArgTypes, null);
|
|
Debug.Assert(newQueryBuilderCreateMethod != null, "Unable to bind to EntityDataSourceObjectQueryBuilder<T>.Create (static, non-visible) method");
|
|
|
|
// If the query results were wrapped before, we need to reset the wrapper to use the new type
|
|
if (_collectionManager.Mode == WrapperCollectionManager.ManagerMode.FlattenedEntities)
|
|
{
|
|
Debug.Assert(_collectionManager.FlattenedEntityCollection != null);
|
|
|
|
MetadataWorkspace workspace = Context.MetadataWorkspace;
|
|
EntityType newOSpaceType = workspace.GetItem<EntityType>(elementType.FullName, DataSpace.OSpace);
|
|
EntityType newCSpaceType = (EntityType)workspace.GetEdmSpaceType((StructuralType)newOSpaceType);
|
|
|
|
_collectionManager.CreateCollection(Context, EntitySet, newCSpaceType, WrapperCollectionManager.ManagerMode.FlattenedEntities);
|
|
|
|
// Don't need to regenerate the where clause and parameters because they must be declaratively specified and if they reference
|
|
// properties that only exist in the subtype we would have already failed to generate the where clause previously and couldn't get this far
|
|
|
|
// Regeneate the OrderByBuilder so that we can apply the sort expression later based on the new wrapper properties
|
|
orderByBuilder = new OrderByBuilder(arguments.SortExpression, _collectionManager.FlattenedEntityCollection,
|
|
_owner.OrderBy, _owner.AutoGenerateOrderByClause, _owner.OrderByParameters,
|
|
CanPage, //There's no need to generate the default OrderBy clause if paging is disabled. Prevents an unnecessary sort at the server.
|
|
_owner);
|
|
}
|
|
|
|
object newQueryBuilder = newQueryBuilderCreateMethod.Invoke(null, new object[] {
|
|
arguments,
|
|
_owner.CommandText, _owner.GetCommandParameters(),
|
|
whereClause, whereParameters, entitySetQueryExpression,
|
|
_owner.Select, _owner.GroupBy, _owner.GetSelectParameters(),
|
|
orderByBuilder,
|
|
_owner.Include });
|
|
return (IEnumerable)_continueSelectMethod.MakeGenericMethod(elementType).Invoke(this, new object[] { arguments, newQueryBuilder, queryReturned, wasQueryModified });
|
|
}
|
|
|
|
// Type hasn't changed, we can dispatch as normal
|
|
query = queryReturned as ObjectQuery<T>;
|
|
}
|
|
|
|
return ContinueSelectTyped<T>(arguments, queryBuilder, query, wasQueryModified);
|
|
}
|
|
|
|
private IEnumerable ContinueSelectTyped<T>(DataSourceSelectArguments arguments, EntityDataSourceQueryBuilder<T> queryBuilder, ObjectQuery<T> queryT, bool wasQueryModified)
|
|
{
|
|
// Reset the MergeOption to AppendOnly
|
|
queryT.MergeOption = MergeOption.AppendOnly;
|
|
queryT = queryBuilder.CompleteBuild(queryT, Context, arguments.RetrieveTotalRowCount, wasQueryModified);
|
|
|
|
// SelectedEventArgs.TotalRowCount has three possible states:
|
|
// 1. The databound control requests it via arguments.RetrieveTotalRowCount
|
|
// This returns the total number of rows on all pages.
|
|
// arguments.TotalRowCount is only set if arguments.RetrieveTotalRowCount is true.
|
|
// 2. Paging is disabled via !CanPage.
|
|
// This returns the number of rows returned. On one page.
|
|
// 3. Else it returns negative one.
|
|
int totalRowCount = -1;
|
|
|
|
if (arguments.RetrieveTotalRowCount)
|
|
{
|
|
// The Selected event args gets the total number of rows on all pages.
|
|
totalRowCount = queryBuilder.TotalCount;
|
|
// The databound control requests totalRowCount. We return the total rows on all pages.
|
|
arguments.TotalRowCount = totalRowCount;
|
|
}
|
|
|
|
if (!_disableUpdates)
|
|
{
|
|
Debug.Assert(null != EntitySet, "Can't be updatable with a null EntitySet");
|
|
EntityDataSourceUtil.
|
|
CheckNonPolymorphicTypeUsage(EntitySet.ElementType,
|
|
Context.MetadataWorkspace.GetItemCollection(DataSpace.CSpace),
|
|
_owner.EntityTypeFilter);
|
|
}
|
|
|
|
IEnumerable entities = null;
|
|
try
|
|
{
|
|
entities = queryBuilder.Execute(queryT);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
entities = null;
|
|
var selectedArgs = new EntityDataSourceSelectedEventArgs(e);
|
|
OnSelected(selectedArgs);
|
|
OnException(new DynamicValidatorEventArgs(e, DynamicDataSourceOperation.Select));
|
|
if (!selectedArgs.ExceptionHandled)
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// OnSelected outside "try" to prevent double-calling OnSelected if the first OnSelected throws
|
|
if (null != entities)
|
|
{
|
|
if (!CanPage)
|
|
{
|
|
// Paging is disabled, totalRowCount gets the number of rows returned from this query.
|
|
totalRowCount = ((IList)entities).Count;
|
|
}
|
|
OnSelected(new EntityDataSourceSelectedEventArgs(Context, entities, totalRowCount, arguments));
|
|
}
|
|
|
|
var checkEntitySet = wasQueryModified && !String.IsNullOrEmpty(_owner.EntitySetName) && (CanDelete || CanUpdate);
|
|
|
|
if (_collectionManager.Mode == WrapperCollectionManager.ManagerMode.None && !checkEntitySet)
|
|
{
|
|
Debug.Assert(
|
|
_collectionManager.FlattenedEntityCollection == null && _collectionManager.UpdateCache == null,
|
|
"ManagerMode is None so both collections should be null.");
|
|
|
|
return entities;
|
|
}
|
|
|
|
foreach (object element in entities)
|
|
{
|
|
var elementEntitySet = Context.ObjectStateManager.GetObjectStateEntry(element).EntitySet;
|
|
if (elementEntitySet != EntitySet)
|
|
{
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_EntitySetMismatchWithQueryResults(elementEntitySet, EntitySet));
|
|
}
|
|
if (_collectionManager.Mode != WrapperCollectionManager.ManagerMode.None)
|
|
{
|
|
_collectionManager.AddWrappedEntity(element);
|
|
}
|
|
}
|
|
|
|
// If EnableFlattening flag is true return flattened entities. Otherwise if we ended up here
|
|
// because we needed to cache values in the viewstate for further updates we return non-flattened entities.
|
|
// Same happens if neither of the above is true and we ended up here because we needed to verify that
|
|
// all entities are from the same entity set.
|
|
return _collectionManager.Mode == WrapperCollectionManager.ManagerMode.FlattenedEntities ?
|
|
_collectionManager.FlattenedEntityCollection :
|
|
entities;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restrict the query to the type specified by EntityTypeFilter.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private string GenerateEntitySetQueryExpression()
|
|
{
|
|
if (null == EntitySet)
|
|
{
|
|
return String.Empty;
|
|
}
|
|
|
|
string entitySetIdentifier = EntityDataSourceUtil.CreateEntitySqlSetIdentifier(EntitySet);
|
|
|
|
if (String.IsNullOrEmpty(_owner.EntityTypeFilter))
|
|
{
|
|
return entitySetIdentifier;
|
|
}
|
|
|
|
Debug.Assert(EntityOSpaceType != null, "EntitySet is not null, EntityOSpaceType should also be defined.");
|
|
|
|
// oftype ([Northwind].[Products], only [Northwind].[ActiveProducts])
|
|
StringBuilder queryExpressionBuilder = new StringBuilder();
|
|
queryExpressionBuilder.Append("oftype (");
|
|
queryExpressionBuilder.Append(entitySetIdentifier);
|
|
queryExpressionBuilder.Append(", only ");
|
|
queryExpressionBuilder.Append(EntityDataSourceUtil.CreateEntitySqlTypeIdentifier(EntityOSpaceType));
|
|
queryExpressionBuilder.Append(")");
|
|
|
|
return queryExpressionBuilder.ToString();
|
|
}
|
|
|
|
#endregion ExecuteSelect
|
|
|
|
#region ExecuteUpdate
|
|
|
|
protected override int ExecuteUpdate(IDictionary keys, IDictionary values, IDictionary oldValues)
|
|
{
|
|
if (!CanUpdate)
|
|
{
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_UpdateDisabledForThisControl);
|
|
}
|
|
|
|
ConstructContext();
|
|
EntityDataSourceChangingEventArgs changingArgs;
|
|
|
|
Dictionary<string, object> originalEntityValues = new Dictionary<string, object>();
|
|
EntityDataSourceWrapperCollection wrapperCollection =
|
|
new EntityDataSourceWrapperCollection(Context, EntitySet, CSpaceFilteredEntityType);
|
|
EntityDataSourceWrapper modifiedEntityWrapper;
|
|
try
|
|
{
|
|
object entity = EntityDataSourceUtil.InitializeType(this.EntityClrType);
|
|
Context.AddObject(_owner.FQEntitySetName, entity);
|
|
modifiedEntityWrapper = new EntityDataSourceWrapper(wrapperCollection, entity);
|
|
|
|
// Get the values in the following order
|
|
// 1. Key values from the page
|
|
// 2. Values from view state
|
|
// 3. Values from oldValues
|
|
ConvertProperties(keys, modifiedEntityWrapper.GetProperties(), /* ParameterCollection */null, originalEntityValues);
|
|
GetValuesFromViewState(originalEntityValues);
|
|
ConvertProperties(oldValues, modifiedEntityWrapper.GetProperties(), /* ParameterCollection */null, originalEntityValues);
|
|
|
|
// Validate that we have values for key properties
|
|
EntityDataSourceUtil.ValidateKeyPropertyValuesExist(modifiedEntityWrapper, originalEntityValues);
|
|
|
|
// Populate the entity with values
|
|
EntityDataSourceUtil.SetAllPropertiesWithVerification(modifiedEntityWrapper, originalEntityValues, /*overwrite*/true);
|
|
}
|
|
catch (EntityDataSourceValidationException e)
|
|
{
|
|
changingArgs = new EntityDataSourceChangingEventArgs(e);
|
|
OnUpdating(changingArgs);
|
|
OnException(new DynamicValidatorEventArgs(e, DynamicDataSourceOperation.Update));
|
|
if (!changingArgs.ExceptionHandled)
|
|
{
|
|
throw;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//Modify the properties with the new values
|
|
try
|
|
{
|
|
Context.AcceptAllChanges(); //Puts modifiedEntityWrapper into unchanged state.
|
|
UpdateEntity(originalEntityValues, values, modifiedEntityWrapper, _owner.UpdateParameters);
|
|
}
|
|
catch (EntityDataSourceValidationException e)
|
|
{
|
|
changingArgs = new EntityDataSourceChangingEventArgs(e);
|
|
OnUpdating(changingArgs);
|
|
OnException(new DynamicValidatorEventArgs(e, DynamicDataSourceOperation.Update));
|
|
if (!changingArgs.ExceptionHandled)
|
|
{
|
|
throw;
|
|
}
|
|
return -1;
|
|
}
|
|
try
|
|
{
|
|
// Call DetectChanges to ensure the changes to the entity are reflected in the state manager
|
|
Context.DetectChanges();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
changingArgs = new EntityDataSourceChangingEventArgs(e);
|
|
OnUpdating(changingArgs);
|
|
OnException(new DynamicValidatorEventArgs(e, DynamicDataSourceOperation.Update));
|
|
if (!changingArgs.ExceptionHandled)
|
|
{
|
|
throw;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Call DetectChanges to ensure the changes to the entity are reflected in the state manager
|
|
Context.DetectChanges();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
changingArgs = new EntityDataSourceChangingEventArgs(e);
|
|
OnUpdating(changingArgs);
|
|
OnException(new DynamicValidatorEventArgs(e, DynamicDataSourceOperation.Update));
|
|
if (!changingArgs.ExceptionHandled)
|
|
{
|
|
throw;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
changingArgs = new EntityDataSourceChangingEventArgs(Context, modifiedEntityWrapper.WrappedEntity);
|
|
OnUpdating(changingArgs);
|
|
if (changingArgs.Cancel)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
try
|
|
{
|
|
Context.SaveChanges();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// Catches SaveChanges exceptions.
|
|
EntityDataSourceChangedEventArgs changedArgs = new EntityDataSourceChangedEventArgs(e);
|
|
OnUpdated(changedArgs);
|
|
OnException(new DynamicValidatorEventArgs(e, DynamicDataSourceOperation.Update));
|
|
if (!changedArgs.ExceptionHandled)
|
|
{
|
|
throw;
|
|
}
|
|
return -1;
|
|
}
|
|
OnUpdated(new EntityDataSourceChangedEventArgs(Context, modifiedEntityWrapper.WrappedEntity));
|
|
return 1;
|
|
|
|
}
|
|
|
|
#endregion ExecuteUpdate
|
|
|
|
#region ExecuteDelete
|
|
protected override int ExecuteDelete(IDictionary keys, IDictionary oldValues)
|
|
{
|
|
if (!CanDelete)
|
|
{
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_DeleteDisabledForThiscontrol);
|
|
}
|
|
|
|
ConstructContext();
|
|
|
|
EntityDataSourceWrapperCollection wrapperCollection = new EntityDataSourceWrapperCollection(Context, EntitySet, CSpaceFilteredEntityType);
|
|
EntityDataSourceWrapper entityWrapper;
|
|
object entity;
|
|
EntityDataSourceChangingEventArgs changingArgs;
|
|
try
|
|
{
|
|
entity = EntityDataSourceUtil.InitializeType(this.EntityClrType);
|
|
Context.AddObject(_owner.FQEntitySetName, entity); // Add/AcceptAllChanges because wrappers must contain attached entities
|
|
entityWrapper = new EntityDataSourceWrapper(wrapperCollection, entity);
|
|
|
|
// Get the values in the following order
|
|
// 1. Key values from the page
|
|
// 2. Values from view state
|
|
// 3. Values from oldValues
|
|
Dictionary<string, object> entityValues = new Dictionary<string, object>();
|
|
ConvertProperties(keys, entityWrapper.GetProperties(), _owner.DeleteParameters, entityValues);
|
|
GetValuesFromViewState(entityValues);
|
|
ConvertProperties(oldValues, entityWrapper.GetProperties(), _owner.DeleteParameters, entityValues);
|
|
|
|
// Validate that we have values for key properties
|
|
EntityDataSourceUtil.ValidateKeyPropertyValuesExist(entityWrapper, entityValues);
|
|
|
|
// Populate the entity with values
|
|
EntityDataSourceUtil.SetAllPropertiesWithVerification(entityWrapper, entityValues, /*overwrite*/true);
|
|
|
|
Context.AcceptAllChanges(); //Force the entity just added into unchanged state. Wrapped entities must be tracked.
|
|
}
|
|
catch (EntityDataSourceValidationException e)
|
|
{
|
|
changingArgs = new EntityDataSourceChangingEventArgs(e);
|
|
OnDeleting(changingArgs);
|
|
OnException(new DynamicValidatorEventArgs(e, DynamicDataSourceOperation.Delete));
|
|
if (!changingArgs.ExceptionHandled)
|
|
{
|
|
throw;
|
|
}
|
|
return -1;
|
|
}
|
|
changingArgs = new EntityDataSourceChangingEventArgs(Context, entityWrapper.WrappedEntity);
|
|
OnDeleting(changingArgs); //Outside "try" to prevent from begin called twice.
|
|
if (changingArgs.Cancel)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
try
|
|
{
|
|
Context.DeleteObject(entityWrapper.WrappedEntity);
|
|
Context.SaveChanges();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// Catches errors on the context.
|
|
EntityDataSourceChangedEventArgs changedArgs = new EntityDataSourceChangedEventArgs(e);
|
|
OnDeleted(changedArgs);
|
|
OnException(new DynamicValidatorEventArgs(e, DynamicDataSourceOperation.Delete));
|
|
if (!changedArgs.ExceptionHandled)
|
|
{
|
|
throw;
|
|
}
|
|
return -1;
|
|
}
|
|
OnDeleted(new EntityDataSourceChangedEventArgs(Context, entity)); //Outside "try" to prevent being called twice.
|
|
return 1;
|
|
}
|
|
#endregion ExecuteDelete
|
|
|
|
#region ExecuteInsert
|
|
|
|
protected override int ExecuteInsert(IDictionary values)
|
|
{
|
|
if (!CanInsert)
|
|
{
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_InsertDisabledForThisControl);
|
|
}
|
|
|
|
ConstructContext();
|
|
|
|
EntityDataSourceChangingEventArgs changingArgs;
|
|
EntityDataSourceWrapperCollection wrapperCollection = new EntityDataSourceWrapperCollection(Context, EntitySet, CSpaceFilteredEntityType);
|
|
EntityDataSourceWrapper entityWrapper;
|
|
try
|
|
{
|
|
object entity = EntityDataSourceUtil.InitializeType(this.EntityClrType);
|
|
Context.AddObject(_owner.FQEntitySetName, entity);
|
|
entityWrapper = new EntityDataSourceWrapper(wrapperCollection, entity);
|
|
CreateEntityForInsert(entityWrapper, values, _owner.InsertParameters);
|
|
}
|
|
catch (EntityDataSourceValidationException e)
|
|
{
|
|
changingArgs = new EntityDataSourceChangingEventArgs(e);
|
|
OnInserting(changingArgs);
|
|
OnException(new DynamicValidatorEventArgs(e, DynamicDataSourceOperation.Insert));
|
|
if (!changingArgs.ExceptionHandled)
|
|
{
|
|
throw;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
changingArgs = new EntityDataSourceChangingEventArgs(Context, entityWrapper.WrappedEntity);
|
|
OnInserting(changingArgs); //Could return an entity to insert.
|
|
if (changingArgs.Cancel)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (!Object.ReferenceEquals(entityWrapper.WrappedEntity, changingArgs.Entity))
|
|
{
|
|
Context.Detach(entityWrapper.WrappedEntity);
|
|
Context.AddObject(_owner.EntitySetName, changingArgs.Entity);
|
|
entityWrapper = new EntityDataSourceWrapper(wrapperCollection, changingArgs.Entity);
|
|
}
|
|
|
|
try
|
|
{
|
|
Context.SaveChanges();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
EntityDataSourceChangedEventArgs changedArgs = new EntityDataSourceChangedEventArgs(e);
|
|
OnInserted(changedArgs);
|
|
OnException(new DynamicValidatorEventArgs(e, DynamicDataSourceOperation.Insert));
|
|
if (!changedArgs.ExceptionHandled)
|
|
{
|
|
throw;
|
|
}
|
|
return -1;
|
|
}
|
|
OnInserted(new EntityDataSourceChangedEventArgs(Context, entityWrapper.WrappedEntity));
|
|
return 1;
|
|
}
|
|
|
|
#endregion ExecuteInsert
|
|
|
|
#region Public Methods
|
|
public DataTable GetViewSchema()
|
|
{
|
|
EntityDataSourceViewSchema propTable;
|
|
if (_owner.ValidateWrappable())
|
|
{
|
|
ConstructContext();
|
|
EntityDataSourceWrapperCollection wrappers = new EntityDataSourceWrapperCollection(Context, EntitySet, CSpaceFilteredEntityType);
|
|
propTable = new EntityDataSourceViewSchema(wrappers);
|
|
}
|
|
else
|
|
{
|
|
string where = _owner.Where;
|
|
_owner.Where = "0=1";
|
|
try
|
|
{
|
|
DataSourceSelectArguments args = new DataSourceSelectArguments();
|
|
args.RetrieveTotalRowCount = false;
|
|
IEnumerable results = ExecuteSelect(args);
|
|
ITypedList typedList = results as ITypedList;
|
|
if (null != typedList)
|
|
{
|
|
propTable = new EntityDataSourceViewSchema(typedList);
|
|
}
|
|
else if (_owner.HasIdentity())
|
|
{
|
|
// If the results have an identity/primary keys, gather them from restricted type or the element type from the set the control is querying
|
|
// Make sure to use keys from the ObjectSpace type in case there were name mappings
|
|
EntityType entityType = Context.MetadataWorkspace.GetObjectSpaceType(CSpaceFilteredEntityType ?? EntitySet.ElementType) as EntityType;
|
|
propTable = new EntityDataSourceViewSchema(results, entityType.KeyMembers.Select(x => x.Name).ToArray());
|
|
}
|
|
else
|
|
{
|
|
propTable = new EntityDataSourceViewSchema(results);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_owner.Where = where;
|
|
}
|
|
}
|
|
return propTable;
|
|
}
|
|
#endregion Public Methods
|
|
|
|
#region Private Methods
|
|
|
|
#region ExecuteSelect Support
|
|
|
|
private void GenerateWhereClause(out string whereClause, out ObjectParameter[] whereParameters)
|
|
{
|
|
if (!_owner.AutoGenerateWhereClause)
|
|
{
|
|
whereClause = _owner.Where;
|
|
whereParameters = _owner.GetWhereParameters();
|
|
return;
|
|
}
|
|
|
|
//This is the automatically generated Where clause.
|
|
IOrderedDictionary paramValues = _owner.WhereParameters.GetValues(_owner.HttpContext, _owner);
|
|
// Under some conditions, the paramValues has a null entry.
|
|
StringBuilder whereClauseBuilder = new StringBuilder();
|
|
List<ObjectParameter> whereParameterList = new List<ObjectParameter>();
|
|
bool first = true;
|
|
|
|
int idx = 0;
|
|
foreach (DictionaryEntry de in paramValues)
|
|
{
|
|
string propertyName = (string)(de.Key);
|
|
if (0 < propertyName.Length && null != de.Value)
|
|
{
|
|
if (!String.IsNullOrEmpty(_owner.EntitySetName) && !EntityDataSourceUtil.PropertyIsOnEntity(propertyName, _collectionManager.FlattenedEntityCollection, EntitySet, null))
|
|
{
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_PropertyDoesNotExistOnEntity(propertyName, EntityClrType.FullName));
|
|
}
|
|
|
|
if (first)
|
|
{
|
|
first = false;
|
|
}
|
|
else
|
|
{
|
|
whereClauseBuilder.Append(" AND ");
|
|
}
|
|
|
|
string namedParameterName = "NamedParameter" + idx.ToString(CultureInfo.InvariantCulture);
|
|
|
|
whereClauseBuilder.Append(EntityDataSourceUtil.GetEntitySqlValueForColumnName(propertyName, _collectionManager.FlattenedEntityCollection));
|
|
whereClauseBuilder.Append("=@");
|
|
whereClauseBuilder.Append(namedParameterName);
|
|
|
|
whereParameterList.Add(new ObjectParameter(namedParameterName, de.Value));
|
|
idx++;
|
|
}
|
|
}
|
|
|
|
whereParameters = whereParameterList.ToArray();
|
|
whereClause = whereClauseBuilder.ToString();
|
|
}
|
|
|
|
#endregion ExecuteSelect Support
|
|
|
|
#region Entity Creation Support for Update/Insert/Delete
|
|
|
|
private object ConvertProperty(object origPropertyValue, PropertyDescriptor propertyDescriptor, WebControlParameterProxy referenceParameter)
|
|
{
|
|
if (null == propertyDescriptor)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
object propValue = origPropertyValue;
|
|
propValue = Parameter_GetValue(propValue, referenceParameter, true);
|
|
propValue = EntityDataSourceUtil.ConvertType(propValue, propertyDescriptor.PropertyType, propertyDescriptor.Name);
|
|
return propValue;
|
|
}
|
|
|
|
private void ConvertWCProperty(IDictionary values,
|
|
Dictionary<string, object> convertedValues,
|
|
List<string> visitedProperties,
|
|
PropertyDescriptor pd,
|
|
ParameterCollection referenceParameters,
|
|
ref Dictionary<string, Exception> exceptions)
|
|
{
|
|
Debug.Assert(pd != null, "PropertyDescriptor is null");
|
|
string propertyName = pd.Name;
|
|
WebControlParameterProxy wcParameter = new WebControlParameterProxy(propertyName, referenceParameters, _owner);
|
|
object propValue = null;
|
|
object value = null;
|
|
if (null != values)
|
|
{
|
|
value = values[propertyName];
|
|
}
|
|
try
|
|
{
|
|
propValue = ConvertProperty(value, pd, wcParameter);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (null == exceptions)
|
|
{
|
|
exceptions = new Dictionary<string, Exception>();
|
|
}
|
|
exceptions.Add(propertyName, e);
|
|
}
|
|
convertedValues[propertyName] = propValue;
|
|
visitedProperties.Add(propertyName);
|
|
}
|
|
private void ConvertProperties(IDictionary values, PropertyDescriptorCollection propertyDescriptors, ParameterCollection referenceParameters, Dictionary<string, object> convertedValues)
|
|
{
|
|
List<string> visitedProperties = new List<string>();
|
|
Dictionary<string, Exception> exceptions = null;
|
|
|
|
// "values" come from the web page. Either via keys or original values. This loops sets them as first priority
|
|
foreach (string propertyName in values.Keys)
|
|
{
|
|
if (!convertedValues.ContainsKey(propertyName))
|
|
{
|
|
PropertyDescriptor pd = propertyDescriptors.Find(propertyName, /*ignoreCase*/ false);
|
|
if (pd == null)
|
|
{
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_UnknownProperty(propertyName));
|
|
}
|
|
ConvertWCProperty(values, convertedValues, visitedProperties,
|
|
pd, referenceParameters, ref exceptions);
|
|
}
|
|
// else ignore the value because it is already contained in the set of output values
|
|
}
|
|
|
|
// If a property hasn't been set by visible columns or DataKeyNames, then we set them here.
|
|
// "referenceParameters" are Insert, Update or DeleteParameters assigning default values to
|
|
// columns that are not set by the control
|
|
if (null != referenceParameters)
|
|
{
|
|
IOrderedDictionary referenceValues = referenceParameters.GetValues(_owner.HttpContext, _owner);
|
|
foreach (string propertyName in referenceValues.Keys)
|
|
{
|
|
if (!visitedProperties.Contains(propertyName))
|
|
{
|
|
PropertyDescriptor pd = propertyDescriptors.Find(propertyName, /*ignoreCase*/ false);
|
|
if (pd == null)
|
|
{
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_UnknownProperty(propertyName));
|
|
}
|
|
ConvertWCProperty(null, convertedValues, visitedProperties,
|
|
pd, referenceParameters, ref exceptions);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (null != exceptions)
|
|
{
|
|
// The IDynamicValidationException encapsulates all of the data conversion errors.
|
|
// This exposes one of the encapsulated errors in its own message like:
|
|
// "Error while setting property 'ProductName': 'The value cannot be null.'."
|
|
string key = exceptions.Keys.First();
|
|
throw new EntityDataSourceValidationException(
|
|
Strings.EntityDataSourceView_DataConversionError(
|
|
key, exceptions[key].Message), exceptions);
|
|
}
|
|
}
|
|
|
|
private void CreateEntityForInsert(EntityDataSourceWrapper entityWrapper, IDictionary values, ParameterCollection insertParameters)
|
|
{
|
|
EntityDataSourceUtil.ValidateWebControlParameterNames(entityWrapper, insertParameters, _owner);
|
|
|
|
// Throws EntityDataSourceValidationException if data conversion fails
|
|
Dictionary<string, object> entityValues = new Dictionary<string, object>();
|
|
ConvertProperties(values, entityWrapper.GetProperties(), insertParameters, entityValues);
|
|
|
|
EntityDataSourceUtil.SetAllPropertiesWithVerification(entityWrapper, entityValues, /*overwrite*/true);
|
|
}
|
|
|
|
private void UpdateEntity(Dictionary<string, object> originalEntityValues, IDictionary values,
|
|
EntityDataSourceWrapper entityWrapper, ParameterCollection updateParameters)
|
|
{
|
|
EntityDataSourceUtil.ValidateWebControlParameterNames(entityWrapper, updateParameters, _owner);
|
|
Dictionary<string, object> currentEntityValues = new Dictionary<string, object>();
|
|
ConvertProperties(values, entityWrapper.GetProperties(), updateParameters, currentEntityValues);
|
|
Dictionary<string, object> allModifiedProperties = new Dictionary<string, object>();
|
|
|
|
// Compare the propertyValues from the original values from the page to those in the new "values" properties.
|
|
// Note that the comparison is between the values that came back from the databound control, BUT
|
|
// The values on the entity in the entityWrapper came from ViewState. We can't compare the values from the page
|
|
// To those stored in ViewState in case they didn't roundtrip well. Hence this next loop.
|
|
foreach (string propertyName in currentEntityValues.Keys)
|
|
{
|
|
object originalValue = null;
|
|
originalEntityValues.TryGetValue(propertyName, out originalValue);
|
|
object newValue = currentEntityValues[propertyName];
|
|
|
|
if (!OriginalValueMatches(originalValue, newValue))
|
|
{
|
|
allModifiedProperties[propertyName] = newValue;
|
|
}
|
|
}
|
|
|
|
EntityDataSourceUtil.SetAllPropertiesWithVerification(entityWrapper, allModifiedProperties, /*overwrite*/false);
|
|
}
|
|
|
|
#endregion Entity Creation Support
|
|
|
|
#region ViewState Storage
|
|
|
|
internal void StoreOriginalPropertiesIntoViewState()
|
|
{
|
|
EntityDataSourceWrapperCollection wrapperCollection = _collectionManager.FlattenedEntityCollection ?? _collectionManager.UpdateCache;
|
|
|
|
if (null == wrapperCollection ||
|
|
0 == wrapperCollection.Count ||
|
|
!_owner.StoreOriginalValuesInViewState ||
|
|
(!CanDelete && !CanUpdate)) //Only store entities into viewstate if Delete or Update is enabled.
|
|
{
|
|
return;
|
|
}
|
|
|
|
_originalProperties = new Dictionary<string, ArrayList>();
|
|
|
|
PropertyDescriptorCollection collection = wrapperCollection.GetItemProperties(null);
|
|
|
|
// Retrieve the named properties from each entity and place them into the viewstate collection.
|
|
foreach (EntityDataSourceWrapper wrapper in wrapperCollection)
|
|
{
|
|
foreach (PropertyDescriptor propertyDescriptor in collection)
|
|
{
|
|
EntityDataSourceWrapperPropertyDescriptor wrapperPropertyDescriptor = (EntityDataSourceWrapperPropertyDescriptor)propertyDescriptor;
|
|
|
|
if (wrapperPropertyDescriptor.Column.IsInteresting && wrapperPropertyDescriptor.Column.IsScalar)
|
|
{
|
|
object property = wrapperPropertyDescriptor.GetValue(wrapper);
|
|
string propertyName = wrapperPropertyDescriptor.DisplayName;
|
|
|
|
if (!_originalProperties.ContainsKey(propertyName))
|
|
{
|
|
_originalProperties[propertyName] = new ArrayList();
|
|
}
|
|
|
|
(_originalProperties[propertyName]).Add(property);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void GetValuesFromViewState(Dictionary<string, object> entityValues)
|
|
{
|
|
int idx = FindIdxOfPropertiesStoredInViewState(entityValues);
|
|
|
|
if (0 <= idx) // "-1" indicates that the value was not found in ViewState
|
|
{
|
|
foreach (string propertyName in _originalProperties.Keys)
|
|
{
|
|
object property = (_originalProperties[propertyName])[idx];
|
|
entityValues[propertyName] = property;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the index of the entity in ViewState that has key values that match the values from the page.
|
|
/// If a match is not found, it returns -1.
|
|
/// </summary>
|
|
/// <param name="mergedKeysAndOldValues"></param>
|
|
/// <returns></returns>
|
|
private int FindIdxOfPropertiesStoredInViewState(IDictionary mergedKeysAndOldValues)
|
|
{
|
|
if (null == _originalProperties || 0 == _originalProperties.Count)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
Dictionary<string, object> localMergedKeysAndOldValues = (Dictionary<string, object>)mergedKeysAndOldValues;
|
|
|
|
// This get the number of entities from the first property's values.
|
|
// The ArrayList that holds the values contains the count of the number of entities.
|
|
int numEntities = (_originalProperties.First().Value).Count;
|
|
for (int idx = 0; idx < numEntities; idx++)
|
|
{
|
|
bool match = true;
|
|
foreach (EdmMember edmMember in KeyMembers)
|
|
{
|
|
string propertyName = edmMember.Name;
|
|
// The page must return the key values.
|
|
if (!localMergedKeysAndOldValues.ContainsKey(propertyName))
|
|
{
|
|
match = false;
|
|
break;
|
|
}
|
|
|
|
object origValue = (_originalProperties[propertyName])[idx];
|
|
object pageValue = localMergedKeysAndOldValues[propertyName];
|
|
if (!OriginalValueMatches(origValue, pageValue))
|
|
{
|
|
match = false;
|
|
break;
|
|
}
|
|
}
|
|
if (match)
|
|
{
|
|
return idx;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
private bool OriginalValueMatches(object originalValue, object value)
|
|
{
|
|
if (null == originalValue)
|
|
{
|
|
if (null == value)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// 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 EnumerableContentEquals(originalValueEnumerable, valueEnumerable);
|
|
}
|
|
|
|
return originalValue.Equals(value);
|
|
}
|
|
|
|
#endregion ViewState Storage
|
|
|
|
#region Context and Query construction
|
|
|
|
private void ConstructContext()
|
|
{
|
|
this.DisposeContext();
|
|
|
|
Type contextType = null;
|
|
EntityDataSourceContextCreatingEventArgs creatingArgs = new EntityDataSourceContextCreatingEventArgs();
|
|
OnContextCreating(creatingArgs);
|
|
if (null != creatingArgs.Context) //Context was created in event code
|
|
{
|
|
_ctx = creatingArgs.Context;
|
|
contextType = _ctx.GetType();
|
|
}
|
|
else if (null != _owner.ContextType || !String.IsNullOrEmpty(_owner.ContextTypeName))
|
|
{
|
|
//Construct the context.
|
|
if (null != _owner.ContextType)
|
|
{
|
|
contextType = _owner.ContextType;
|
|
}
|
|
else
|
|
{
|
|
contextType = System.Web.Compilation.BuildManager.GetType(_owner.ContextTypeName, /*throw on error*/ true);
|
|
}
|
|
|
|
ConstructorInfo ctxInfo = contextType.GetConstructor(
|
|
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance,
|
|
null, System.Type.EmptyTypes, null);
|
|
|
|
if (null == ctxInfo)
|
|
{
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_NoParameterlessConstructorForTheContext);
|
|
}
|
|
|
|
_ctx = (ObjectContext)ctxInfo.Invoke(new object[0]);
|
|
}
|
|
else // Non-strongly-typed context built from ConnectionString and DefaultContainerName
|
|
{
|
|
if (!String.IsNullOrEmpty(_owner.DefaultContainerName) &&
|
|
!String.IsNullOrEmpty(_owner.ConnectionString))
|
|
{
|
|
_ctx = new ObjectContext(_owner.ConnectionString);
|
|
|
|
if (System.Web.Hosting.HostingEnvironment.IsHosted)
|
|
{
|
|
// Since we don't have the type from the strongly-typed context,
|
|
// load from all of the referenced assemblies, including code from App_Code and the top-level directory:
|
|
// http://msdn2.microsoft.com/en-us/library/system.web.compilation.buildmanager.getreferencedassemblies.aspx
|
|
ICollection codeAssemblies = System.Web.Compilation.BuildManager.GetReferencedAssemblies();
|
|
foreach (Assembly assembly in codeAssemblies)
|
|
{
|
|
if (ShouldTryLoadTypesFrom(assembly))
|
|
{
|
|
try
|
|
{
|
|
_ctx.MetadataWorkspace.LoadFromAssembly(assembly);
|
|
}
|
|
catch (ReflectionTypeLoadException)
|
|
{
|
|
// BuildManager returns all assemblies that could possibly be involved in generating this page (e.g.
|
|
// assemblies that are defined in config files, assemblies generated for the files compiled on the fly,
|
|
// assemblies that are in the bin folder. If for some reason (security, missing dependencies etc.) we are
|
|
// not able to load one of these just skip it and continue looking instead of dying. In vast majority of
|
|
// cases the assembly causing problem is not the assembly that contains the types we are interested in.
|
|
// If this exception happens for an assembly we are interested in then we will be missing OSpace type for
|
|
// a CSpace type and will throw when we will try using the type for the first time.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (!String.IsNullOrEmpty(_owner.DefaultContainerName) &&
|
|
null != _owner.Connection)
|
|
{
|
|
_ctx = new ObjectContext(_owner.Connection);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_ObjectContextMustBeSpecified);
|
|
}
|
|
// Must set the DefaultContainerName for both of the above conditions.
|
|
_ctx.DefaultContainerName = _owner.DefaultContainerName;
|
|
contextType = typeof(ObjectContext);
|
|
}
|
|
|
|
_ctx.MetadataWorkspace.LoadFromAssembly(System.Reflection.Assembly.GetCallingAssembly());
|
|
_ctx.MetadataWorkspace.LoadFromAssembly(contextType.Assembly);
|
|
|
|
|
|
// Error Checking on the Context
|
|
ValidateContainerName();
|
|
|
|
OnContextCreated(new EntityDataSourceContextCreatedEventArgs(Context));
|
|
}
|
|
|
|
// QueryCreated Event
|
|
private void OnQueryCreated(QueryCreatedEventArgs e)
|
|
{
|
|
var handler = (EventHandler<QueryCreatedEventArgs>)Events[EventQueryCreated];
|
|
if (null != handler)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
|
|
[CLSCompliant(false)]
|
|
public event EventHandler<QueryCreatedEventArgs> QueryCreated
|
|
{
|
|
add { Events.AddHandler(EventQueryCreated, value); }
|
|
remove { Events.RemoveHandler(EventQueryCreated, value); }
|
|
}
|
|
|
|
internal void DisposeContext()
|
|
{
|
|
if (null != _ctx)
|
|
{
|
|
EntityDataSourceContextDisposingEventArgs disposeArgs = new EntityDataSourceContextDisposingEventArgs(_ctx);
|
|
OnContextDisposing(disposeArgs);
|
|
if (!disposeArgs.Cancel)
|
|
{
|
|
_ctx.Dispose();
|
|
_ctx = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion Context and Query construction
|
|
#endregion Private Methods
|
|
|
|
#region Public Overrides
|
|
public override bool CanInsert
|
|
{
|
|
get { return _owner.EnableInsert && !_disableUpdates; }
|
|
}
|
|
public override bool CanUpdate
|
|
{
|
|
get { return _owner.EnableUpdate && !_disableUpdates; }
|
|
}
|
|
public override bool CanDelete
|
|
{
|
|
get { return _owner.EnableDelete && !_disableUpdates; }
|
|
}
|
|
public override bool CanSort
|
|
{
|
|
get { return _owner.AutoSort; }
|
|
}
|
|
public override bool CanPage
|
|
{
|
|
get { return _owner.AutoPage; }
|
|
}
|
|
public override bool CanRetrieveTotalRowCount
|
|
{
|
|
get { return _owner.AutoPage; }
|
|
}
|
|
#endregion Public Overrides
|
|
|
|
#region Events
|
|
|
|
//Oryx exception event
|
|
private void OnException(DynamicValidatorEventArgs args)
|
|
{
|
|
var handler = (EventHandler<DynamicValidatorEventArgs>)Events[EventException];
|
|
if (null != handler)
|
|
{
|
|
handler(this, args);
|
|
}
|
|
}
|
|
public event EventHandler<DynamicValidatorEventArgs> Exception
|
|
{
|
|
add { Events.AddHandler(EventException, value); }
|
|
remove { Events.RemoveHandler(EventException, value); }
|
|
}
|
|
|
|
// ContextCreating Event
|
|
private void OnContextCreating(EntityDataSourceContextCreatingEventArgs e)
|
|
{
|
|
var handler = (EventHandler<EntityDataSourceContextCreatingEventArgs>)Events[EventContextCreating];
|
|
if (null != handler)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
public event EventHandler<EntityDataSourceContextCreatingEventArgs> ContextCreating
|
|
{
|
|
add { Events.AddHandler(EventContextCreating, value); }
|
|
remove { Events.RemoveHandler(EventContextCreating, value); }
|
|
}
|
|
|
|
// ContextCreated Event
|
|
private void OnContextCreated(EntityDataSourceContextCreatedEventArgs e)
|
|
{
|
|
var handler = (EventHandler<EntityDataSourceContextCreatedEventArgs>)Events[EventContextCreated];
|
|
if (null != handler)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
public event EventHandler<EntityDataSourceContextCreatedEventArgs> ContextCreated
|
|
{
|
|
add { Events.AddHandler(EventContextCreated, value); }
|
|
remove { Events.RemoveHandler(EventContextCreated, value); }
|
|
}
|
|
|
|
// ContextDisposing Event
|
|
private void OnContextDisposing(EntityDataSourceContextDisposingEventArgs e)
|
|
{
|
|
var handler = (EventHandler<EntityDataSourceContextDisposingEventArgs>)Events[EventContextDisposing];
|
|
if (null != handler)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
public event EventHandler<EntityDataSourceContextDisposingEventArgs> ContextDisposing
|
|
{
|
|
add { Events.AddHandler(EventContextDisposing, value); }
|
|
remove { Events.RemoveHandler(EventContextDisposing, value); }
|
|
}
|
|
|
|
// Selecting Event
|
|
private void OnSelecting(EntityDataSourceSelectingEventArgs e)
|
|
{
|
|
var handler = (EventHandler<EntityDataSourceSelectingEventArgs>)Events[EventSelecting];
|
|
if (null != handler)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
public event EventHandler<EntityDataSourceSelectingEventArgs> Selecting
|
|
{
|
|
add { Events.AddHandler(EventSelecting, value); }
|
|
remove { Events.RemoveHandler(EventSelecting, value); }
|
|
}
|
|
|
|
// Selected Event
|
|
private void OnSelected(EntityDataSourceSelectedEventArgs e)
|
|
{
|
|
var handler = (EventHandler<EntityDataSourceSelectedEventArgs>)Events[EventSelected];
|
|
if (null != handler)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
public event EventHandler<EntityDataSourceSelectedEventArgs> Selected
|
|
{
|
|
add { Events.AddHandler(EventSelected, value); }
|
|
remove { Events.RemoveHandler(EventSelected, value); }
|
|
}
|
|
|
|
// Deleting Event
|
|
private void OnDeleting(EntityDataSourceChangingEventArgs e)
|
|
{
|
|
var handler = (EventHandler<EntityDataSourceChangingEventArgs>)Events[EventDeleting];
|
|
if (null != handler)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
public event EventHandler<EntityDataSourceChangingEventArgs> Deleting
|
|
{
|
|
add { Events.AddHandler(EventDeleting, value); }
|
|
remove { Events.RemoveHandler(EventDeleting, value); }
|
|
}
|
|
|
|
// Deleted Event
|
|
private void OnDeleted(EntityDataSourceChangedEventArgs e)
|
|
{
|
|
var handler = (EventHandler<EntityDataSourceChangedEventArgs>)Events[EventDeleted];
|
|
if (null != handler)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
public event EventHandler<EntityDataSourceChangedEventArgs> Deleted
|
|
{
|
|
add { Events.AddHandler(EventDeleted, value); }
|
|
remove { Events.RemoveHandler(EventDeleted, value); }
|
|
}
|
|
|
|
//Inserting Event
|
|
private void OnInserting(EntityDataSourceChangingEventArgs e)
|
|
{
|
|
var handler = (EventHandler<EntityDataSourceChangingEventArgs>)Events[EventInserting];
|
|
if (handler != null)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
public event EventHandler<EntityDataSourceChangingEventArgs> Inserting
|
|
{
|
|
add { Events.AddHandler(EventInserting, value); }
|
|
remove { Events.RemoveHandler(EventInserting, value); }
|
|
}
|
|
|
|
//Inserted Event
|
|
private void OnInserted(EntityDataSourceChangedEventArgs e)
|
|
{
|
|
var handler = (EventHandler<EntityDataSourceChangedEventArgs>)Events[EventInserted];
|
|
if (handler != null)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
public event EventHandler<EntityDataSourceChangedEventArgs> Inserted
|
|
{
|
|
add { Events.AddHandler(EventInserted, value); }
|
|
remove { Events.RemoveHandler(EventInserted, value); }
|
|
}
|
|
|
|
//Updating Event
|
|
private void OnUpdating(EntityDataSourceChangingEventArgs e)
|
|
{
|
|
var handler = (EventHandler<EntityDataSourceChangingEventArgs>)Events[EventUpdating];
|
|
if (handler != null)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
public event EventHandler<EntityDataSourceChangingEventArgs> Updating
|
|
{
|
|
add { Events.AddHandler(EventUpdating, value); }
|
|
remove { Events.RemoveHandler(EventUpdating, value); }
|
|
}
|
|
|
|
//Updated Event
|
|
private void OnUpdated(EntityDataSourceChangedEventArgs e)
|
|
{
|
|
var handler = (EventHandler<EntityDataSourceChangedEventArgs>)Events[EventUpdated];
|
|
if (handler != null)
|
|
{
|
|
handler(this, e);
|
|
}
|
|
}
|
|
public event EventHandler<EntityDataSourceChangedEventArgs> Updated
|
|
{
|
|
add { Events.AddHandler(EventUpdated, value); }
|
|
remove { Events.RemoveHandler(EventUpdated, value); }
|
|
}
|
|
|
|
|
|
internal void RaiseChangedEvent()
|
|
{
|
|
OnDataSourceViewChanged(EventArgs.Empty);
|
|
}
|
|
|
|
#endregion Events
|
|
|
|
#region Private Properties
|
|
|
|
private StructuralType EntityOSpaceType
|
|
{
|
|
get
|
|
{
|
|
// If the CSpaceType is not determinable (e.g. DataSource.EntitySetName is not specified),
|
|
// then return null.
|
|
if (null == EntityCSpaceType)
|
|
{
|
|
return null;
|
|
}
|
|
StructuralType oSpaceType = Context.MetadataWorkspace.GetObjectSpaceType(EntityCSpaceType);
|
|
return oSpaceType;
|
|
}
|
|
}
|
|
|
|
// Returns the type for EntityTypeFilter, or
|
|
// null if both EntitySet and EntityTypeFilter are not specified.
|
|
private EntityType CSpaceFilteredEntityType
|
|
{
|
|
get
|
|
{
|
|
EntityType cSpaceType = null;
|
|
if (null == EntitySet)
|
|
{
|
|
return null; //read-only scenario in which the EntitySet is not specified on the DataSource
|
|
}
|
|
|
|
// Return the type specified in EntityTypeFilter
|
|
if (!String.IsNullOrEmpty(_owner.EntityTypeFilter))
|
|
{
|
|
cSpaceType = (EntityType)Context.MetadataWorkspace.GetType(_owner.EntityTypeFilter, EntitySet.ElementType.NamespaceName, DataSpace.CSpace);
|
|
if (!EntityDataSourceUtil.IsTypeOrSubtypeOf(EntitySet.ElementType, cSpaceType, Context.MetadataWorkspace.GetItemCollection(DataSpace.CSpace)))
|
|
{
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_FilteredEntityTypeMustBeDerivableFromEntitySet(_owner.EntityTypeFilter, _owner.EntitySetName));
|
|
}
|
|
return cSpaceType;
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
}
|
|
|
|
// Returns the actualy EntityCSpaceType to be returned from the query, either via
|
|
// EntityTypeFilter or via the base type for the EntitySet.
|
|
private EntityType EntityCSpaceType
|
|
{
|
|
get
|
|
{
|
|
EntityType cSpaceType = CSpaceFilteredEntityType;
|
|
if (null != cSpaceType)
|
|
{
|
|
return cSpaceType;
|
|
}
|
|
|
|
if (null != EntitySet)
|
|
{
|
|
//If EntityDataSource.EntityTypeFilter is not specified, return the base type for the EntitySet.
|
|
cSpaceType = EntitySet.ElementType;
|
|
}
|
|
return cSpaceType;
|
|
}
|
|
|
|
}
|
|
|
|
private EntityContainer EntityContainer
|
|
{
|
|
get
|
|
{
|
|
return Context.MetadataWorkspace.GetEntityContainer(ContainerName, DataSpace.CSpace);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The EntitySet Associated with this DataSource. If EntityDataSource.EntitySetName is not set, then
|
|
/// This property returns null.
|
|
/// </summary>
|
|
private EntitySet EntitySet
|
|
{
|
|
get
|
|
{
|
|
if (String.IsNullOrEmpty(_owner.EntitySetName))
|
|
{
|
|
return null;
|
|
}
|
|
return EntityContainer.GetEntitySetByName(_owner.EntitySetName, /*ignoreCase*/ false);
|
|
}
|
|
}
|
|
|
|
private Type EntityClrType
|
|
{
|
|
get
|
|
{
|
|
ObjectItemCollection objectItemCollection =
|
|
(ObjectItemCollection)(Context.MetadataWorkspace.GetItemCollection(DataSpace.OSpace));
|
|
Type clrType = objectItemCollection.GetClrType(EntityOSpaceType);
|
|
return clrType;
|
|
}
|
|
}
|
|
|
|
private ObjectContext Context
|
|
{
|
|
get
|
|
{
|
|
Debug.Assert(null != _ctx, "The context hasn't yet been constructed");
|
|
return _ctx;
|
|
}
|
|
}
|
|
|
|
private ReadOnlyMetadataCollection<EdmMember> KeyMembers
|
|
{
|
|
get
|
|
{
|
|
if (null == _keyMembers)
|
|
{
|
|
EntityContainer entityContainer = Context.MetadataWorkspace.GetEntityContainer(ContainerName, DataSpace.CSpace);
|
|
EntitySet entitySet = entityContainer.GetEntitySetByName(_owner.EntitySetName, false);
|
|
|
|
_keyMembers = ((EntityType)(entitySet.ElementType)).KeyMembers;
|
|
}
|
|
return _keyMembers;
|
|
}
|
|
}
|
|
|
|
private string ContainerName
|
|
{
|
|
get
|
|
{
|
|
if (!string.IsNullOrEmpty(_owner.DefaultContainerName))
|
|
{
|
|
return _owner.DefaultContainerName;
|
|
}
|
|
if (!string.IsNullOrEmpty(Context.DefaultContainerName))
|
|
{
|
|
return Context.DefaultContainerName;
|
|
}
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_ContainerNameMustBeSpecified);
|
|
}
|
|
}
|
|
|
|
#endregion Private Properties
|
|
|
|
#region private getters
|
|
|
|
#endregion private getters
|
|
|
|
#region IStateManager implementation
|
|
bool IStateManager.IsTrackingViewState
|
|
{
|
|
get { return _tracking; }
|
|
}
|
|
void IStateManager.LoadViewState(object savedState)
|
|
{
|
|
if (null != savedState)
|
|
{
|
|
var state = (Pair)savedState;
|
|
_disableUpdates = (bool)state.First;
|
|
_originalProperties = (Dictionary<string, ArrayList>)state.Second;
|
|
}
|
|
}
|
|
|
|
object IStateManager.SaveViewState()
|
|
{
|
|
StoreOriginalPropertiesIntoViewState();
|
|
return new Pair(_disableUpdates, _originalProperties);
|
|
}
|
|
void IStateManager.TrackViewState()
|
|
{
|
|
_tracking = true;
|
|
}
|
|
#endregion
|
|
|
|
#region Utilities and Error Checking
|
|
|
|
private void AddSupportedCapabilities(DataSourceSelectArguments arguments)
|
|
{
|
|
if (CanSort)
|
|
{
|
|
arguments.AddSupportedCapabilities(DataSourceCapabilities.Sort);
|
|
}
|
|
if (CanPage)
|
|
{
|
|
arguments.AddSupportedCapabilities(DataSourceCapabilities.Page);
|
|
arguments.AddSupportedCapabilities(DataSourceCapabilities.RetrieveTotalRowCount);
|
|
}
|
|
}
|
|
|
|
internal void ValidateEntitySetName()
|
|
{
|
|
EntityContainer entityContainer = Context.MetadataWorkspace.GetEntityContainer(ContainerName, DataSpace.CSpace);
|
|
EntitySet entitySet;
|
|
if (!entityContainer.TryGetEntitySetByName(_owner.EntitySetName, /*ignoreCase*/false, out entitySet))
|
|
{
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_EntitySetDoesNotExistOnTheContainer(_owner.EntitySetName));
|
|
}
|
|
}
|
|
|
|
private void ValidateContainerName()
|
|
{
|
|
EntityContainer container;
|
|
if (!Context.MetadataWorkspace.TryGetEntityContainer(ContainerName, DataSpace.CSpace, out container))
|
|
{
|
|
throw new InvalidOperationException(Strings.EntityDataSourceView_ContainerNameDoesNotExistOnTheContext(ContainerName));
|
|
}
|
|
}
|
|
|
|
// From LinqDataSourceHelper. Timestamp columns (of type byte[]) are of type IEnumerable.
|
|
// This routine compares all elements of the IEnumerable.
|
|
private static bool EnumerableContentEquals(IEnumerable enumerableA, IEnumerable enumerableB)
|
|
{
|
|
IEnumerator enumeratorA = enumerableA.GetEnumerator();
|
|
IEnumerator enumeratorB = enumerableB.GetEnumerator();
|
|
while (enumeratorA.MoveNext())
|
|
{
|
|
if (!enumeratorB.MoveNext())
|
|
return false;
|
|
object itemA = enumeratorA.Current;
|
|
object itemB = enumeratorB.Current;
|
|
if (itemA == null)
|
|
{
|
|
if (itemB != null)
|
|
return false;
|
|
}
|
|
else if (!itemA.Equals(itemB))
|
|
return false;
|
|
}
|
|
if (enumeratorB.MoveNext())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// This routine modified from System.Web.Ui.WebControls.
|
|
private static object Parameter_GetValue(object value, WebControlParameterProxy parameter, bool ignoreNullableTypeChanges)
|
|
{
|
|
// Convert.ChangeType() throws if you attempt to convert to DBNull, so we have to special case it.
|
|
if (parameter.TypeCode == TypeCode.DBNull)
|
|
{
|
|
return DBNull.Value;
|
|
}
|
|
|
|
// Get the value and convert it to the default value if it is null
|
|
if (parameter.ConvertEmptyStringToNull)
|
|
{
|
|
string stringValue = value as string;
|
|
if ((stringValue != null) && (stringValue.Length == 0))
|
|
{
|
|
value = null;
|
|
}
|
|
|
|
}
|
|
|
|
if (value == null) // Fill it with values from referenceParameters
|
|
{
|
|
// Use the parameter value if it is non-null
|
|
if (!parameter.HasValue)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
object parameterValue = parameter.Value;
|
|
string valueString = parameterValue as String;
|
|
if (parameter.TypeCode == TypeCode.String && parameter.ConvertEmptyStringToNull && String.IsNullOrEmpty(valueString))
|
|
{
|
|
parameterValue = null;
|
|
}
|
|
|
|
if (null == parameterValue)
|
|
{
|
|
return null;
|
|
}
|
|
value = parameterValue;
|
|
}
|
|
|
|
Debug.Assert(value != null, "Value should not be null at this point.");
|
|
|
|
if (parameter.TypeCode == TypeCode.Object || parameter.TypeCode == TypeCode.Empty)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
// For ObjectDataSource we special-case Nullable<T> and do nothing because these
|
|
// types will get converted when we actually call the method.
|
|
if (ignoreNullableTypeChanges)
|
|
{
|
|
Type valueType = value.GetType();
|
|
if (valueType.IsGenericType && (valueType.GetGenericTypeDefinition() == typeof(Nullable<>)))
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
return value = Convert.ChangeType(value, parameter.TypeCode, CultureInfo.CurrentCulture); ;
|
|
}
|
|
|
|
|
|
|
|
private static byte[] EcmaPublicKeyToken = { 0xb7, 0x7a, 0x5c, 0x56, 0x19, 0x34, 0xe0, 0x89 }; // b77a5c561934e089
|
|
private static byte[] MicrosoftPublicKeyToken = { 0xb0, 0x3f, 0x5f, 0x7f, 0x11, 0xd5, 0x0a, 0x3a }; // b03f5f7f11d50a3a
|
|
private static byte[] SharedLibPublicKeyToken = { 0x31, 0xbf, 0x38, 0x56, 0xad, 0x36, 0x4e, 0x35 }; // 31bf3856ad364e35
|
|
|
|
/// <summary>
|
|
/// Checks whether to try loading types from the <paramref name="assembly"/>.
|
|
/// </summary>
|
|
/// <param name="assembly">Assembly to be checked.</param>
|
|
/// <returns><c>true</c> if we should try loading types from this assembly. <c>false</c> otherwise.</returns>
|
|
/// <remarks>
|
|
/// Assemblies that are part of .NET Framework don't have entity/complex/enum types that could correspond to EdmTypes.
|
|
/// Therefore we should not try loading types from these assemblies. There are a lot of types there so it is costly
|
|
/// but we know that we would not find any interesting types there. This method filters out these assemblies.
|
|
/// </remarks>
|
|
private static bool ShouldTryLoadTypesFrom(Assembly assembly)
|
|
{
|
|
// assembly.GetName() required FileIOPermission and won't work in partial trust.
|
|
// The workaround is to parse the assembly.FullName
|
|
var asmPublicKeyToken = new AssemblyName(assembly.FullName).GetPublicKeyToken();
|
|
|
|
Debug.Assert(asmPublicKeyToken != null);
|
|
|
|
return !(asmPublicKeyToken.SequenceEqual(EcmaPublicKeyToken) ||
|
|
asmPublicKeyToken.SequenceEqual(MicrosoftPublicKeyToken) ||
|
|
asmPublicKeyToken.SequenceEqual(SharedLibPublicKeyToken));
|
|
}
|
|
|
|
#endregion Utilities
|
|
|
|
//public override void Delete(IDictionary keys, IDictionary oldValues, DataSourceViewOperationCallback callback){}
|
|
//public override void Insert(IDictionary values, DataSourceViewOperationCallback callback){}
|
|
//public override void Select(DataSourceSelectArguments arguments, DataSourceViewSelectCallback callback){}
|
|
//public override void Update(IDictionary keys, IDictionary values, IDictionary oldValues, DataSourceViewOperationCallback callback){}
|
|
|
|
}
|
|
}
|