private void OnAddToCollection( NotifyCollectionChangedEventArgs eventArgs, object source, String sourceProperty, String targetEntitySet, object collection) { Debug.Assert(collection != null, "Must have a valid collection to which entities are added."); if (eventArgs.NewItems != null) { foreach (object target in eventArgs.NewItems) { if (target == null) { throw new InvalidOperationException(Strings.DataBinding_BindingOperation_ArrayItemNull("Add")); } if (!BindingEntityInfo.IsEntityType(target.GetType())) { throw new InvalidOperationException(Strings.DataBinding_BindingOperation_ArrayItemNotEntity("Add")); } this.bindingGraph.AddEntity( source, sourceProperty, target, targetEntitySet, collection); } } }
private void HandleUpdateEntity(object entity, string propertyName, object propertyValue) { if (!this.Context.ApplyingChanges) { if (!BindingEntityInfo.IsEntityType(entity.GetType(), this.Context.MaxProtocolVersion)) { this.bindingGraph.GetAncestorEntityForComplexProperty(ref entity, ref propertyName, ref propertyValue); } if (!this.IsDetachedOrDeletedFromContext(entity)) { if (this.EntityChanged != null) { EntityChangedParams arg = new EntityChangedParams(this.Context, entity, propertyName, propertyValue, null, null); if (this.EntityChanged(arg)) { return; } } if (this.IsContextTrackingEntity(entity)) { this.Context.UpdateObject(entity); } } } }
public void RemoveNonTrackedEntities() { foreach (object obj2 in this.graph.Select(o => BindingEntityInfo.IsEntityType(o.GetType(), this.observer.Context.MaxProtocolVersion) && !this.observer.IsContextTrackingEntity(o))) { this.graph.ClearEdgesForVertex(this.graph.LookupVertex(obj2)); } this.RemoveUnreachableVertices(); }
private bool IsDetachedOrDeletedFromContext(object entity) { Debug.Assert(entity != null, "entity must be provided."); Debug.Assert(BindingEntityInfo.IsEntityType(entity.GetType()), "entity must be an entity with keys."); EntityDescriptor descriptor = this.Context.GetEntityDescriptor(entity); return(descriptor == null || descriptor.State == EntityStates.Deleted); }
public void RemoveNonTrackedEntities() { foreach (var entity in this.graph.Select(o => BindingEntityInfo.IsEntityType(o.GetType()) && !this.observer.IsContextTrackingEntity(o))) { this.graph.ClearEdgesForVertex(this.graph.LookupVertex(entity)); } this.RemoveUnreachableVertices(); }
private void ValidateDataServiceCollectionItem(object target) { if (target == null) { throw new InvalidOperationException(Strings.DataBinding_BindingOperation_ArrayItemNull("Remove")); } if (!BindingEntityInfo.IsEntityType(target.GetType(), this.Context.MaxProtocolVersion)) { throw new InvalidOperationException(Strings.DataBinding_BindingOperation_ArrayItemNotEntity("Remove")); } }
private bool IsContextTrackingLink(object source, string sourceProperty, object target) { Debug.Assert(source != null, "source entity must be provided."); Debug.Assert(BindingEntityInfo.IsEntityType(source.GetType()), "source must be an entity with keys."); Debug.Assert(!String.IsNullOrEmpty(sourceProperty), "sourceProperty must be provided."); Debug.Assert(target != null, "target entity must be provided."); Debug.Assert(BindingEntityInfo.IsEntityType(target.GetType()), "target must be an entity with keys."); return(this.Context.GetLinkDescriptor(source, sourceProperty, target) != default(LinkDescriptor)); }
private void ValidateCollectionItem(object target) { if (target == null) { throw new InvalidOperationException(Strings.DataBinding_BindingOperation_ArrayItemNull("Remove")); } if (!BindingEntityInfo.IsEntityType(target.GetType())) { throw new InvalidOperationException(Strings.DataBinding_BindingOperation_ArrayItemNotEntity("Remove")); } }
/// <summary> /// Handle changes to an entity object tracked by the BindingObserver /// </summary> /// <param name="entity">The entity object that has changed.</param> /// <param name="propertyName">The property of the target entity object that has changed.</param> /// <param name="propertyValue">The value of the changed property of the target object.</param> private void HandleUpdateEntity(object entity, string propertyName, object propertyValue) { Debug.Assert(!this.AttachBehavior || this.Context.ApplyingChanges, "Entity updates must not happen during Attach or construction phases, deserialization case is the exception."); if (this.Context.ApplyingChanges) { return; } // For complex types, we will perform notification and update on the closest ancestor entity using the farthest ancestor complex property. if (!BindingEntityInfo.IsEntityType(entity.GetType())) { this.bindingGraph.GetAncestorEntityForComplexProperty(ref entity, ref propertyName, ref propertyValue); } Debug.Assert(entity != null, "entity must be provided for update operations."); Debug.Assert(BindingEntityInfo.IsEntityType(entity.GetType()), "entity must be an entity with keys."); Debug.Assert(!String.IsNullOrEmpty(propertyName) || propertyValue == null, "When propertyName is null no propertyValue should be provided."); // Do not handle update for detached and deleted entities. if (this.IsDetachedOrDeletedFromContext(entity)) { return; } // First give the user code a chance to handle Update operation. if (this.EntityChanged != null) { EntityChangedParams args = new EntityChangedParams( this.Context, entity, propertyName, propertyValue, null, null); if (this.EntityChanged(args)) { return; } } // Default implementation. // The user callback code could detach the entity. if (this.IsContextTrackingEntity(entity)) { // Let UpdateObject check the state of the entity. this.Context.UpdateObject(entity); } }
internal static string GetEntitySet( object target, string targetEntitySet) { Debug.Assert(target != null, "Argument 'target' cannot be null."); Debug.Assert(BindingEntityInfo.IsEntityType(target.GetType()), "Argument 'target' must be an entity type."); if (!String.IsNullOrEmpty(targetEntitySet)) { return(targetEntitySet); } else { return(BindingEntityInfo.GetEntitySetAttribute(target.GetType())); } }
private void OnAddToDataServiceCollection(NotifyCollectionChangedEventArgs eventArgs, object source, string sourceProperty, string targetEntitySet, object collection) { if (eventArgs.NewItems != null) { foreach (object obj2 in eventArgs.NewItems) { if (obj2 == null) { throw new InvalidOperationException(Strings.DataBinding_BindingOperation_ArrayItemNull("Add")); } if (!BindingEntityInfo.IsEntityType(obj2.GetType(), this.Context.MaxProtocolVersion)) { throw new InvalidOperationException(Strings.DataBinding_BindingOperation_ArrayItemNotEntity("Add")); } this.bindingGraph.AddEntity(source, sourceProperty, obj2, targetEntitySet, collection); } } }
private void HandleUpdateEntity(object entity, string propertyName, object propertyValue) { Debug.Assert(!this.AttachBehavior || this.Context.ApplyingChanges, "Entity updates must not happen during Attach or construction phases, deserialization case is the exception."); if (this.Context.ApplyingChanges) { return; } if (!BindingEntityInfo.IsEntityType(entity.GetType())) { this.bindingGraph.GetAncestorEntityForComplexProperty(ref entity, ref propertyName, ref propertyValue); } Debug.Assert(entity != null, "entity must be provided for update operations."); Debug.Assert(BindingEntityInfo.IsEntityType(entity.GetType()), "entity must be an entity with keys."); Debug.Assert(!String.IsNullOrEmpty(propertyName) || propertyValue == null, "When propertyName is null no propertyValue should be provided."); if (this.IsDetachedOrDeletedFromContext(entity)) { return; } if (this.EntityChanged != null) { EntityChangedParams args = new EntityChangedParams( this.Context, entity, propertyName, propertyValue, null, null); if (this.EntityChanged(args)) { return; } } if (this.IsContextTrackingEntity(entity)) { this.Context.UpdateObject(entity); } }
internal void StartTracking <T>(DataServiceCollection <T> collection, string collectionEntitySet) { Debug.Assert(collection != null, "Only constructed collections are tracked."); if (!BindingEntityInfo.IsEntityType(typeof(T))) { throw new ArgumentException(Strings.DataBinding_DataServiceCollectionArgumentMustHaveEntityType(typeof(T))); } try { this.AttachBehavior = true; this.bindingGraph.AddCollection(null, null, collection, collectionEntitySet); } finally { this.AttachBehavior = false; } }
private void StartTracking(DataServiceContext context, IEnumerable <T> items, string entitySet, Func <EntityChangedParams, bool> entityChanged, Func <EntityCollectionChangedParams, bool> collectionChanged) { if (!BindingEntityInfo.IsEntityType(typeof(T), context.MaxProtocolVersion)) { throw new ArgumentException(Strings.DataBinding_DataServiceCollectionArgumentMustHaveEntityType(typeof(T))); } this.observer = new BindingObserver(context, entityChanged, collectionChanged); if (items != null) { try { this.InternalLoadCollection(items); } catch { this.observer = null; throw; } } this.observer.StartTracking <T>((DataServiceCollection <T>) this, entitySet); this.rootCollection = true; }
/// <summary>Start tracking the specified DataServiceCollection.</summary> /// <typeparam name="T">An entity type.</typeparam> /// <param name="collection">An DataServiceCollection.</param> /// <param name="collectionEntitySet">The entity set of the elements in <paramref name="collection"/>.</param> internal void StartTracking <T>(DataServiceCollection <T> collection, string collectionEntitySet) { Debug.Assert(collection != null, "Only constructed collections are tracked."); // Verify that T corresponds to an entity type. if (!BindingEntityInfo.IsEntityType(typeof(T))) { throw new ArgumentException(Strings.DataBinding_DataServiceCollectionArgumentMustHaveEntityType(typeof(T))); } try { this.AttachBehavior = true; // Recursively traverse the entire object graph under the root collection. this.bindingGraph.AddCollection(null, null, collection, collectionEntitySet); } finally { this.AttachBehavior = false; } }
/// <summary> /// Get the entity set name for the target entity object. /// </summary> /// <param name="target">An entity object.</param> /// <param name="targetEntitySet">The 'currently known' entity set name for the target object.</param> /// <returns>The entity set name for the target object.</returns> /// <remarks> /// Allow user code to provide the entity set name. If user code does not provide the entity set name, then /// this method will get the entity set name from the value of the EntitySetAttribute. /// The 'currently known' entity set name for top level collections can be provided through OEC constructor /// </remarks> internal static string GetEntitySet( object target, string targetEntitySet) { Debug.Assert(target != null, "Argument 'target' cannot be null."); Debug.Assert(BindingEntityInfo.IsEntityType(target.GetType()), "Argument 'target' must be an entity type."); // Here's the rules in order of priority for resolving entity set name // 1. EntitySet name passed in the constructor or extension methods of DataServiceCollection // 2. EntitySet name specified in the EntitySet attribute by the code gen. {Remember this attribute is // not generated in case of MEST) if (!String.IsNullOrEmpty(targetEntitySet)) { return(targetEntitySet); } else { // If there is not a 'currently known' entity set name to validate against, then there must be // EntitySet attribute on the entity type return(BindingEntityInfo.GetEntitySetAttribute(target.GetType())); } }
/// <summary>Handle Adds to a tracked DataServiceCollection. Perform operations on context to reflect the changes.</summary> /// <param name="source">The source object that reference the target object through a navigation property.</param> /// <param name="sourceProperty">The navigation property in the source object that reference the target object.</param> /// <param name="sourceEntitySet">The entity set of the source object.</param> /// <param name="collection">The collection containing the target object.</param> /// <param name="target">The target entity to attach.</param> /// <param name="targetEntitySet">The entity set name of the target object.</param> internal void HandleAddEntity( object source, string sourceProperty, string sourceEntitySet, ICollection collection, object target, string targetEntitySet) { if (this.Context.ApplyingChanges) { return; } Debug.Assert( (source == null && sourceProperty == null) || (source != null && !String.IsNullOrEmpty(sourceProperty)), "source and sourceProperty should either both be present or both be absent."); Debug.Assert(target != null, "target must be provided by the caller."); Debug.Assert(BindingEntityInfo.IsEntityType(target.GetType()), "target must be an entity type."); // Do not handle add for already detached and deleted entities. if (source != null && this.IsDetachedOrDeletedFromContext(source)) { return; } // Do we need an operation on context to handle the Add operation. EntityDescriptor targetDescriptor = this.Context.GetEntityDescriptor(target); // Following are the conditions where context operation is required: // 1. Not a call to Load or constructions i.e. we have Add behavior and not Attach behavior // 2. Target entity is not being tracked // 3. Target is being tracked but there is no link between the source and target entity and target is in non-deleted state bool contextOperationRequired = !this.AttachBehavior && (targetDescriptor == null || (source != null && !this.IsContextTrackingLink(source, sourceProperty, target) && targetDescriptor.State != EntityStates.Deleted)); if (contextOperationRequired) { // First give the user code a chance to handle Add operation. if (this.CollectionChanged != null) { EntityCollectionChangedParams args = new EntityCollectionChangedParams( this.Context, source, sourceProperty, sourceEntitySet, collection, target, targetEntitySet, NotifyCollectionChangedAction.Add); if (this.CollectionChanged(args)) { return; } } } // The user callback code could detach the source. if (source != null && this.IsDetachedOrDeletedFromContext(source)) { throw new InvalidOperationException(Strings.DataBinding_BindingOperation_DetachedSource); } // Default implementation. targetDescriptor = this.Context.GetEntityDescriptor(target); if (source != null) { if (this.AttachBehavior) { // If the target entity is not being currently tracked, we attach both the // entity and the link between source and target entity. if (targetDescriptor == null) { BindingUtils.ValidateEntitySetName(targetEntitySet, target); this.Context.AttachTo(targetEntitySet, target); this.Context.AttachLink(source, sourceProperty, target); } else if (targetDescriptor.State != EntityStates.Deleted && !this.IsContextTrackingLink(source, sourceProperty, target)) { // If the target is already being tracked, then we attach the link if it // does not already exist between the source and target entities and the // target entity is not already in Deleted state. this.Context.AttachLink(source, sourceProperty, target); } } else { // The target will be added and link from source to target will get established in the code // below. Note that if there is already target present then we just try to establish the link // however, if the link is also already established then we don't do anything. if (targetDescriptor == null) { // If the entity is not tracked, that means the entity needs to // be added to the context. We need to call AddRelatedObject, // which adds via the parent (for e.g. POST Customers(0)/Orders). this.Context.AddRelatedObject(source, sourceProperty, target); } else if (targetDescriptor.State != EntityStates.Deleted && !this.IsContextTrackingLink(source, sourceProperty, target)) { // If the entity is already tracked, then we just add the link. // However, we would not do it if the target entity is already // in a Deleted state. this.Context.AddLink(source, sourceProperty, target); } } } else if (targetDescriptor == null) { // The source is null when the DataServiceCollection is the root collection. BindingUtils.ValidateEntitySetName(targetEntitySet, target); if (this.AttachBehavior) { // Attach the target entity. this.Context.AttachTo(targetEntitySet, target); } else { // Add the target entity. this.Context.AddObject(targetEntitySet, target); } } }
private static BindingEntityInfoPerType GetBindingEntityInfoFor(Type entityType) { BindingEntityInfoPerType bindingEntityInfo; metadataCacheLock.EnterReadLock(); try { if (bindingEntityInfos.TryGetValue(entityType, out bindingEntityInfo)) { return(bindingEntityInfo); } } finally { metadataCacheLock.ExitReadLock(); } bindingEntityInfo = new BindingEntityInfoPerType(); object[] attributes = entityType.GetCustomAttributes(typeof(EntitySetAttribute), true); bindingEntityInfo.EntitySet = (attributes != null && attributes.Length == 1) ? ((EntitySetAttribute)attributes[0]).EntitySet : null; bindingEntityInfo.ClientType = ClientType.Create(entityType); foreach (ClientType.ClientProperty p in bindingEntityInfo.ClientType.Properties) { BindingPropertyInfo bpi = null; Type propertyType = p.PropertyType; if (p.CollectionType != null) { if (BindingEntityInfo.IsDataServiceCollection(propertyType)) { bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindCollection }; } } else if (BindingEntityInfo.IsEntityType(propertyType)) { bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindEntity }; } else if (BindingEntityInfo.CanBeComplexProperty(p)) { bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindComplex }; } if (bpi != null) { bpi.PropertyInfo = p; if (bindingEntityInfo.ClientType.IsEntityType || bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindComplex) { bindingEntityInfo.ObservableProperties.Add(bpi); } } } metadataCacheLock.EnterWriteLock(); try { if (!bindingEntityInfos.ContainsKey(entityType)) { bindingEntityInfos[entityType] = bindingEntityInfo; } } finally { metadataCacheLock.ExitWriteLock(); } return(bindingEntityInfo); }
/// <summary>Handle changes to navigation properties of a tracked entity. Perform operations on context to reflect the changes.</summary> /// <param name="source">The source object that reference the target object through a navigation property.</param> /// <param name="sourceProperty">The navigation property in the source object that reference the target object.</param> /// <param name="sourceEntitySet">The entity set of the source object.</param> /// <param name="target">The target entity.</param> /// <param name="targetEntitySet">The entity set name of the target object.</param> internal void HandleUpdateEntityReference( object source, string sourceProperty, string sourceEntitySet, object target, string targetEntitySet) { if (this.Context.ApplyingChanges) { return; } Debug.Assert(source != null, "source can not be null for update operations."); Debug.Assert(BindingEntityInfo.IsEntityType(source.GetType()), "source must be an entity with keys."); Debug.Assert(!String.IsNullOrEmpty(sourceProperty), "sourceProperty must be a non-empty string for update operations."); Debug.Assert(!String.IsNullOrEmpty(sourceEntitySet), "sourceEntitySet must be non-empty string for update operation."); // Do not handle update for detached and deleted entities. if (this.IsDetachedOrDeletedFromContext(source)) { return; } // Do we need an operation on context to handle the Update operation. EntityDescriptor targetDescriptor = target != null?this.Context.GetEntityDescriptor(target) : null; // Following are the conditions where context operation is required: // 1. Not a call to Load or constructions i.e. we have Add behavior and not Attach behavior // 2. Target entity is not being tracked // 3. Target is being tracked but there is no link between the source and target entity bool contextOperationRequired = !this.AttachBehavior && (targetDescriptor == null || !this.IsContextTrackingLink(source, sourceProperty, target)); if (contextOperationRequired) { // First give the user code a chance to handle Update link operation. if (this.EntityChanged != null) { EntityChangedParams args = new EntityChangedParams( this.Context, source, sourceProperty, target, sourceEntitySet, targetEntitySet); if (this.EntityChanged(args)) { return; } } } // The user callback code could detach the source. if (this.IsDetachedOrDeletedFromContext(source)) { throw new InvalidOperationException(Strings.DataBinding_BindingOperation_DetachedSource); } // Default implementation. targetDescriptor = target != null?this.Context.GetEntityDescriptor(target) : null; if (target != null) { if (targetDescriptor == null) { // If the entity set name is not known, then we must throw since we need to know the // entity set in order to add/attach the referenced object to it's entity set. BindingUtils.ValidateEntitySetName(targetEntitySet, target); if (this.AttachBehavior) { this.Context.AttachTo(targetEntitySet, target); } else { this.Context.AddObject(targetEntitySet, target); } targetDescriptor = this.Context.GetEntityDescriptor(target); } // if the entity is already tracked, then just set/attach the link. However, do // not try to attach the link if the target is in Deleted state. if (!this.IsContextTrackingLink(source, sourceProperty, target)) { if (this.AttachBehavior) { if (targetDescriptor.State != EntityStates.Deleted) { this.Context.AttachLink(source, sourceProperty, target); } } else { this.Context.SetLink(source, sourceProperty, target); } } } else { Debug.Assert(!this.AttachBehavior, "During attach operations we must never perform operations for null values."); // The target could be null in which case we just need to set the link to null. this.Context.SetLink(source, sourceProperty, null); } }
/// <summary>Handle Deletes from a tracked DataServiceCollection. Perform operations on context to reflect the changes.</summary> /// <param name="source">The source object that reference the target object through a navigation property.</param> /// <param name="sourceProperty">The navigation property in the source object that reference the target object.</param> /// <param name="sourceEntitySet">The entity set of the source object.</param> /// <param name="collection">The collection containing the target object.</param> /// <param name="target">The target entity.</param> /// <param name="targetEntitySet">The entity set name of the target object.</param> internal void HandleDeleteEntity( object source, string sourceProperty, string sourceEntitySet, ICollection collection, object target, string targetEntitySet) { if (this.Context.ApplyingChanges) { return; } Debug.Assert( (source == null && sourceProperty == null) || (source != null && !String.IsNullOrEmpty(sourceProperty)), "source and sourceProperty should either both be present or both be absent."); Debug.Assert(target != null, "target must be provided by the caller."); Debug.Assert(BindingEntityInfo.IsEntityType(target.GetType()), "target must be an entity type."); Debug.Assert(!this.AttachBehavior, "AttachBehavior is only allowed during Construction and Load when this method should never be entered."); // Do not handle delete for already detached and deleted entities. if (source != null && this.IsDetachedOrDeletedFromContext(source)) { return; } // Do we need an operation on context to handle the Delete operation. // Detach behavior is special because it is only applicable in Clear // cases, where we don't callback users for detach nofications. bool contextOperationRequired = this.IsContextTrackingEntity(target) && !this.DetachBehavior; if (contextOperationRequired) { // First give the user code a chance to handle Delete operation. if (this.CollectionChanged != null) { EntityCollectionChangedParams args = new EntityCollectionChangedParams( this.Context, source, sourceProperty, sourceEntitySet, collection, target, targetEntitySet, NotifyCollectionChangedAction.Remove); if (this.CollectionChanged(args)) { return; } } } // The user callback code could detach the source. if (source != null && !this.IsContextTrackingEntity(source)) { throw new InvalidOperationException(Strings.DataBinding_BindingOperation_DetachedSource); } // Default implementation. // Remove the entity from the context if it is currently being tracked. if (this.IsContextTrackingEntity(target)) { if (this.DetachBehavior) { this.Context.Detach(target); } else { this.Context.DeleteObject(target); } } }
/// <summary>Obtain binding info corresponding to a given type</summary> /// <param name="entityType">Type for which to obtain information</param> /// <returns>Info about the <paramref name="entityType"/></returns> private static BindingEntityInfoPerType GetBindingEntityInfoFor(Type entityType) { BindingEntityInfoPerType bindingEntityInfo; metadataCacheLock.EnterReadLock(); try { if (bindingEntityInfos.TryGetValue(entityType, out bindingEntityInfo)) { return(bindingEntityInfo); } } finally { metadataCacheLock.ExitReadLock(); } bindingEntityInfo = new BindingEntityInfoPerType(); // Try to get the entity set name from the EntitySetAttribute attributes. In order to make the // inheritance work, we need to look at the attributes declared in the base types also. object[] attributes = entityType.GetCustomAttributes(typeof(EntitySetAttribute), true); // There must be exactly one (unambiguous) EntitySetAttribute attribute. bindingEntityInfo.EntitySet = (attributes != null && attributes.Length == 1) ? ((EntitySetAttribute)attributes[0]).EntitySet : null; bindingEntityInfo.ClientType = ClientType.Create(entityType); foreach (ClientType.ClientProperty p in bindingEntityInfo.ClientType.Properties) { BindingPropertyInfo bpi = null; Type propertyType = p.PropertyType; if (p.CollectionType != null) { if (BindingEntityInfo.IsDataServiceCollection(propertyType)) { bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindCollection }; } } else if (BindingEntityInfo.IsEntityType(propertyType)) { bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindEntity }; } else if (BindingEntityInfo.CanBeComplexProperty(p)) { // Add complex types and nothing else. bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindComplex }; } if (bpi != null) { bpi.PropertyInfo = p; // For complex types only treat complex typed properties as observable, we are not going to observer entity typed or primitive properties. if (bindingEntityInfo.ClientType.IsEntityType || bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindComplex) { bindingEntityInfo.ObservableProperties.Add(bpi); } } } metadataCacheLock.EnterWriteLock(); try { if (!bindingEntityInfos.ContainsKey(entityType)) { bindingEntityInfos[entityType] = bindingEntityInfo; } } finally { metadataCacheLock.ExitWriteLock(); } return(bindingEntityInfo); }
/// <summary>Obtain binding info corresponding to a given type</summary> /// <param name="entityType">Type for which to obtain information</param> /// <param name="model">The client model.</param> /// <returns>Info about the <paramref name="entityType"/></returns> private static BindingEntityInfoPerType GetBindingEntityInfoFor(Type entityType, ClientEdmModel model) { BindingEntityInfoPerType bindingEntityInfo; metadataCacheLock.EnterReadLock(); try { if (bindingEntityInfos.TryGetValue(entityType, out bindingEntityInfo)) { return(bindingEntityInfo); } } finally { metadataCacheLock.ExitReadLock(); } bindingEntityInfo = new BindingEntityInfoPerType(); // Try to get the entity set name from the EntitySetAttribute attributes. In order to make the // inheritance work, we need to look at the attributes declared in the base types also. // EntitySetAttribute does not allow multiples, so there can be at most 1 instance on the type. EntitySetAttribute entitySetAttribute = (EntitySetAttribute)entityType.GetCustomAttributes(typeof(EntitySetAttribute), true).SingleOrDefault(); // There must be exactly one (unambiguous) EntitySetAttribute attribute. bindingEntityInfo.EntitySet = entitySetAttribute != null ? entitySetAttribute.EntitySet : null; bindingEntityInfo.ClientType = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityType)); foreach (ClientPropertyAnnotation p in bindingEntityInfo.ClientType.Properties()) { BindingPropertyInfo bpi = null; Type propertyType = p.PropertyType; if (p.IsStreamLinkProperty) { // DataServiceStreamLink is not mutable externally // It implements INPC to notify controls about our updates // We should ignore its events since we are the only one updating it. continue; } else if (p.IsPrimitiveOrComplexCollection) { Debug.Assert(!BindingEntityInfo.IsDataServiceCollection(propertyType, model), "DataServiceCollection cannot be the type that backs collections of primitives or complex types."); bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindPrimitiveOrComplexCollection }; } else if (p.IsEntityCollection) { if (BindingEntityInfo.IsDataServiceCollection(propertyType, model)) { bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindDataServiceCollection }; } } else if (BindingEntityInfo.IsEntityType(propertyType, model)) { bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindEntity }; } else if (BindingEntityInfo.CanBeComplexType(propertyType)) { // Add complex types and nothing else. Debug.Assert(!p.IsKnownType, "Known types do not implement INotifyPropertyChanged."); bpi = new BindingPropertyInfo { PropertyKind = BindingPropertyKind.BindingPropertyKindComplex }; } if (bpi != null) { bpi.PropertyInfo = p; // For entity types, all of the above types of properties are interesting. // For complex types, only observe collections and complex type properties. if (bindingEntityInfo.ClientType.IsEntityType || bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindComplex || bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindPrimitiveOrComplexCollection) { bindingEntityInfo.ObservableProperties.Add(bpi); } } } metadataCacheLock.EnterWriteLock(); try { if (!bindingEntityInfos.ContainsKey(entityType)) { bindingEntityInfos[entityType] = bindingEntityInfo; } } finally { metadataCacheLock.ExitWriteLock(); } return(bindingEntityInfo); }
internal void HandleUpdateEntityReference( object source, string sourceProperty, string sourceEntitySet, object target, string targetEntitySet) { if (this.Context.ApplyingChanges) { return; } Debug.Assert(source != null, "source can not be null for update operations."); Debug.Assert(BindingEntityInfo.IsEntityType(source.GetType()), "source must be an entity with keys."); Debug.Assert(!String.IsNullOrEmpty(sourceProperty), "sourceProperty must be a non-empty string for update operations."); Debug.Assert(!String.IsNullOrEmpty(sourceEntitySet), "sourceEntitySet must be non-empty string for update operation."); if (this.IsDetachedOrDeletedFromContext(source)) { return; } EntityDescriptor targetDescriptor = target != null?this.Context.GetEntityDescriptor(target) : null; bool contextOperationRequired = !this.AttachBehavior && (targetDescriptor == null || !this.IsContextTrackingLink(source, sourceProperty, target)); if (contextOperationRequired) { if (this.EntityChanged != null) { EntityChangedParams args = new EntityChangedParams( this.Context, source, sourceProperty, target, sourceEntitySet, targetEntitySet); if (this.EntityChanged(args)) { return; } } } if (this.IsDetachedOrDeletedFromContext(source)) { throw new InvalidOperationException(Strings.DataBinding_BindingOperation_DetachedSource); } targetDescriptor = target != null?this.Context.GetEntityDescriptor(target) : null; if (target != null) { if (targetDescriptor == null) { BindingUtils.ValidateEntitySetName(targetEntitySet, target); if (this.AttachBehavior) { this.Context.AttachTo(targetEntitySet, target); } else { this.Context.AddObject(targetEntitySet, target); } targetDescriptor = this.Context.GetEntityDescriptor(target); } if (!this.IsContextTrackingLink(source, sourceProperty, target)) { if (this.AttachBehavior) { if (targetDescriptor.State != EntityStates.Deleted) { this.Context.AttachLink(source, sourceProperty, target); } } else { this.Context.SetLink(source, sourceProperty, target); } } } else { Debug.Assert(!this.AttachBehavior, "During attach operations we must never perform operations for null values."); this.Context.SetLink(source, sourceProperty, null); } }
internal void HandleDeleteEntity( object source, string sourceProperty, string sourceEntitySet, ICollection collection, object target, string targetEntitySet) { if (this.Context.ApplyingChanges) { return; } Debug.Assert( (source == null && sourceProperty == null) || (source != null && !String.IsNullOrEmpty(sourceProperty)), "source and sourceProperty should either both be present or both be absent."); Debug.Assert(target != null, "target must be provided by the caller."); Debug.Assert(BindingEntityInfo.IsEntityType(target.GetType()), "target must be an entity type."); Debug.Assert(!this.AttachBehavior, "AttachBehavior is only allowed during Construction and Load when this method should never be entered."); if (source != null && this.IsDetachedOrDeletedFromContext(source)) { return; } bool contextOperationRequired = this.IsContextTrackingEntity(target) && !this.DetachBehavior; if (contextOperationRequired) { if (this.CollectionChanged != null) { EntityCollectionChangedParams args = new EntityCollectionChangedParams( this.Context, source, sourceProperty, sourceEntitySet, collection, target, targetEntitySet, NotifyCollectionChangedAction.Remove); if (this.CollectionChanged(args)) { return; } } } if (source != null && !this.IsContextTrackingEntity(source)) { throw new InvalidOperationException(Strings.DataBinding_BindingOperation_DetachedSource); } if (this.IsContextTrackingEntity(target)) { if (this.DetachBehavior) { this.Context.Detach(target); } else { this.Context.DeleteObject(target); } } }
internal void HandleAddEntity( object source, string sourceProperty, string sourceEntitySet, ICollection collection, object target, string targetEntitySet) { if (this.Context.ApplyingChanges) { return; } Debug.Assert( (source == null && sourceProperty == null) || (source != null && !String.IsNullOrEmpty(sourceProperty)), "source and sourceProperty should either both be present or both be absent."); Debug.Assert(target != null, "target must be provided by the caller."); Debug.Assert(BindingEntityInfo.IsEntityType(target.GetType()), "target must be an entity type."); if (source != null && this.IsDetachedOrDeletedFromContext(source)) { return; } EntityDescriptor targetDescriptor = this.Context.GetEntityDescriptor(target); bool contextOperationRequired = !this.AttachBehavior && (targetDescriptor == null || (source != null && !this.IsContextTrackingLink(source, sourceProperty, target) && targetDescriptor.State != EntityStates.Deleted)); if (contextOperationRequired) { if (this.CollectionChanged != null) { EntityCollectionChangedParams args = new EntityCollectionChangedParams( this.Context, source, sourceProperty, sourceEntitySet, collection, target, targetEntitySet, NotifyCollectionChangedAction.Add); if (this.CollectionChanged(args)) { return; } } } if (source != null && this.IsDetachedOrDeletedFromContext(source)) { throw new InvalidOperationException(Strings.DataBinding_BindingOperation_DetachedSource); } targetDescriptor = this.Context.GetEntityDescriptor(target); if (source != null) { if (this.AttachBehavior) { if (targetDescriptor == null) { BindingUtils.ValidateEntitySetName(targetEntitySet, target); this.Context.AttachTo(targetEntitySet, target); this.Context.AttachLink(source, sourceProperty, target); } else if (targetDescriptor.State != EntityStates.Deleted && !this.IsContextTrackingLink(source, sourceProperty, target)) { this.Context.AttachLink(source, sourceProperty, target); } } else { if (targetDescriptor == null) { this.Context.AddRelatedObject(source, sourceProperty, target); } else if (targetDescriptor.State != EntityStates.Deleted && !this.IsContextTrackingLink(source, sourceProperty, target)) { this.Context.AddLink(source, sourceProperty, target); } } } else if (targetDescriptor == null) { BindingUtils.ValidateEntitySetName(targetEntitySet, target); if (this.AttachBehavior) { this.Context.AttachTo(targetEntitySet, target); } else { this.Context.AddObject(targetEntitySet, target); } } }