//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @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 { /// /// Helper class used to manage EntityDataSourceWrapperCollection collections. /// /// /// 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. /// private class WrapperCollectionManager { /// /// Modes the manager can be working in. /// public enum ManagerMode { /// /// None - entities are not stored. is null. /// Both and return null. /// None, /// /// Entites are stored because flag is set to true. /// returns non-null value whiel returns null. /// FlattenedEntities, /// /// Entites are stored because flag is set to false but /// is set to true. /// returns non-null value whiel returns null. /// UpdateCache } private ManagerMode _collectionMode; private EntityDataSourceWrapperCollection _collection; /// /// Mode the WrapperCollectionManager is working in. /// public ManagerMode Mode { get { return _collectionMode; } } /// /// Gets collection containing flattened entities. Possibly null. /// public EntityDataSourceWrapperCollection FlattenedEntityCollection { get { return _collectionMode == ManagerMode.FlattenedEntities ? _collection : null; } } /// /// Gets collection cached entities. Possibly null. /// public EntityDataSourceWrapperCollection UpdateCache { get { return _collectionMode == ManagerMode.UpdateCache ? _collection : null; } } /// /// Creates a collection for storing wrapped entities. /// /// ObjectContext /// Entity set the stored belongs to. /// What entity type restrict the collection to. Null if all derived entity types are allowed. /// What mode to store the entities. Never . 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); } /// /// Wraps the and adds it to the collection. /// /// Entity to add to the collection. Must not be null. 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 _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 _originalProperties; private WrapperCollectionManager _collectionManager = new WrapperCollectionManager(); #region Constructor /// /// Initialize a new named instance of the EntityDataSourceView class, and /// associates the specified EntityDataSource with it. /// 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(arguments, EntityDataSourceRecordQueryBuilder.Create); } else if (!string.IsNullOrEmpty(_owner.CommandText)) { return ExecuteSelectTyped(arguments, EntityDataSourceObjectQueryBuilder.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(DataSourceSelectArguments arguments, EntityDataSourceQueryBuilder.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 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 query = queryBuilder.BuildBasicQuery(Context, arguments.RetrieveTotalRowCount); ObjectQuery 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.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(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; } return ContinueSelectTyped(arguments, queryBuilder, query, wasQueryModified); } private IEnumerable ContinueSelectTyped(DataSourceSelectArguments arguments, EntityDataSourceQueryBuilder queryBuilder, ObjectQuery 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; } /// /// Restrict the query to the type specified by EntityTypeFilter. /// /// 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 originalEntityValues = new Dictionary(); 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 entityValues = new Dictionary(); 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 whereParameterList = new List(); 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 convertedValues, List visitedProperties, PropertyDescriptor pd, ParameterCollection referenceParameters, ref Dictionary 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(); } exceptions.Add(propertyName, e); } convertedValues[propertyName] = propValue; visitedProperties.Add(propertyName); } private void ConvertProperties(IDictionary values, PropertyDescriptorCollection propertyDescriptors, ParameterCollection referenceParameters, Dictionary convertedValues) { List visitedProperties = new List(); Dictionary 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 entityValues = new Dictionary(); ConvertProperties(values, entityWrapper.GetProperties(), insertParameters, entityValues); EntityDataSourceUtil.SetAllPropertiesWithVerification(entityWrapper, entityValues, /*overwrite*/true); } private void UpdateEntity(Dictionary originalEntityValues, IDictionary values, EntityDataSourceWrapper entityWrapper, ParameterCollection updateParameters) { EntityDataSourceUtil.ValidateWebControlParameterNames(entityWrapper, updateParameters, _owner); Dictionary currentEntityValues = new Dictionary(); ConvertProperties(values, entityWrapper.GetProperties(), updateParameters, currentEntityValues); Dictionary allModifiedProperties = new Dictionary(); // 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(); 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 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; } } } /// /// 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. /// /// /// private int FindIdxOfPropertiesStoredInViewState(IDictionary mergedKeysAndOldValues) { if (null == _originalProperties || 0 == _originalProperties.Count) { return -1; } Dictionary localMergedKeysAndOldValues = (Dictionary)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)Events[EventQueryCreated]; if (null != handler) { handler(this, e); } } [CLSCompliant(false)] public event EventHandler 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)Events[EventException]; if (null != handler) { handler(this, args); } } public event EventHandler Exception { add { Events.AddHandler(EventException, value); } remove { Events.RemoveHandler(EventException, value); } } // ContextCreating Event private void OnContextCreating(EntityDataSourceContextCreatingEventArgs e) { var handler = (EventHandler)Events[EventContextCreating]; if (null != handler) { handler(this, e); } } public event EventHandler ContextCreating { add { Events.AddHandler(EventContextCreating, value); } remove { Events.RemoveHandler(EventContextCreating, value); } } // ContextCreated Event private void OnContextCreated(EntityDataSourceContextCreatedEventArgs e) { var handler = (EventHandler)Events[EventContextCreated]; if (null != handler) { handler(this, e); } } public event EventHandler ContextCreated { add { Events.AddHandler(EventContextCreated, value); } remove { Events.RemoveHandler(EventContextCreated, value); } } // ContextDisposing Event private void OnContextDisposing(EntityDataSourceContextDisposingEventArgs e) { var handler = (EventHandler)Events[EventContextDisposing]; if (null != handler) { handler(this, e); } } public event EventHandler ContextDisposing { add { Events.AddHandler(EventContextDisposing, value); } remove { Events.RemoveHandler(EventContextDisposing, value); } } // Selecting Event private void OnSelecting(EntityDataSourceSelectingEventArgs e) { var handler = (EventHandler)Events[EventSelecting]; if (null != handler) { handler(this, e); } } public event EventHandler Selecting { add { Events.AddHandler(EventSelecting, value); } remove { Events.RemoveHandler(EventSelecting, value); } } // Selected Event private void OnSelected(EntityDataSourceSelectedEventArgs e) { var handler = (EventHandler)Events[EventSelected]; if (null != handler) { handler(this, e); } } public event EventHandler Selected { add { Events.AddHandler(EventSelected, value); } remove { Events.RemoveHandler(EventSelected, value); } } // Deleting Event private void OnDeleting(EntityDataSourceChangingEventArgs e) { var handler = (EventHandler)Events[EventDeleting]; if (null != handler) { handler(this, e); } } public event EventHandler Deleting { add { Events.AddHandler(EventDeleting, value); } remove { Events.RemoveHandler(EventDeleting, value); } } // Deleted Event private void OnDeleted(EntityDataSourceChangedEventArgs e) { var handler = (EventHandler)Events[EventDeleted]; if (null != handler) { handler(this, e); } } public event EventHandler Deleted { add { Events.AddHandler(EventDeleted, value); } remove { Events.RemoveHandler(EventDeleted, value); } } //Inserting Event private void OnInserting(EntityDataSourceChangingEventArgs e) { var handler = (EventHandler)Events[EventInserting]; if (handler != null) { handler(this, e); } } public event EventHandler Inserting { add { Events.AddHandler(EventInserting, value); } remove { Events.RemoveHandler(EventInserting, value); } } //Inserted Event private void OnInserted(EntityDataSourceChangedEventArgs e) { var handler = (EventHandler)Events[EventInserted]; if (handler != null) { handler(this, e); } } public event EventHandler Inserted { add { Events.AddHandler(EventInserted, value); } remove { Events.RemoveHandler(EventInserted, value); } } //Updating Event private void OnUpdating(EntityDataSourceChangingEventArgs e) { var handler = (EventHandler)Events[EventUpdating]; if (handler != null) { handler(this, e); } } public event EventHandler Updating { add { Events.AddHandler(EventUpdating, value); } remove { Events.RemoveHandler(EventUpdating, value); } } //Updated Event private void OnUpdated(EntityDataSourceChangedEventArgs e) { var handler = (EventHandler)Events[EventUpdated]; if (handler != null) { handler(this, e); } } public event EventHandler 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); } } /// /// The EntitySet Associated with this DataSource. If EntityDataSource.EntitySetName is not set, then /// This property returns null. /// 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 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)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 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 /// /// Checks whether to try loading types from the . /// /// Assembly to be checked. /// true if we should try loading types from this assembly. false otherwise. /// /// 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. /// 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){} } }