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