DataServiceCollectionOfT.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 / DataServiceCollectionOfT.cs / 1305376 / DataServiceCollectionOfT.cs

                            //---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//  
//   DataServiceCollection class
//  
// 
//---------------------------------------------------------------------
namespace System.Data.Services.Client 
{
    #region Namespaces.

    using System.Collections.Generic; 
    using System.Collections.ObjectModel;
    using System.ComponentModel; 
    using System.Diagnostics; 
    using System.Linq;
 
    #endregion Namespaces.

    /// 
    /// Enumeration used for cunstructing DataServiceCollection which specifies 
    /// whether the collection should be in automatic change tracking mode or in manual (none)
    /// change tracking mode. 
    ///  
    public enum TrackingMode
    { 
        /// The collection should not track changes.
        None,

        /// The collection should automatically track changes to the entities 
        /// in the collection.
        AutoChangeTracking 
    } 

    ///  
    /// An DataServiceCollection is a collection of entites.
    /// The collection implement INotifyCollectionChanged and INotifyPropertyChanged.
    /// 
    /// An entity class 
    public class DataServiceCollection : ObservableCollection
    { 
        #region Private fields. 

        /// The BindingObserver associated with the DataServiceCollection 
        private BindingObserver observer;

        /// Is this a root collection
        private bool rootCollection; 

        /// The continuation for partial collections. 
        private DataServiceQueryContinuation continuation; 

        /// True if tracking setup was deferred to first Load() call. 
        private bool trackingOnLoad;

        /// Callback tracked until tracking is enabled.
        private Func entityChangedCallback; 

        /// Callback tracked until tracking is enabled. 
        private Func collectionChangedCallback; 

        /// Entity set name tracked until tracking is enabled. 
        private string entitySetName;

#if ASTORIA_LIGHT
        /// If there's an async operation in progress (LoadAsync methods), this field is true 
        /// otherwise it's false.
        private bool asyncOperationInProgress; 
#endif 

        #endregion Private fields. 

        /// 
        /// Creates a default data service collection, with auto-change tracking enabled
        /// as soon as data is loaded into it. 
        /// 
        public DataServiceCollection() 
            : this(null, null, TrackingMode.AutoChangeTracking, null, null, null) 
        {
        } 

        /// 
        /// Creates a tracking DataServiceCollection and pre-loads it
        ///  
        /// Collection to initialize the new DataServiceCollection with.
        public DataServiceCollection(IEnumerable items) 
            : this(null, items, TrackingMode.AutoChangeTracking, null, null, null) 
        {
        } 

        /// 
        /// Creates a tracking DataServiceCollection and pre-loads it, enabling or disabling auto-change tracking as needed
        ///  
        /// Collection to initialize the new DataServiceCollection with.
        /// Whether auto-change tracking should be enabled 
        public DataServiceCollection(IEnumerable items, TrackingMode trackingMode) 
            : this(null, items, trackingMode, null, null, null)
        { 
        }

        /// 
        /// Creates a data service collection associated to the provided context for 
        /// the purpose of auto-change tracking.
        ///  
        /// DataServiceContext associated with the new collection. 
        public DataServiceCollection(DataServiceContext context)
            : this(context, null, TrackingMode.AutoChangeTracking, null, null, null) 
        {
        }

        /// Creates a new DataServiceCollection. 
        /// DataServiceContext associated with the new collection.
        /// The entity set of the elements in the collection. 
        /// Delegate that would get called when an entity changes. 
        /// Delegate that would get called when an entity collection changes.
        public DataServiceCollection( 
            DataServiceContext context,
            string entitySetName,
            Func entityChangedCallback,
            Func collectionChangedCallback) 
            : this(context, null, TrackingMode.AutoChangeTracking, entitySetName, entityChangedCallback, collectionChangedCallback)
        { 
        } 

        /// Creates a new DataServiceCollection. 
        /// Enumeration of items to initialize the new DataServiceCollection with.
        /// The tracking mode for the new collection.
        /// The name of the entity set the elements in the collection belong to.
        /// Delegate that gets called when an entity changes. 
        /// Delegate that gets called when an entity collection changes.
        public DataServiceCollection( 
            IEnumerable items, 
            TrackingMode trackingMode,
            string entitySetName, 
            Func entityChangedCallback,
            Func collectionChangedCallback)
            : this(null, items, trackingMode, entitySetName, entityChangedCallback, collectionChangedCallback)
        { 
        }
 
        /// Creates a new DataServiceCollection. 
        ///  associated with the new collection.
        /// Enumeration of items to initialize the new DataServiceCollection with. 
        /// The tracking mode for the new collection.
        /// The name of the entity set the elements in the collection belong to.
        /// Delegate that gets called when an entity changes.
        /// Delegate that gets called when an entity collection changes. 
        public DataServiceCollection(
            DataServiceContext context, 
            IEnumerable items, 
            TrackingMode trackingMode,
            string entitySetName, 
            Func entityChangedCallback,
            Func collectionChangedCallback)
        {
            if (trackingMode == TrackingMode.AutoChangeTracking) 
            {
                if (context == null) 
                { 
                    if (items == null)
                    { 
                        // Enable tracking on first Load/LoadAsync call, when we can obtain a context
                        this.trackingOnLoad = true;

                        // Save off these for when we enable tracking later 
                        this.entitySetName = entitySetName;
                        this.entityChangedCallback = entityChangedCallback; 
                        this.collectionChangedCallback = collectionChangedCallback; 
                    }
                    else 
                    {
                        // This throws if no context can be obtained, no need to check here
                        context = DataServiceCollection.GetContextFromItems(items);
                    } 
                }
 
                if (!this.trackingOnLoad) 
                {
                    if (items != null) 
                    {
                        DataServiceCollection.ValidateIteratorParameter(items);
                    }
 
                    this.StartTracking(context, items, entitySetName, entityChangedCallback, collectionChangedCallback);
                } 
            } 
            else if (items != null)
            { 
                this.Load(items);
            }
        }
 
        /// Creates new DataServiceCollection.
        /// The materializer 
        ///  associated with the new collection. 
        /// Enumeration of items to initialize the new DataServiceCollection with.
        /// The tracking mode for the new collection. 
        /// The name of the entity set the elements in the collection belong to.
        /// Delegate that gets called when an entity changes.
        /// Delegate that gets called when an entity collection changes.
        /// This is the internal constructor called from materializer and used inside our projection queries. 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800", Justification = "Constructor and debug-only code can't reuse cast.")]
        internal DataServiceCollection( 
            object atomMaterializer, 
            DataServiceContext context,
            IEnumerable items, 
            TrackingMode trackingMode,
            string entitySetName,
            Func entityChangedCallback,
            Func collectionChangedCallback) 
            : this(
                context != null ? context : ((AtomMaterializer)atomMaterializer).Context, 
                items, 
                trackingMode,
                entitySetName, 
                entityChangedCallback,
                collectionChangedCallback)
        {
            Debug.Assert(atomMaterializer != null, "atomMaterializer != null"); 
            Debug.Assert(((AtomMaterializer)atomMaterializer).Context != null, "Context != null");
 
            if (items != null) 
            {
                ((AtomMaterializer)atomMaterializer).PropagateContinuation(items, this); 
            }
        }

        #region Properties 

        /// The continuation for additional results; null if none are available. 
        public DataServiceQueryContinuation Continuation 
        {
            get { return this.continuation; } 
            set { this.continuation = value; }
        }

        /// Observer for the collection. 
        /// The setter would get called only for child collections in the graph.
        internal BindingObserver Observer 
        { 
            get
            { 
                return this.observer;
            }

            set 
            {
                Debug.Assert(!this.rootCollection, "Must be a child collection to have the Observer setter called."); 
                Debug.Assert(typeof(System.ComponentModel.INotifyPropertyChanged).IsAssignableFrom(typeof(T)), "The entity type must be trackable (by implementing INotifyPropertyChanged interface)"); 
                this.observer = value;
            } 
        }

        /// 
        /// Whether this collection is actively tracking 
        /// 
        internal bool IsTracking 
        { 
            get { return this.observer != null; }
        } 

        #endregion

        /// Loads the collection from another collection. 
        /// Collection whose elements will be loaded into the DataServiceCollection.
        ///  
        /// When tracking is enabled, the behavior of Load would be to attach all those entities that are not already tracked by the context 
        /// associated with the collection. The operation will go deep into the input entities so that all related
        /// entities are attached to the context if not already present. All entities in  
        /// will be tracked after Load is done.
        /// Load method checks for duplication. The collection will ignore any duplicated items been loaded.
        /// For large amount of items, consider DataServiceContext.LoadProperty instead.
        ///  
        public void Load(IEnumerable items)
        { 
            DataServiceCollection.ValidateIteratorParameter(items); 

            if (this.trackingOnLoad) 
            {
                // This throws if no context can be obtained, no need to check here
                DataServiceContext context = DataServiceCollection.GetContextFromItems(items);
 
                this.trackingOnLoad = false;
 
                this.StartTracking(context, items, this.entitySetName, this.entityChangedCallback, this.collectionChangedCallback); 
            }
            else 
            {
                this.StartLoading();
                try
                { 
                    this.InternalLoadCollection(items);
                } 
                finally 
                {
                    this.FinishLoading(); 
                }
            }
        }
 
#if ASTORIA_LIGHT
        /// A completion event for the ,  
        /// and  method. 
        /// This event is raised exactly once for each call to the ,
        ///  or  method. It is called both when the operation 
        /// succeeded and/or when it failed.
        public event EventHandler LoadCompleted;

        /// Loads the collection asynchronously from a DataServiceQuery instance. 
        /// A query of type DataServiceQuery.
        /// This method uses the event-based async pattern. 
        /// The method returns immediately without waiting for the query to complete. Then it calls the handler of the 
        ///  event exactly once on the UI thread. The event will be raised regradless
        /// if the query succeeded or not. 
        /// This class only support one asynchronous operation in flight.
        public void LoadAsync(IQueryable query)
        {
            Util.CheckArgumentNull(query, "query"); 
            DataServiceQuery dsq = query as DataServiceQuery;
            if (dsq == null) 
            { 
                throw new ArgumentException(Strings.DataServiceCollection_LoadAsyncRequiresDataServiceQuery, "query");
            } 

            if (this.asyncOperationInProgress)
            {
                throw new InvalidOperationException(Strings.DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime); 
            }
 
            if (this.trackingOnLoad) 
            {
                this.StartTracking(((DataServiceQueryProvider)dsq.Provider).Context, 
                                   null,
                                   this.entitySetName,
                                   this.entityChangedCallback,
                                   this.collectionChangedCallback); 
                this.trackingOnLoad = false;
            } 
 
            BeginLoadAsyncOperation(
                asyncCallback => dsq.BeginExecute(asyncCallback, null), 
                asyncResult =>
                    {
                        QueryOperationResponse response = (QueryOperationResponse)dsq.EndExecute(asyncResult);
                        this.Load(response); 
                        return response;
                    }); 
        } 

        /// Loads the collection asynchronously for a property represented by the DataServiceCollection. 
        /// This method loads the content of a property represented by this DataServiceCollection.
        /// If this instance is not associated with any property and entity the method will fail.
        /// This method uses the event-based async pattern.
        /// The method returns immediately without waiting for the query to complete. Then it calls the handler of the 
        ///  event exactly once on the UI thread. The event will be raised regradless
        /// if the query succeeded or not. 
        /// This class only support one asynchronous operation in flight. 
        public void LoadAsync()
        { 
            if (!this.IsTracking)
            {
                throw new InvalidOperationException(Strings.DataServiceCollection_OperationForTrackedOnly);
            } 

            object parent; 
            string property; 
            if (!this.observer.LookupParent(this, out parent, out property))
            { 
                throw new InvalidOperationException(Strings.DataServiceCollection_LoadAsyncNoParamsWithoutParentEntity);
            }

            if (this.asyncOperationInProgress) 
            {
                throw new InvalidOperationException(Strings.DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime); 
            } 

            BeginLoadAsyncOperation( 
                asyncCallback => this.observer.Context.BeginLoadProperty(parent, property, asyncCallback, null),
                asyncResult => (QueryOperationResponse)this.observer.Context.EndLoadProperty(asyncResult));
        }
 
        /// Loads next partial set for this collection.
        /// If this collection doesn't have a continuation token (this.Continuation == null) then this method 
        /// returns false and does not issue any request. 
        /// If there is a continuation token the method will return true and will start a request to load
        /// the next partial set based on that continuation token. 
        /// This method is the same as  except that it runs the query as defined
        /// by the continuation token of this collection.
        /// The method returns immediately without waiting for the query to complete. Then it calls the handler of the
        ///  event exactly once on the UI thread. The event will be raised regradless 
        /// if the query succeeded or not. Even if the method returns false, the event will be raised (immeditaly)
        /// This class only support one asynchronous operation in flight. 
        public bool LoadNextPartialSetAsync() 
        {
            if (!this.IsTracking) 
            {
                throw new InvalidOperationException(Strings.DataServiceCollection_OperationForTrackedOnly);
            }
 
            if (this.asyncOperationInProgress)
            { 
                throw new InvalidOperationException(Strings.DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime); 
            }
 
            if (this.Continuation == null)
            {
                if (this.LoadCompleted != null)
                { 
                    this.LoadCompleted(this, new LoadCompletedEventArgs(null, null));
                } 
                return false; 
            }
 
            BeginLoadAsyncOperation(
                asyncCallback => this.observer.Context.BeginExecute(this.Continuation, asyncCallback, null),
                asyncResult =>
                    { 
                        QueryOperationResponse response = (QueryOperationResponse)this.observer.Context.EndExecute(asyncResult);
                        this.Load(response); 
                        return response; 
                    });
 
            return true;
        }

#endif 

        /// Loads a single entity into the collection. 
        /// Entity to be added. 
        /// 
        /// When tracking is enabled, the behavior of Load would be to attach the entity if it is not already tracked by the context 
        /// associated with the collection. The operation will go deep into the input entity so that all related
        /// entities are attached to the context if not already present. The  will be
        /// tracked after Load is done.
        /// Load method checks for duplication. The collection will ignore any duplicated items been loaded. 
        /// 
        public void Load(T item) 
        { 
            // When loading a single item,
            if (item == null) 
            {
                throw Error.ArgumentNull("item");
            }
 
            this.StartLoading();
            try 
            { 
                if (!this.Contains(item))
                { 
                    this.Add(item);
                }
            }
            finally 
            {
                this.FinishLoading(); 
            } 
        }
 
        /// 
        /// Clears a collection and optionally detaches all the items in it including deep added items from the context
        /// 
        /// true if detach from context must happen, false otherwise. This parameter only affects tracked collections. 
        public void Clear(bool stopTracking)
        { 
            if (!this.IsTracking) 
            {
                throw new InvalidOperationException(Strings.DataServiceCollection_OperationForTrackedOnly); 
            }

            if (!stopTracking)
            { 
                // non-binding or just clear
                this.Clear(); 
            } 
            else
            { 
                Debug.Assert(this.observer.Context != null, "Must have valid context when the collection is being observed.");
                try
                {
                    this.observer.DetachBehavior = true; 
                    this.Clear();
                } 
                finally 
                {
                    this.observer.DetachBehavior = false; 
                }
            }
        }
 
        /// Stop tracking the collection. The operation is only allowed for root collections.
        ///  
        /// All the entitities in the root collection and all it's related objects will be untracked at the 
        /// end of this operation.
        ///  
        public void Detach()
        {
            if (!this.IsTracking)
            { 
                throw new InvalidOperationException(Strings.DataServiceCollection_OperationForTrackedOnly);
            } 
 
            // Operation only allowed on root collections.
            if (!this.rootCollection) 
            {
                throw new InvalidOperationException(Strings.DataServiceCollection_CannotStopTrackingChildCollection);
            }
 
            this.observer.StopTracking();
            this.observer = null; 
 
            this.rootCollection = false;
        } 

#if ASTORIA_LIGHT
        public new void Add(T item)
        { 
            if (this.IsTracking)
            { 
                INotifyPropertyChanged notify = item as INotifyPropertyChanged; 
                if (notify == null)
                { 
                    throw new InvalidOperationException(Strings.DataBinding_NotifyPropertyChangedNotImpl(item.GetType()));
                }
            }
            base.Add(item); 
        }
#endif 
 
        /// 
        /// Override to prevent additions to the collection in "deferred tracking" mode 
        /// 
        /// Index of element to add
        /// Element to add
        protected override void InsertItem(int index, T item) 
        {
            if (this.trackingOnLoad) 
            { 
                throw new InvalidOperationException(Strings.DataServiceCollection_InsertIntoTrackedButNotLoadedCollection);
            } 

            base.InsertItem(index, item);
        }
 
        /// 
        /// Verifies that input iterator parameter is not null and in case 
        /// of Silverlight, it is not of DataServiceQuery type. 
        /// 
        /// Input iterator parameter. 
        private static void ValidateIteratorParameter(IEnumerable items)
        {
            Util.CheckArgumentNull(items, "items");
#if ASTORIA_LIGHT 
            DataServiceQuery dsq = items as DataServiceQuery;
            if (dsq != null) 
            { 
                throw new ArgumentException(Strings.DataServiceCollection_DataServiceQueryCanNotBeEnumerated);
            } 
#endif
        }

        ///  
        /// Obtain the DataServiceContext from the incoming enumerable
        ///  
        /// An IEnumerable that may be a DataServiceQuery or QueryOperationResponse object 
        /// DataServiceContext instance associated with the input
        private static DataServiceContext GetContextFromItems(IEnumerable items) 
        {
            Debug.Assert(items != null, "items != null");

            DataServiceQuery dataServiceQuery = items as DataServiceQuery; 
            if (dataServiceQuery != null)
            { 
                DataServiceQueryProvider queryProvider = dataServiceQuery.Provider as DataServiceQueryProvider; 
                Debug.Assert(queryProvider != null, "Got DataServiceQuery with unknown query provider.");
                DataServiceContext context = queryProvider.Context; 
                Debug.Assert(context != null, "Query provider must always have valid context.");
                return context;
            }
 
            QueryOperationResponse queryOperationResponse = items as QueryOperationResponse;
            if (queryOperationResponse != null) 
            { 
                Debug.Assert(queryOperationResponse.Results != null, "Got QueryOperationResponse without valid results.");
                DataServiceContext context = queryOperationResponse.Results.Context; 
                Debug.Assert(context != null, "Materializer must always have valid context.");
                return context;
            }
 
            throw new ArgumentException(Strings.DataServiceCollection_CannotDetermineContextFromItems);
        } 
 
        /// 
        /// Populate this collection with another collection of items 
        /// 
        /// The items to populate this collection with
        private void InternalLoadCollection(IEnumerable items)
        { 
            Debug.Assert(items != null, "items != null");
#if !ASTORIA_LIGHT 
            // For SDP, we must execute the Query implicitly 
            DataServiceQuery query = items as DataServiceQuery;
            if (query != null) 
            {
                items = query.Execute() as QueryOperationResponse;
            }
#else 
            Debug.Assert(!(items is DataServiceQuery), "SL Client using DSQ as items...should have been caught by ValidateIteratorParameter.");
#endif 
 
            foreach (T item in items)
            { 
                // if this is too slow, consider hashing the set
                // or just use LoadProperties
                if (!this.Contains(item))
                { 
                    this.Add(item);
                } 
            } 

            QueryOperationResponse response = items as QueryOperationResponse; 
            if (response != null)
            {
                // this should never be throwing (since we've enumerated already)!
                // Note: Inner collection's nextPartLinkUri is set by the materializer 
                this.continuation = response.GetContinuation();
            } 
            else 
            {
                this.continuation = null; 
            }
        }

        ///  
        /// Prepare the collection for loading. For tracked collections, we enter the attaching state
        ///  
        private void StartLoading() 
        {
            if (this.IsTracking) 
            {
                // Observer must be present on the target collection which implies that the operation would fail on default constructed objects.
                if (this.observer.Context == null)
                { 
                    throw new InvalidOperationException(Strings.DataServiceCollection_LoadRequiresTargetCollectionObserved);
                } 
 
                this.observer.AttachBehavior = true;
            } 
        }

        /// 
        /// Reset the collection after loading. For tracked collections, we exit the attaching state. 
        /// 
        private void FinishLoading() 
        { 
            if (this.IsTracking)
            { 
                this.observer.AttachBehavior = false;
            }
        }
 
        /// Initialize and start tracking an DataServiceCollection
        /// The context 
        /// Collection to initialize with 
        /// The entity set of the elements in the collection.
        /// delegate that needs to be called when an entity changes. 
        /// delegate that needs to be called when an entity collection is changed.
        private void StartTracking(
            DataServiceContext context,
            IEnumerable items, 
            String entitySet,
            Func entityChanged, 
            Func collectionChanged) 
        {
            Debug.Assert(context != null, "Must have a valid context to initialize."); 
            Debug.Assert(this.observer == null, "Must have no observer which implies Initialize should only be called once.");

            // Add everything from the input collection.
            if (items != null) 
            {
                this.InternalLoadCollection(items); 
            } 

            this.observer = new BindingObserver(context, entityChanged, collectionChanged); 

            this.observer.StartTracking(this, entitySet);

            this.rootCollection = true; 
        }
 
#if ASTORIA_LIGHT 
        /// Helper method to start a LoadAsync operation.
        /// Function which calls the Begin method for the load. It should take  
        /// parameter which should be used as the callback for the Begin call. It should return 
        /// of the started asynchronous operation (or throw).
        /// Function which calls the End method for the load. It should take 
        /// which represents the asynchronous operation in flight. It should return  
        /// with the result of the operation (or throw).
        /// The method takes care of error handling as well as maintaining the . 
        /// Note that it does not check the  to disallow multiple operations in flight. 
        /// The method makes sure that the  will be called from the UI thread. It makes no assumptions
        /// about the calling thread of this method. 
        /// The method does not process the results of the , it just raises the 
        /// event as appropriate. If there's some processing to be done for the results it should all be done by the
        ///  method before it returns.
        private void BeginLoadAsyncOperation( 
            Func beginCall,
            Func endCall) 
        { 
            Debug.Assert(!this.asyncOperationInProgress, "Trying to start a new LoadAsync while another is still in progress. We should have thrown.");
 
            // NOTE: this is Silverlight-only, use BackgroundWorker instead of Deployment.Current.Dispatcher
            // to do this in [....]/WCF once we decide to add it there as well.
            // Note that we must mark the operation as in progress before we actually call Begin
            //   as the async operation might end immediately inside the Begin call and we have no control 
            //   over the ordering between the End callback thread, the thread Begin is called from
            //   and the UI thread on which we process the end event. 
            this.asyncOperationInProgress = true; 
            try
            { 
                IAsyncResult asyncResult = beginCall(
                    ar => System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
                    {
                        try 
                        {
                            QueryOperationResponse result = endCall(ar); 
                            this.asyncOperationInProgress = false; 
                            if (this.LoadCompleted != null)
                            { 
                                this.LoadCompleted(this, new LoadCompletedEventArgs(result, null));
                            }
                        }
                        catch (Exception ex) 
                        {
                            this.asyncOperationInProgress = false; 
                            if (this.LoadCompleted != null) 
                            {
                                this.LoadCompleted(this, new LoadCompletedEventArgs(null, ex)); 
                            }
                        }
                    }));
            } 
            catch (Exception)
            { 
                this.asyncOperationInProgress = false; 
                throw;
            } 
        }
#endif
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//  
//   DataServiceCollection class
//  
// 
//---------------------------------------------------------------------
namespace System.Data.Services.Client 
{
    #region Namespaces.

    using System.Collections.Generic; 
    using System.Collections.ObjectModel;
    using System.ComponentModel; 
    using System.Diagnostics; 
    using System.Linq;
 
    #endregion Namespaces.

    /// 
    /// Enumeration used for cunstructing DataServiceCollection which specifies 
    /// whether the collection should be in automatic change tracking mode or in manual (none)
    /// change tracking mode. 
    ///  
    public enum TrackingMode
    { 
        /// The collection should not track changes.
        None,

        /// The collection should automatically track changes to the entities 
        /// in the collection.
        AutoChangeTracking 
    } 

    ///  
    /// An DataServiceCollection is a collection of entites.
    /// The collection implement INotifyCollectionChanged and INotifyPropertyChanged.
    /// 
    /// An entity class 
    public class DataServiceCollection : ObservableCollection
    { 
        #region Private fields. 

        /// The BindingObserver associated with the DataServiceCollection 
        private BindingObserver observer;

        /// Is this a root collection
        private bool rootCollection; 

        /// The continuation for partial collections. 
        private DataServiceQueryContinuation continuation; 

        /// True if tracking setup was deferred to first Load() call. 
        private bool trackingOnLoad;

        /// Callback tracked until tracking is enabled.
        private Func entityChangedCallback; 

        /// Callback tracked until tracking is enabled. 
        private Func collectionChangedCallback; 

        /// Entity set name tracked until tracking is enabled. 
        private string entitySetName;

#if ASTORIA_LIGHT
        /// If there's an async operation in progress (LoadAsync methods), this field is true 
        /// otherwise it's false.
        private bool asyncOperationInProgress; 
#endif 

        #endregion Private fields. 

        /// 
        /// Creates a default data service collection, with auto-change tracking enabled
        /// as soon as data is loaded into it. 
        /// 
        public DataServiceCollection() 
            : this(null, null, TrackingMode.AutoChangeTracking, null, null, null) 
        {
        } 

        /// 
        /// Creates a tracking DataServiceCollection and pre-loads it
        ///  
        /// Collection to initialize the new DataServiceCollection with.
        public DataServiceCollection(IEnumerable items) 
            : this(null, items, TrackingMode.AutoChangeTracking, null, null, null) 
        {
        } 

        /// 
        /// Creates a tracking DataServiceCollection and pre-loads it, enabling or disabling auto-change tracking as needed
        ///  
        /// Collection to initialize the new DataServiceCollection with.
        /// Whether auto-change tracking should be enabled 
        public DataServiceCollection(IEnumerable items, TrackingMode trackingMode) 
            : this(null, items, trackingMode, null, null, null)
        { 
        }

        /// 
        /// Creates a data service collection associated to the provided context for 
        /// the purpose of auto-change tracking.
        ///  
        /// DataServiceContext associated with the new collection. 
        public DataServiceCollection(DataServiceContext context)
            : this(context, null, TrackingMode.AutoChangeTracking, null, null, null) 
        {
        }

        /// Creates a new DataServiceCollection. 
        /// DataServiceContext associated with the new collection.
        /// The entity set of the elements in the collection. 
        /// Delegate that would get called when an entity changes. 
        /// Delegate that would get called when an entity collection changes.
        public DataServiceCollection( 
            DataServiceContext context,
            string entitySetName,
            Func entityChangedCallback,
            Func collectionChangedCallback) 
            : this(context, null, TrackingMode.AutoChangeTracking, entitySetName, entityChangedCallback, collectionChangedCallback)
        { 
        } 

        /// Creates a new DataServiceCollection. 
        /// Enumeration of items to initialize the new DataServiceCollection with.
        /// The tracking mode for the new collection.
        /// The name of the entity set the elements in the collection belong to.
        /// Delegate that gets called when an entity changes. 
        /// Delegate that gets called when an entity collection changes.
        public DataServiceCollection( 
            IEnumerable items, 
            TrackingMode trackingMode,
            string entitySetName, 
            Func entityChangedCallback,
            Func collectionChangedCallback)
            : this(null, items, trackingMode, entitySetName, entityChangedCallback, collectionChangedCallback)
        { 
        }
 
        /// Creates a new DataServiceCollection. 
        ///  associated with the new collection.
        /// Enumeration of items to initialize the new DataServiceCollection with. 
        /// The tracking mode for the new collection.
        /// The name of the entity set the elements in the collection belong to.
        /// Delegate that gets called when an entity changes.
        /// Delegate that gets called when an entity collection changes. 
        public DataServiceCollection(
            DataServiceContext context, 
            IEnumerable items, 
            TrackingMode trackingMode,
            string entitySetName, 
            Func entityChangedCallback,
            Func collectionChangedCallback)
        {
            if (trackingMode == TrackingMode.AutoChangeTracking) 
            {
                if (context == null) 
                { 
                    if (items == null)
                    { 
                        // Enable tracking on first Load/LoadAsync call, when we can obtain a context
                        this.trackingOnLoad = true;

                        // Save off these for when we enable tracking later 
                        this.entitySetName = entitySetName;
                        this.entityChangedCallback = entityChangedCallback; 
                        this.collectionChangedCallback = collectionChangedCallback; 
                    }
                    else 
                    {
                        // This throws if no context can be obtained, no need to check here
                        context = DataServiceCollection.GetContextFromItems(items);
                    } 
                }
 
                if (!this.trackingOnLoad) 
                {
                    if (items != null) 
                    {
                        DataServiceCollection.ValidateIteratorParameter(items);
                    }
 
                    this.StartTracking(context, items, entitySetName, entityChangedCallback, collectionChangedCallback);
                } 
            } 
            else if (items != null)
            { 
                this.Load(items);
            }
        }
 
        /// Creates new DataServiceCollection.
        /// The materializer 
        ///  associated with the new collection. 
        /// Enumeration of items to initialize the new DataServiceCollection with.
        /// The tracking mode for the new collection. 
        /// The name of the entity set the elements in the collection belong to.
        /// Delegate that gets called when an entity changes.
        /// Delegate that gets called when an entity collection changes.
        /// This is the internal constructor called from materializer and used inside our projection queries. 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800", Justification = "Constructor and debug-only code can't reuse cast.")]
        internal DataServiceCollection( 
            object atomMaterializer, 
            DataServiceContext context,
            IEnumerable items, 
            TrackingMode trackingMode,
            string entitySetName,
            Func entityChangedCallback,
            Func collectionChangedCallback) 
            : this(
                context != null ? context : ((AtomMaterializer)atomMaterializer).Context, 
                items, 
                trackingMode,
                entitySetName, 
                entityChangedCallback,
                collectionChangedCallback)
        {
            Debug.Assert(atomMaterializer != null, "atomMaterializer != null"); 
            Debug.Assert(((AtomMaterializer)atomMaterializer).Context != null, "Context != null");
 
            if (items != null) 
            {
                ((AtomMaterializer)atomMaterializer).PropagateContinuation(items, this); 
            }
        }

        #region Properties 

        /// The continuation for additional results; null if none are available. 
        public DataServiceQueryContinuation Continuation 
        {
            get { return this.continuation; } 
            set { this.continuation = value; }
        }

        /// Observer for the collection. 
        /// The setter would get called only for child collections in the graph.
        internal BindingObserver Observer 
        { 
            get
            { 
                return this.observer;
            }

            set 
            {
                Debug.Assert(!this.rootCollection, "Must be a child collection to have the Observer setter called."); 
                Debug.Assert(typeof(System.ComponentModel.INotifyPropertyChanged).IsAssignableFrom(typeof(T)), "The entity type must be trackable (by implementing INotifyPropertyChanged interface)"); 
                this.observer = value;
            } 
        }

        /// 
        /// Whether this collection is actively tracking 
        /// 
        internal bool IsTracking 
        { 
            get { return this.observer != null; }
        } 

        #endregion

        /// Loads the collection from another collection. 
        /// Collection whose elements will be loaded into the DataServiceCollection.
        ///  
        /// When tracking is enabled, the behavior of Load would be to attach all those entities that are not already tracked by the context 
        /// associated with the collection. The operation will go deep into the input entities so that all related
        /// entities are attached to the context if not already present. All entities in  
        /// will be tracked after Load is done.
        /// Load method checks for duplication. The collection will ignore any duplicated items been loaded.
        /// For large amount of items, consider DataServiceContext.LoadProperty instead.
        ///  
        public void Load(IEnumerable items)
        { 
            DataServiceCollection.ValidateIteratorParameter(items); 

            if (this.trackingOnLoad) 
            {
                // This throws if no context can be obtained, no need to check here
                DataServiceContext context = DataServiceCollection.GetContextFromItems(items);
 
                this.trackingOnLoad = false;
 
                this.StartTracking(context, items, this.entitySetName, this.entityChangedCallback, this.collectionChangedCallback); 
            }
            else 
            {
                this.StartLoading();
                try
                { 
                    this.InternalLoadCollection(items);
                } 
                finally 
                {
                    this.FinishLoading(); 
                }
            }
        }
 
#if ASTORIA_LIGHT
        /// A completion event for the ,  
        /// and  method. 
        /// This event is raised exactly once for each call to the ,
        ///  or  method. It is called both when the operation 
        /// succeeded and/or when it failed.
        public event EventHandler LoadCompleted;

        /// Loads the collection asynchronously from a DataServiceQuery instance. 
        /// A query of type DataServiceQuery.
        /// This method uses the event-based async pattern. 
        /// The method returns immediately without waiting for the query to complete. Then it calls the handler of the 
        ///  event exactly once on the UI thread. The event will be raised regradless
        /// if the query succeeded or not. 
        /// This class only support one asynchronous operation in flight.
        public void LoadAsync(IQueryable query)
        {
            Util.CheckArgumentNull(query, "query"); 
            DataServiceQuery dsq = query as DataServiceQuery;
            if (dsq == null) 
            { 
                throw new ArgumentException(Strings.DataServiceCollection_LoadAsyncRequiresDataServiceQuery, "query");
            } 

            if (this.asyncOperationInProgress)
            {
                throw new InvalidOperationException(Strings.DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime); 
            }
 
            if (this.trackingOnLoad) 
            {
                this.StartTracking(((DataServiceQueryProvider)dsq.Provider).Context, 
                                   null,
                                   this.entitySetName,
                                   this.entityChangedCallback,
                                   this.collectionChangedCallback); 
                this.trackingOnLoad = false;
            } 
 
            BeginLoadAsyncOperation(
                asyncCallback => dsq.BeginExecute(asyncCallback, null), 
                asyncResult =>
                    {
                        QueryOperationResponse response = (QueryOperationResponse)dsq.EndExecute(asyncResult);
                        this.Load(response); 
                        return response;
                    }); 
        } 

        /// Loads the collection asynchronously for a property represented by the DataServiceCollection. 
        /// This method loads the content of a property represented by this DataServiceCollection.
        /// If this instance is not associated with any property and entity the method will fail.
        /// This method uses the event-based async pattern.
        /// The method returns immediately without waiting for the query to complete. Then it calls the handler of the 
        ///  event exactly once on the UI thread. The event will be raised regradless
        /// if the query succeeded or not. 
        /// This class only support one asynchronous operation in flight. 
        public void LoadAsync()
        { 
            if (!this.IsTracking)
            {
                throw new InvalidOperationException(Strings.DataServiceCollection_OperationForTrackedOnly);
            } 

            object parent; 
            string property; 
            if (!this.observer.LookupParent(this, out parent, out property))
            { 
                throw new InvalidOperationException(Strings.DataServiceCollection_LoadAsyncNoParamsWithoutParentEntity);
            }

            if (this.asyncOperationInProgress) 
            {
                throw new InvalidOperationException(Strings.DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime); 
            } 

            BeginLoadAsyncOperation( 
                asyncCallback => this.observer.Context.BeginLoadProperty(parent, property, asyncCallback, null),
                asyncResult => (QueryOperationResponse)this.observer.Context.EndLoadProperty(asyncResult));
        }
 
        /// Loads next partial set for this collection.
        /// If this collection doesn't have a continuation token (this.Continuation == null) then this method 
        /// returns false and does not issue any request. 
        /// If there is a continuation token the method will return true and will start a request to load
        /// the next partial set based on that continuation token. 
        /// This method is the same as  except that it runs the query as defined
        /// by the continuation token of this collection.
        /// The method returns immediately without waiting for the query to complete. Then it calls the handler of the
        ///  event exactly once on the UI thread. The event will be raised regradless 
        /// if the query succeeded or not. Even if the method returns false, the event will be raised (immeditaly)
        /// This class only support one asynchronous operation in flight. 
        public bool LoadNextPartialSetAsync() 
        {
            if (!this.IsTracking) 
            {
                throw new InvalidOperationException(Strings.DataServiceCollection_OperationForTrackedOnly);
            }
 
            if (this.asyncOperationInProgress)
            { 
                throw new InvalidOperationException(Strings.DataServiceCollection_MultipleLoadAsyncOperationsAtTheSameTime); 
            }
 
            if (this.Continuation == null)
            {
                if (this.LoadCompleted != null)
                { 
                    this.LoadCompleted(this, new LoadCompletedEventArgs(null, null));
                } 
                return false; 
            }
 
            BeginLoadAsyncOperation(
                asyncCallback => this.observer.Context.BeginExecute(this.Continuation, asyncCallback, null),
                asyncResult =>
                    { 
                        QueryOperationResponse response = (QueryOperationResponse)this.observer.Context.EndExecute(asyncResult);
                        this.Load(response); 
                        return response; 
                    });
 
            return true;
        }

#endif 

        /// Loads a single entity into the collection. 
        /// Entity to be added. 
        /// 
        /// When tracking is enabled, the behavior of Load would be to attach the entity if it is not already tracked by the context 
        /// associated with the collection. The operation will go deep into the input entity so that all related
        /// entities are attached to the context if not already present. The  will be
        /// tracked after Load is done.
        /// Load method checks for duplication. The collection will ignore any duplicated items been loaded. 
        /// 
        public void Load(T item) 
        { 
            // When loading a single item,
            if (item == null) 
            {
                throw Error.ArgumentNull("item");
            }
 
            this.StartLoading();
            try 
            { 
                if (!this.Contains(item))
                { 
                    this.Add(item);
                }
            }
            finally 
            {
                this.FinishLoading(); 
            } 
        }
 
        /// 
        /// Clears a collection and optionally detaches all the items in it including deep added items from the context
        /// 
        /// true if detach from context must happen, false otherwise. This parameter only affects tracked collections. 
        public void Clear(bool stopTracking)
        { 
            if (!this.IsTracking) 
            {
                throw new InvalidOperationException(Strings.DataServiceCollection_OperationForTrackedOnly); 
            }

            if (!stopTracking)
            { 
                // non-binding or just clear
                this.Clear(); 
            } 
            else
            { 
                Debug.Assert(this.observer.Context != null, "Must have valid context when the collection is being observed.");
                try
                {
                    this.observer.DetachBehavior = true; 
                    this.Clear();
                } 
                finally 
                {
                    this.observer.DetachBehavior = false; 
                }
            }
        }
 
        /// Stop tracking the collection. The operation is only allowed for root collections.
        ///  
        /// All the entitities in the root collection and all it's related objects will be untracked at the 
        /// end of this operation.
        ///  
        public void Detach()
        {
            if (!this.IsTracking)
            { 
                throw new InvalidOperationException(Strings.DataServiceCollection_OperationForTrackedOnly);
            } 
 
            // Operation only allowed on root collections.
            if (!this.rootCollection) 
            {
                throw new InvalidOperationException(Strings.DataServiceCollection_CannotStopTrackingChildCollection);
            }
 
            this.observer.StopTracking();
            this.observer = null; 
 
            this.rootCollection = false;
        } 

#if ASTORIA_LIGHT
        public new void Add(T item)
        { 
            if (this.IsTracking)
            { 
                INotifyPropertyChanged notify = item as INotifyPropertyChanged; 
                if (notify == null)
                { 
                    throw new InvalidOperationException(Strings.DataBinding_NotifyPropertyChangedNotImpl(item.GetType()));
                }
            }
            base.Add(item); 
        }
#endif 
 
        /// 
        /// Override to prevent additions to the collection in "deferred tracking" mode 
        /// 
        /// Index of element to add
        /// Element to add
        protected override void InsertItem(int index, T item) 
        {
            if (this.trackingOnLoad) 
            { 
                throw new InvalidOperationException(Strings.DataServiceCollection_InsertIntoTrackedButNotLoadedCollection);
            } 

            base.InsertItem(index, item);
        }
 
        /// 
        /// Verifies that input iterator parameter is not null and in case 
        /// of Silverlight, it is not of DataServiceQuery type. 
        /// 
        /// Input iterator parameter. 
        private static void ValidateIteratorParameter(IEnumerable items)
        {
            Util.CheckArgumentNull(items, "items");
#if ASTORIA_LIGHT 
            DataServiceQuery dsq = items as DataServiceQuery;
            if (dsq != null) 
            { 
                throw new ArgumentException(Strings.DataServiceCollection_DataServiceQueryCanNotBeEnumerated);
            } 
#endif
        }

        ///  
        /// Obtain the DataServiceContext from the incoming enumerable
        ///  
        /// An IEnumerable that may be a DataServiceQuery or QueryOperationResponse object 
        /// DataServiceContext instance associated with the input
        private static DataServiceContext GetContextFromItems(IEnumerable items) 
        {
            Debug.Assert(items != null, "items != null");

            DataServiceQuery dataServiceQuery = items as DataServiceQuery; 
            if (dataServiceQuery != null)
            { 
                DataServiceQueryProvider queryProvider = dataServiceQuery.Provider as DataServiceQueryProvider; 
                Debug.Assert(queryProvider != null, "Got DataServiceQuery with unknown query provider.");
                DataServiceContext context = queryProvider.Context; 
                Debug.Assert(context != null, "Query provider must always have valid context.");
                return context;
            }
 
            QueryOperationResponse queryOperationResponse = items as QueryOperationResponse;
            if (queryOperationResponse != null) 
            { 
                Debug.Assert(queryOperationResponse.Results != null, "Got QueryOperationResponse without valid results.");
                DataServiceContext context = queryOperationResponse.Results.Context; 
                Debug.Assert(context != null, "Materializer must always have valid context.");
                return context;
            }
 
            throw new ArgumentException(Strings.DataServiceCollection_CannotDetermineContextFromItems);
        } 
 
        /// 
        /// Populate this collection with another collection of items 
        /// 
        /// The items to populate this collection with
        private void InternalLoadCollection(IEnumerable items)
        { 
            Debug.Assert(items != null, "items != null");
#if !ASTORIA_LIGHT 
            // For SDP, we must execute the Query implicitly 
            DataServiceQuery query = items as DataServiceQuery;
            if (query != null) 
            {
                items = query.Execute() as QueryOperationResponse;
            }
#else 
            Debug.Assert(!(items is DataServiceQuery), "SL Client using DSQ as items...should have been caught by ValidateIteratorParameter.");
#endif 
 
            foreach (T item in items)
            { 
                // if this is too slow, consider hashing the set
                // or just use LoadProperties
                if (!this.Contains(item))
                { 
                    this.Add(item);
                } 
            } 

            QueryOperationResponse response = items as QueryOperationResponse; 
            if (response != null)
            {
                // this should never be throwing (since we've enumerated already)!
                // Note: Inner collection's nextPartLinkUri is set by the materializer 
                this.continuation = response.GetContinuation();
            } 
            else 
            {
                this.continuation = null; 
            }
        }

        ///  
        /// Prepare the collection for loading. For tracked collections, we enter the attaching state
        ///  
        private void StartLoading() 
        {
            if (this.IsTracking) 
            {
                // Observer must be present on the target collection which implies that the operation would fail on default constructed objects.
                if (this.observer.Context == null)
                { 
                    throw new InvalidOperationException(Strings.DataServiceCollection_LoadRequiresTargetCollectionObserved);
                } 
 
                this.observer.AttachBehavior = true;
            } 
        }

        /// 
        /// Reset the collection after loading. For tracked collections, we exit the attaching state. 
        /// 
        private void FinishLoading() 
        { 
            if (this.IsTracking)
            { 
                this.observer.AttachBehavior = false;
            }
        }
 
        /// Initialize and start tracking an DataServiceCollection
        /// The context 
        /// Collection to initialize with 
        /// The entity set of the elements in the collection.
        /// delegate that needs to be called when an entity changes. 
        /// delegate that needs to be called when an entity collection is changed.
        private void StartTracking(
            DataServiceContext context,
            IEnumerable items, 
            String entitySet,
            Func entityChanged, 
            Func collectionChanged) 
        {
            Debug.Assert(context != null, "Must have a valid context to initialize."); 
            Debug.Assert(this.observer == null, "Must have no observer which implies Initialize should only be called once.");

            // Add everything from the input collection.
            if (items != null) 
            {
                this.InternalLoadCollection(items); 
            } 

            this.observer = new BindingObserver(context, entityChanged, collectionChanged); 

            this.observer.StartTracking(this, entitySet);

            this.rootCollection = true; 
        }
 
#if ASTORIA_LIGHT 
        /// Helper method to start a LoadAsync operation.
        /// Function which calls the Begin method for the load. It should take  
        /// parameter which should be used as the callback for the Begin call. It should return 
        /// of the started asynchronous operation (or throw).
        /// Function which calls the End method for the load. It should take 
        /// which represents the asynchronous operation in flight. It should return  
        /// with the result of the operation (or throw).
        /// The method takes care of error handling as well as maintaining the . 
        /// Note that it does not check the  to disallow multiple operations in flight. 
        /// The method makes sure that the  will be called from the UI thread. It makes no assumptions
        /// about the calling thread of this method. 
        /// The method does not process the results of the , it just raises the 
        /// event as appropriate. If there's some processing to be done for the results it should all be done by the
        ///  method before it returns.
        private void BeginLoadAsyncOperation( 
            Func beginCall,
            Func endCall) 
        { 
            Debug.Assert(!this.asyncOperationInProgress, "Trying to start a new LoadAsync while another is still in progress. We should have thrown.");
 
            // NOTE: this is Silverlight-only, use BackgroundWorker instead of Deployment.Current.Dispatcher
            // to do this in [....]/WCF once we decide to add it there as well.
            // Note that we must mark the operation as in progress before we actually call Begin
            //   as the async operation might end immediately inside the Begin call and we have no control 
            //   over the ordering between the End callback thread, the thread Begin is called from
            //   and the UI thread on which we process the end event. 
            this.asyncOperationInProgress = true; 
            try
            { 
                IAsyncResult asyncResult = beginCall(
                    ar => System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
                    {
                        try 
                        {
                            QueryOperationResponse result = endCall(ar); 
                            this.asyncOperationInProgress = false; 
                            if (this.LoadCompleted != null)
                            { 
                                this.LoadCompleted(this, new LoadCompletedEventArgs(result, null));
                            }
                        }
                        catch (Exception ex) 
                        {
                            this.asyncOperationInProgress = false; 
                            if (this.LoadCompleted != null) 
                            {
                                this.LoadCompleted(this, new LoadCompletedEventArgs(null, ex)); 
                            }
                        }
                    }));
            } 
            catch (Exception)
            { 
                this.asyncOperationInProgress = false; 
                throw;
            } 
        }
#endif
    }
} 

// 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