BindingObserver.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DataWeb / Client / System / Data / Services / Client / Binding / BindingObserver.cs / 1625574 / BindingObserver.cs

                            //---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//  
//   BindingObserver class
//  
// 
//---------------------------------------------------------------------
 
namespace System.Data.Services.Client
{
#region Namespaces
    using System.Collections; 
    using System.Collections.Generic;
    using System.Collections.Specialized; 
    using System.ComponentModel; 
    using System.Diagnostics;
    using System.Linq; 
    using System.Reflection;
#endregion

    /// The BindingObserver class 
    internal sealed class BindingObserver
    { 
        #region Fields 

        ///  
        /// The BindingGraph maps objects tracked by the DataServiceContext to vertices in a
        /// graph used to manage the information needed for data binding. The objects tracked
        /// by the BindingGraph are entities, complex types and DataServiceCollections.
        ///  
        private BindingGraph bindingGraph;
 
        #endregion 

        #region Constructor 

        /// Constructor
        /// The DataServiceContext associated with the BindingObserver.
        /// EntityChanged delegate. 
        /// EntityCollectionChanged delegate.
        internal BindingObserver(DataServiceContext context, Func entityChanged, Func collectionChanged) 
        { 
            Debug.Assert(context != null, "Must have been validated during DataServiceCollection construction.");
            this.Context = context; 
            this.Context.ChangesSaved += this.OnChangesSaved;

            this.EntityChanged = entityChanged;
            this.CollectionChanged = collectionChanged; 

            this.bindingGraph = new BindingGraph(this); 
        } 

        #endregion 

        #region Properties

        /// The DataServiceContext associated with the BindingObserver. 
        internal DataServiceContext Context
        { 
            get; 
            private set;
        } 

        /// The behavior of add operations should be Attach or Add on the context.
        internal bool AttachBehavior
        { 
            get;
            set; 
        } 

        /// The behavior of remove operations should be Detach on the context. 
        internal bool DetachBehavior
        {
            get;
            set; 
        }
 
        ///  
        /// Callback invoked when a property of an entity object tracked by the BindingObserver has changed.
        ///  
        /// 
        /// Entity objects tracked by the BindingObserver implement INotifyPropertyChanged. Events of this type
        /// flow throw the EntityChangedParams. If this callback is not implemented by user code, or the user code
        /// implementation returns false, the BindingObserver executes a default implementation for the callback. 
        /// 
        internal Func EntityChanged 
        { 
            get;
            private set; 
        }

        /// 
        /// Callback invoked when an DataServiceCollection tracked by the BindingObserver has changed. 
        /// 
        ///  
        /// DataServiceCollection objects tracked by the BindingObserver implement INotifyCollectionChanged. 
        /// Events of this type flow throw the EntityCollectionChanged callback. If this callback is not
        /// implemented by user code, or the user code implementation returns false, the BindingObserver executes 
        /// a default implementation for the callback.
        /// 
        internal Func CollectionChanged
        { 
            get;
            private set; 
        } 

        #endregion 

        #region Methods

        /// Start tracking the specified DataServiceCollection. 
        /// An entity type.
        /// An DataServiceCollection. 
        /// The entity set of the elements in . 
        internal void StartTracking(DataServiceCollection 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; 
            }
        }

        /// Stop tracking the root DataServiceCollection associated with the observer. 
        internal void StopTracking()
        { 
            this.bindingGraph.Reset(); 

            this.Context.ChangesSaved -= this.OnChangesSaved; 
        }

#if ASTORIA_LIGHT
        internal bool LookupParent(DataServiceCollection collection, out object parentEntity, out string parentProperty) 
        {
            string sourceEntitySet; 
            string targetEntitySet; 
            this.bindingGraph.GetEntityCollectionInfo(collection, out parentEntity, out parentProperty, out sourceEntitySet, out targetEntitySet);
 
            return parentEntity != null;
        }
#endif
 
        /// Handle changes to tracked entity.
        /// The entity that raised the event. 
        /// Information about the event such as changed property name. 
        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)]
        internal void OnPropertyChanged(object source, PropertyChangedEventArgs eventArgs) 
        {
            Util.CheckArgumentNull(source, "source");
            Util.CheckArgumentNull(eventArgs, "eventArgs");
 
#if DEBUG
            Debug.Assert(this.bindingGraph.IsTracking(source), "Entity must be part of the graph if it has the event notification registered."); 
#endif 
            string sourceProperty = eventArgs.PropertyName;
 
            // When sourceProperty is null, it is assumed that all properties for the object changed
            // As a result, we should be performing an UpdateObject operation on the context.
            if (String.IsNullOrEmpty(sourceProperty))
            { 
                this.HandleUpdateEntity(
                        source, 
                        null, 
                        null);
            } 
            else
            {
                BindingEntityInfo.BindingPropertyInfo bpi;
 
                // Get the new value for the changed property.
                object sourcePropertyValue = BindingEntityInfo.GetPropertyValue(source, sourceProperty, out bpi); 
 
                // Check if it is an interesting property e.g. collection, entity reference or complex type.
                if (bpi != null) 
                {
                    // Disconnect the edge between source and original source property value.
                    this.bindingGraph.RemoveRelation(source, sourceProperty);
 
                    switch (bpi.PropertyKind)
                    { 
                        case BindingPropertyKind.BindingPropertyKindCollection: 
                            // If collection is already being tracked by the graph we can not have > 1 links to it.
                            if (sourcePropertyValue != null) 
                            {
                                // Make sure that there is no observer on the input collection property.
                                try
                                { 
                                    typeof(BindingUtils)
                                        .GetMethod("VerifyObserverNotPresent", BindingFlags.NonPublic | BindingFlags.Static) 
                                        .MakeGenericMethod(bpi.PropertyInfo.CollectionType) 
                                        .Invoke(null, new object[] { sourcePropertyValue, sourceProperty, source.GetType() });
                                } 
                                catch (TargetInvocationException tie)
                                {
                                    throw tie.InnerException;
                                } 

                                try 
                                { 
                                    this.AttachBehavior = true;
                                    this.bindingGraph.AddCollection( 
                                            source,
                                            sourceProperty,
                                            sourcePropertyValue,
                                            null); 
                                }
                                finally 
                                { 
                                    this.AttachBehavior = false;
                                } 
                            }

                            break;
 
                        case BindingPropertyKind.BindingPropertyKindEntity:
                            // Add the newly added entity to the graph, or update entity reference. 
                            this.bindingGraph.AddEntity( 
                                    source,
                                    sourceProperty, 
                                    sourcePropertyValue,
                                    null,
                                    source);
                            break; 

                        default: 
                            Debug.Assert(bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindComplex, "Must be complex type if PropertyKind is not entity or collection."); 

                            // Attach the newly assigned complex type object and it's child complex typed objects. 
                            if (sourcePropertyValue != null)
                            {
                                this.bindingGraph.AddComplexProperty(
                                        source, 
                                        sourceProperty,
                                        sourcePropertyValue); 
                            } 

                            this.HandleUpdateEntity( 
                                    source,
                                    sourceProperty,
                                    sourcePropertyValue);
                            break; 
                    }
                } 
                else 
                {
                    // For non-interesting properties i.e. value types or regular collection properties we simply call UpdateObject on the context. 
                    // Note that this code also handles primitive properties of complex typed objects.
                    this.HandleUpdateEntity(
                            source,
                            sourceProperty, 
                            sourcePropertyValue);
                } 
            } 
        }
 
        /// Handle changes to tracked DataServiceCollection.
        /// The DataServiceCollection that raised the event.
        /// Information about the event such as added/removed entities, operation.
        internal void OnCollectionChanged(object collection, NotifyCollectionChangedEventArgs eventArgs) 
        {
            Util.CheckArgumentNull(collection, "collection"); 
            Util.CheckArgumentNull(eventArgs, "eventArgs"); 

            Debug.Assert(BindingEntityInfo.IsDataServiceCollection(collection.GetType()), "We only register this event for DataServiceCollections."); 
#if DEBUG
            Debug.Assert(this.bindingGraph.IsTracking(collection), "Collection must be part of the graph if it has the event notification registered.");
#endif
            object source; 
            string sourceProperty;
            string sourceEntitySet; 
            string targetEntitySet; 

            this.bindingGraph.GetEntityCollectionInfo( 
                    collection,
                    out source,
                    out sourceProperty,
                    out sourceEntitySet, 
                    out targetEntitySet);
 
            switch (eventArgs.Action) 
            {
                case NotifyCollectionChangedAction.Add: 
                    // This event is raised by ObservableCollection.InsertItem.
                    this.OnAddToCollection(
                            eventArgs,
                            source, 
                            sourceProperty,
                            targetEntitySet, 
                            collection); 
                    break;
 
                case NotifyCollectionChangedAction.Remove:
                    // This event is raised by ObservableCollection.RemoveItem.
                    this.OnDeleteFromCollection(
                            eventArgs, 
                            source,
                            sourceProperty, 
                            collection); 
                    break;
 
                case NotifyCollectionChangedAction.Replace:
                    // This event is raised by ObservableCollection.SetItem.
                    this.OnDeleteFromCollection(
                            eventArgs, 
                            source,
                            sourceProperty, 
                            collection); 

                    this.OnAddToCollection( 
                            eventArgs,
                            source,
                            sourceProperty,
                            targetEntitySet, 
                            collection);
                    break; 
 
                case NotifyCollectionChangedAction.Reset:
                    // This event is raised by ObservableCollection.Clear. 
                    if (this.DetachBehavior)
                    {
                        // Detach behavior requires going through each item and detaching it from context.
                        this.RemoveWithDetachCollection(collection); 
                    }
                    else 
                    { 
                        // Non-detach behavior requires only removing vertices of collection from graph.
                        this.bindingGraph.RemoveCollection(collection); 
                    }

                    break;
 
#if !ASTORIA_LIGHT
                case NotifyCollectionChangedAction.Move: 
                    // Do Nothing. Added for completeness. 
                    break;
#endif 

                default:
                    throw new InvalidOperationException(Strings.DataBinding_CollectionChangedUnknownAction(eventArgs.Action));
            } 
        }
 
        /// Handle Adds to a tracked DataServiceCollection. Perform operations on context to reflect the changes. 
        /// The source object that reference the target object through a navigation property.
        /// The navigation property in the source object that reference the target object. 
        /// The entity set of the source object.
        /// The collection containing the target object.
        /// The target entity to attach.
        /// The entity set name of the target object. 
        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);
                } 
            } 
        }
 
        /// Handle Deletes from a tracked DataServiceCollection. Perform operations on context to reflect the changes.
        /// The source object that reference the target object through a navigation property.
        /// The navigation property in the source object that reference the target object.
        /// The entity set of the source object. 
        /// The collection containing the target object.
        /// The target entity. 
        /// The entity set name of the target object. 
        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); 
                }
            } 
        }

        /// Handle changes to navigation properties of a tracked entity. Perform operations on context to reflect the changes.
        /// The source object that reference the target object through a navigation property. 
        /// The navigation property in the source object that reference the target object.
        /// The entity set of the source object. 
        /// The target entity. 
        /// The entity set name of the target object.
        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); 
            } 
        }
 
        /// Determine if the DataServiceContext is tracking the specified entity.
        /// An entity object.
        /// true if the entity is tracked; otherwise false.
        internal bool IsContextTrackingEntity(object entity) 
        {
            Debug.Assert(entity != null, "entity must be provided when checking for context tracking."); 
            return this.Context.GetEntityDescriptor(entity) != default(EntityDescriptor); 
        }
 
        /// 
        /// Handle changes to an entity object tracked by the BindingObserver
        /// 
        /// The entity object that has changed. 
        /// The property of the target entity object that has changed.
        /// The value of the changed property of the target object. 
        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); 
            }
        }

        /// Processes the INotifyCollectionChanged.Add event. 
        /// Event information such as added items.
        /// Parent entity to which collection belongs. 
        /// Parent entity property referring to collection. 
        /// Entity set of the collection.
        /// Collection that changed. 
        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"));
                    } 

                    // Start tracking the target entity and synchronize the context with the Add operation. 
                    this.bindingGraph.AddEntity( 
                            source,
                            sourceProperty, 
                            target,
                            targetEntitySet,
                            collection);
                } 
            }
        } 
 
        /// Processes the INotifyCollectionChanged.Remove event.
        /// Event information such as deleted items. 
        /// Parent entity to which collection belongs.
        /// Parent entity property referring to collection.
        /// Collection that changed.
        private void OnDeleteFromCollection( 
            NotifyCollectionChangedEventArgs eventArgs,
            object source, 
            String sourceProperty, 
            object collection)
        { 
            Debug.Assert(collection != null, "Must have a valid collection from which entities are removed.");
            Debug.Assert(
                (source == null && sourceProperty == null) || (source != null && !String.IsNullOrEmpty(sourceProperty)),
                "source and sourceProperty must both be null or both be non-null."); 

            if (eventArgs.OldItems != null) 
            { 
                this.DeepRemoveCollection(
                        eventArgs.OldItems, 
                        source ?? collection,
                        sourceProperty,
                        this.ValidateCollectionItem);
            } 
        }
 
        /// Removes a collection from the binding graph and detaches each item. 
        /// Collection whose elements are to be removed and detached.
        private void RemoveWithDetachCollection(object collection) 
        {
            Debug.Assert(this.DetachBehavior, "Must be detaching each item in collection.");

            object source = null; 
            string sourceProperty = null;
            string sourceEntitySet = null; 
            string targetEntitySet = null; 

            this.bindingGraph.GetEntityCollectionInfo( 
                    collection,
                    out source,
                    out sourceProperty,
                    out sourceEntitySet, 
                    out targetEntitySet);
 
            this.DeepRemoveCollection( 
                    this.bindingGraph.GetCollectionItems(collection),
                    source ?? collection, 
                    sourceProperty,
                    null);
        }
 
        /// Performs a Deep removal of all entities in a collection.
        /// Collection whose items are removed from binding graph. 
        /// Parent item whose property refer to the  being cleared. 
        /// Property of the  that refers to .
        /// Validation method if any that checks the individual item in  for validity. 
        private void DeepRemoveCollection(IEnumerable collection, object source, string sourceProperty, Action itemValidator)
        {
            foreach (object target in collection)
            { 
                if (itemValidator != null)
                { 
                    itemValidator(target); 
                }
 
                // Accumulate the list of entities to untrack, this includes deep added entities under target.
                List untrackingInfo = new List();

                this.CollectUnTrackingInfo( 
                        target,
                        source, 
                        sourceProperty, 
                        untrackingInfo);
 
                // Stop tracking the collection of entities found by CollectUnTrackingInfo from bottom up in object graph.
                foreach (UnTrackingInfo info in untrackingInfo)
                {
                    this.bindingGraph.Remove( 
                            info.Entity,
                            info.Parent, 
                            info.ParentProperty); 
                }
            } 

            this.bindingGraph.RemoveUnreachableVertices();
        }
 
        /// Handle the DataServiceContext.SaveChanges operation.
        /// DataServiceContext for the observer. 
        /// Information about SaveChanges operation results. 
        private void OnChangesSaved(object sender, SaveChangesEventArgs eventArgs)
        { 
            // Does the response status code have to be checked? SaveChanges throws on failure.
            // DataServiceResponse response = eventArgs.Response;
            this.bindingGraph.RemoveNonTrackedEntities();
        } 

        /// Collects a list of entities that observer is supposed to stop tracking 
        /// Entity being delete along with it's children 
        /// Parent of the 
        /// Property by which  refers to  
        /// List in which entities to be untracked are collected
        private void CollectUnTrackingInfo(
            object currentEntity,
            object parentEntity, 
            string parentProperty,
            IList entitiesToUnTrack) 
        { 
            // We need to delete the child objects first before we delete the parent
            foreach (var ed in this.Context 
                                   .Entities
                                   .Where(x => x.ParentEntity == currentEntity && x.State == EntityStates.Added))
            {
                this.CollectUnTrackingInfo( 
                        ed.Entity,
                        ed.ParentEntity, 
                        ed.ParentPropertyForInsert, 
                        entitiesToUnTrack);
            } 

            entitiesToUnTrack.Add(new UnTrackingInfo
                                  {
                                    Entity = currentEntity, 
                                    Parent = parentEntity,
                                    ParentProperty = parentProperty 
                                  }); 
        }
 
        /// Determine if the DataServiceContext is tracking link between  and .
        /// The source object side of the link.
        /// A property in the source side of the link that references the target.
        /// The target object side of the link. 
        /// True if the link is tracked; otherwise false.
        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); 
        }
 
        /// Checks whether the given entity is in detached or deleted state.
        /// Entity being checked.
        /// true if the entity is detached or deleted, otherwise returns false.
        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;
        }

        /// Entity validator that checks if the  is of entity type. 
        /// Entity being validated.
        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")); 
            } 
        }
 
        #endregion

        /// Information regarding each entity to be untracked
        private class UnTrackingInfo 
        {
            /// Entity to untrack 
            public object Entity 
            {
                get; 
                set;
            }

            /// Parent object of  
            public object Parent
            { 
                get; 
                set;
            } 

            /// Parent object property referring to 
            public string ParentProperty
            { 
                get;
                set; 
            } 
        }
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//  
//   BindingObserver class
//  
// 
//---------------------------------------------------------------------
 
namespace System.Data.Services.Client
{
#region Namespaces
    using System.Collections; 
    using System.Collections.Generic;
    using System.Collections.Specialized; 
    using System.ComponentModel; 
    using System.Diagnostics;
    using System.Linq; 
    using System.Reflection;
#endregion

    /// The BindingObserver class 
    internal sealed class BindingObserver
    { 
        #region Fields 

        ///  
        /// The BindingGraph maps objects tracked by the DataServiceContext to vertices in a
        /// graph used to manage the information needed for data binding. The objects tracked
        /// by the BindingGraph are entities, complex types and DataServiceCollections.
        ///  
        private BindingGraph bindingGraph;
 
        #endregion 

        #region Constructor 

        /// Constructor
        /// The DataServiceContext associated with the BindingObserver.
        /// EntityChanged delegate. 
        /// EntityCollectionChanged delegate.
        internal BindingObserver(DataServiceContext context, Func entityChanged, Func collectionChanged) 
        { 
            Debug.Assert(context != null, "Must have been validated during DataServiceCollection construction.");
            this.Context = context; 
            this.Context.ChangesSaved += this.OnChangesSaved;

            this.EntityChanged = entityChanged;
            this.CollectionChanged = collectionChanged; 

            this.bindingGraph = new BindingGraph(this); 
        } 

        #endregion 

        #region Properties

        /// The DataServiceContext associated with the BindingObserver. 
        internal DataServiceContext Context
        { 
            get; 
            private set;
        } 

        /// The behavior of add operations should be Attach or Add on the context.
        internal bool AttachBehavior
        { 
            get;
            set; 
        } 

        /// The behavior of remove operations should be Detach on the context. 
        internal bool DetachBehavior
        {
            get;
            set; 
        }
 
        ///  
        /// Callback invoked when a property of an entity object tracked by the BindingObserver has changed.
        ///  
        /// 
        /// Entity objects tracked by the BindingObserver implement INotifyPropertyChanged. Events of this type
        /// flow throw the EntityChangedParams. If this callback is not implemented by user code, or the user code
        /// implementation returns false, the BindingObserver executes a default implementation for the callback. 
        /// 
        internal Func EntityChanged 
        { 
            get;
            private set; 
        }

        /// 
        /// Callback invoked when an DataServiceCollection tracked by the BindingObserver has changed. 
        /// 
        ///  
        /// DataServiceCollection objects tracked by the BindingObserver implement INotifyCollectionChanged. 
        /// Events of this type flow throw the EntityCollectionChanged callback. If this callback is not
        /// implemented by user code, or the user code implementation returns false, the BindingObserver executes 
        /// a default implementation for the callback.
        /// 
        internal Func CollectionChanged
        { 
            get;
            private set; 
        } 

        #endregion 

        #region Methods

        /// Start tracking the specified DataServiceCollection. 
        /// An entity type.
        /// An DataServiceCollection. 
        /// The entity set of the elements in . 
        internal void StartTracking(DataServiceCollection 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; 
            }
        }

        /// Stop tracking the root DataServiceCollection associated with the observer. 
        internal void StopTracking()
        { 
            this.bindingGraph.Reset(); 

            this.Context.ChangesSaved -= this.OnChangesSaved; 
        }

#if ASTORIA_LIGHT
        internal bool LookupParent(DataServiceCollection collection, out object parentEntity, out string parentProperty) 
        {
            string sourceEntitySet; 
            string targetEntitySet; 
            this.bindingGraph.GetEntityCollectionInfo(collection, out parentEntity, out parentProperty, out sourceEntitySet, out targetEntitySet);
 
            return parentEntity != null;
        }
#endif
 
        /// Handle changes to tracked entity.
        /// The entity that raised the event. 
        /// Information about the event such as changed property name. 
        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)]
        internal void OnPropertyChanged(object source, PropertyChangedEventArgs eventArgs) 
        {
            Util.CheckArgumentNull(source, "source");
            Util.CheckArgumentNull(eventArgs, "eventArgs");
 
#if DEBUG
            Debug.Assert(this.bindingGraph.IsTracking(source), "Entity must be part of the graph if it has the event notification registered."); 
#endif 
            string sourceProperty = eventArgs.PropertyName;
 
            // When sourceProperty is null, it is assumed that all properties for the object changed
            // As a result, we should be performing an UpdateObject operation on the context.
            if (String.IsNullOrEmpty(sourceProperty))
            { 
                this.HandleUpdateEntity(
                        source, 
                        null, 
                        null);
            } 
            else
            {
                BindingEntityInfo.BindingPropertyInfo bpi;
 
                // Get the new value for the changed property.
                object sourcePropertyValue = BindingEntityInfo.GetPropertyValue(source, sourceProperty, out bpi); 
 
                // Check if it is an interesting property e.g. collection, entity reference or complex type.
                if (bpi != null) 
                {
                    // Disconnect the edge between source and original source property value.
                    this.bindingGraph.RemoveRelation(source, sourceProperty);
 
                    switch (bpi.PropertyKind)
                    { 
                        case BindingPropertyKind.BindingPropertyKindCollection: 
                            // If collection is already being tracked by the graph we can not have > 1 links to it.
                            if (sourcePropertyValue != null) 
                            {
                                // Make sure that there is no observer on the input collection property.
                                try
                                { 
                                    typeof(BindingUtils)
                                        .GetMethod("VerifyObserverNotPresent", BindingFlags.NonPublic | BindingFlags.Static) 
                                        .MakeGenericMethod(bpi.PropertyInfo.CollectionType) 
                                        .Invoke(null, new object[] { sourcePropertyValue, sourceProperty, source.GetType() });
                                } 
                                catch (TargetInvocationException tie)
                                {
                                    throw tie.InnerException;
                                } 

                                try 
                                { 
                                    this.AttachBehavior = true;
                                    this.bindingGraph.AddCollection( 
                                            source,
                                            sourceProperty,
                                            sourcePropertyValue,
                                            null); 
                                }
                                finally 
                                { 
                                    this.AttachBehavior = false;
                                } 
                            }

                            break;
 
                        case BindingPropertyKind.BindingPropertyKindEntity:
                            // Add the newly added entity to the graph, or update entity reference. 
                            this.bindingGraph.AddEntity( 
                                    source,
                                    sourceProperty, 
                                    sourcePropertyValue,
                                    null,
                                    source);
                            break; 

                        default: 
                            Debug.Assert(bpi.PropertyKind == BindingPropertyKind.BindingPropertyKindComplex, "Must be complex type if PropertyKind is not entity or collection."); 

                            // Attach the newly assigned complex type object and it's child complex typed objects. 
                            if (sourcePropertyValue != null)
                            {
                                this.bindingGraph.AddComplexProperty(
                                        source, 
                                        sourceProperty,
                                        sourcePropertyValue); 
                            } 

                            this.HandleUpdateEntity( 
                                    source,
                                    sourceProperty,
                                    sourcePropertyValue);
                            break; 
                    }
                } 
                else 
                {
                    // For non-interesting properties i.e. value types or regular collection properties we simply call UpdateObject on the context. 
                    // Note that this code also handles primitive properties of complex typed objects.
                    this.HandleUpdateEntity(
                            source,
                            sourceProperty, 
                            sourcePropertyValue);
                } 
            } 
        }
 
        /// Handle changes to tracked DataServiceCollection.
        /// The DataServiceCollection that raised the event.
        /// Information about the event such as added/removed entities, operation.
        internal void OnCollectionChanged(object collection, NotifyCollectionChangedEventArgs eventArgs) 
        {
            Util.CheckArgumentNull(collection, "collection"); 
            Util.CheckArgumentNull(eventArgs, "eventArgs"); 

            Debug.Assert(BindingEntityInfo.IsDataServiceCollection(collection.GetType()), "We only register this event for DataServiceCollections."); 
#if DEBUG
            Debug.Assert(this.bindingGraph.IsTracking(collection), "Collection must be part of the graph if it has the event notification registered.");
#endif
            object source; 
            string sourceProperty;
            string sourceEntitySet; 
            string targetEntitySet; 

            this.bindingGraph.GetEntityCollectionInfo( 
                    collection,
                    out source,
                    out sourceProperty,
                    out sourceEntitySet, 
                    out targetEntitySet);
 
            switch (eventArgs.Action) 
            {
                case NotifyCollectionChangedAction.Add: 
                    // This event is raised by ObservableCollection.InsertItem.
                    this.OnAddToCollection(
                            eventArgs,
                            source, 
                            sourceProperty,
                            targetEntitySet, 
                            collection); 
                    break;
 
                case NotifyCollectionChangedAction.Remove:
                    // This event is raised by ObservableCollection.RemoveItem.
                    this.OnDeleteFromCollection(
                            eventArgs, 
                            source,
                            sourceProperty, 
                            collection); 
                    break;
 
                case NotifyCollectionChangedAction.Replace:
                    // This event is raised by ObservableCollection.SetItem.
                    this.OnDeleteFromCollection(
                            eventArgs, 
                            source,
                            sourceProperty, 
                            collection); 

                    this.OnAddToCollection( 
                            eventArgs,
                            source,
                            sourceProperty,
                            targetEntitySet, 
                            collection);
                    break; 
 
                case NotifyCollectionChangedAction.Reset:
                    // This event is raised by ObservableCollection.Clear. 
                    if (this.DetachBehavior)
                    {
                        // Detach behavior requires going through each item and detaching it from context.
                        this.RemoveWithDetachCollection(collection); 
                    }
                    else 
                    { 
                        // Non-detach behavior requires only removing vertices of collection from graph.
                        this.bindingGraph.RemoveCollection(collection); 
                    }

                    break;
 
#if !ASTORIA_LIGHT
                case NotifyCollectionChangedAction.Move: 
                    // Do Nothing. Added for completeness. 
                    break;
#endif 

                default:
                    throw new InvalidOperationException(Strings.DataBinding_CollectionChangedUnknownAction(eventArgs.Action));
            } 
        }
 
        /// Handle Adds to a tracked DataServiceCollection. Perform operations on context to reflect the changes. 
        /// The source object that reference the target object through a navigation property.
        /// The navigation property in the source object that reference the target object. 
        /// The entity set of the source object.
        /// The collection containing the target object.
        /// The target entity to attach.
        /// The entity set name of the target object. 
        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);
                } 
            } 
        }
 
        /// Handle Deletes from a tracked DataServiceCollection. Perform operations on context to reflect the changes.
        /// The source object that reference the target object through a navigation property.
        /// The navigation property in the source object that reference the target object.
        /// The entity set of the source object. 
        /// The collection containing the target object.
        /// The target entity. 
        /// The entity set name of the target object. 
        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); 
                }
            } 
        }

        /// Handle changes to navigation properties of a tracked entity. Perform operations on context to reflect the changes.
        /// The source object that reference the target object through a navigation property. 
        /// The navigation property in the source object that reference the target object.
        /// The entity set of the source object. 
        /// The target entity. 
        /// The entity set name of the target object.
        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); 
            } 
        }
 
        /// Determine if the DataServiceContext is tracking the specified entity.
        /// An entity object.
        /// true if the entity is tracked; otherwise false.
        internal bool IsContextTrackingEntity(object entity) 
        {
            Debug.Assert(entity != null, "entity must be provided when checking for context tracking."); 
            return this.Context.GetEntityDescriptor(entity) != default(EntityDescriptor); 
        }
 
        /// 
        /// Handle changes to an entity object tracked by the BindingObserver
        /// 
        /// The entity object that has changed. 
        /// The property of the target entity object that has changed.
        /// The value of the changed property of the target object. 
        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); 
            }
        }

        /// Processes the INotifyCollectionChanged.Add event. 
        /// Event information such as added items.
        /// Parent entity to which collection belongs. 
        /// Parent entity property referring to collection. 
        /// Entity set of the collection.
        /// Collection that changed. 
        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"));
                    } 

                    // Start tracking the target entity and synchronize the context with the Add operation. 
                    this.bindingGraph.AddEntity( 
                            source,
                            sourceProperty, 
                            target,
                            targetEntitySet,
                            collection);
                } 
            }
        } 
 
        /// Processes the INotifyCollectionChanged.Remove event.
        /// Event information such as deleted items. 
        /// Parent entity to which collection belongs.
        /// Parent entity property referring to collection.
        /// Collection that changed.
        private void OnDeleteFromCollection( 
            NotifyCollectionChangedEventArgs eventArgs,
            object source, 
            String sourceProperty, 
            object collection)
        { 
            Debug.Assert(collection != null, "Must have a valid collection from which entities are removed.");
            Debug.Assert(
                (source == null && sourceProperty == null) || (source != null && !String.IsNullOrEmpty(sourceProperty)),
                "source and sourceProperty must both be null or both be non-null."); 

            if (eventArgs.OldItems != null) 
            { 
                this.DeepRemoveCollection(
                        eventArgs.OldItems, 
                        source ?? collection,
                        sourceProperty,
                        this.ValidateCollectionItem);
            } 
        }
 
        /// Removes a collection from the binding graph and detaches each item. 
        /// Collection whose elements are to be removed and detached.
        private void RemoveWithDetachCollection(object collection) 
        {
            Debug.Assert(this.DetachBehavior, "Must be detaching each item in collection.");

            object source = null; 
            string sourceProperty = null;
            string sourceEntitySet = null; 
            string targetEntitySet = null; 

            this.bindingGraph.GetEntityCollectionInfo( 
                    collection,
                    out source,
                    out sourceProperty,
                    out sourceEntitySet, 
                    out targetEntitySet);
 
            this.DeepRemoveCollection( 
                    this.bindingGraph.GetCollectionItems(collection),
                    source ?? collection, 
                    sourceProperty,
                    null);
        }
 
        /// Performs a Deep removal of all entities in a collection.
        /// Collection whose items are removed from binding graph. 
        /// Parent item whose property refer to the  being cleared. 
        /// Property of the  that refers to .
        /// Validation method if any that checks the individual item in  for validity. 
        private void DeepRemoveCollection(IEnumerable collection, object source, string sourceProperty, Action itemValidator)
        {
            foreach (object target in collection)
            { 
                if (itemValidator != null)
                { 
                    itemValidator(target); 
                }
 
                // Accumulate the list of entities to untrack, this includes deep added entities under target.
                List untrackingInfo = new List();

                this.CollectUnTrackingInfo( 
                        target,
                        source, 
                        sourceProperty, 
                        untrackingInfo);
 
                // Stop tracking the collection of entities found by CollectUnTrackingInfo from bottom up in object graph.
                foreach (UnTrackingInfo info in untrackingInfo)
                {
                    this.bindingGraph.Remove( 
                            info.Entity,
                            info.Parent, 
                            info.ParentProperty); 
                }
            } 

            this.bindingGraph.RemoveUnreachableVertices();
        }
 
        /// Handle the DataServiceContext.SaveChanges operation.
        /// DataServiceContext for the observer. 
        /// Information about SaveChanges operation results. 
        private void OnChangesSaved(object sender, SaveChangesEventArgs eventArgs)
        { 
            // Does the response status code have to be checked? SaveChanges throws on failure.
            // DataServiceResponse response = eventArgs.Response;
            this.bindingGraph.RemoveNonTrackedEntities();
        } 

        /// Collects a list of entities that observer is supposed to stop tracking 
        /// Entity being delete along with it's children 
        /// Parent of the 
        /// Property by which  refers to  
        /// List in which entities to be untracked are collected
        private void CollectUnTrackingInfo(
            object currentEntity,
            object parentEntity, 
            string parentProperty,
            IList entitiesToUnTrack) 
        { 
            // We need to delete the child objects first before we delete the parent
            foreach (var ed in this.Context 
                                   .Entities
                                   .Where(x => x.ParentEntity == currentEntity && x.State == EntityStates.Added))
            {
                this.CollectUnTrackingInfo( 
                        ed.Entity,
                        ed.ParentEntity, 
                        ed.ParentPropertyForInsert, 
                        entitiesToUnTrack);
            } 

            entitiesToUnTrack.Add(new UnTrackingInfo
                                  {
                                    Entity = currentEntity, 
                                    Parent = parentEntity,
                                    ParentProperty = parentProperty 
                                  }); 
        }
 
        /// Determine if the DataServiceContext is tracking link between  and .
        /// The source object side of the link.
        /// A property in the source side of the link that references the target.
        /// The target object side of the link. 
        /// True if the link is tracked; otherwise false.
        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); 
        }
 
        /// Checks whether the given entity is in detached or deleted state.
        /// Entity being checked.
        /// true if the entity is detached or deleted, otherwise returns false.
        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;
        }

        /// Entity validator that checks if the  is of entity type. 
        /// Entity being validated.
        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")); 
            } 
        }
 
        #endregion

        /// Information regarding each entity to be untracked
        private class UnTrackingInfo 
        {
            /// Entity to untrack 
            public object Entity 
            {
                get; 
                set;
            }

            /// Parent object of  
            public object Parent
            { 
                get; 
                set;
            } 

            /// Parent object property referring to 
            public string ParentProperty
            { 
                get;
                set; 
            } 
        }
    } 
}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK