File: System\Data\Services\Client\DataServiceContext.cs
Project: ndp\fx\src\DataWeb\Client\System.Data.Services.Client.csproj (System.Data.Services.Client)
//---------------------------------------------------------------------
// <copyright file="DataServiceContext.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
// context
// </summary>
//---------------------------------------------------------------------
 
// #define TESTUNIXNEWLINE
 
namespace System.Data.Services.Client
{
    #region Namespaces.
 
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Data.Services.Common;
#if !ASTORIA_LIGHT
    using System.Net;
#else // Data.Services http stack
    using System.Data.Services.Http;
#endif
    using System.Text;
    using System.Xml;
    using System.Xml.Linq;
 
    #endregion Namespaces.
 
    /// <summary>
    /// context
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506", Justification = "Central class of the API, likely to have many cross-references")]
    public class DataServiceContext
    {
#if !TESTUNIXNEWLINE
        /// <summary>cache NewLine string</summary>
        private static readonly string NewLine = System.Environment.NewLine;
#else
        /// <summary>cache NewLine string</summary>
        private const string NewLine = "\n";
#endif
 
        /// <summary>base uri prepended to relative uri</summary>
        private readonly System.Uri baseUri;
 
        /// <summary>base uri with guranteed trailing slash</summary>
        private readonly System.Uri baseUriWithSlash;
 
#if !ASTORIA_LIGHT  // Credentials not available
        /// <summary>Authentication interface for retrieving credentials for Web client authentication.</summary>
        private System.Net.ICredentials credentials;
#endif
 
        /// <summary>Override the namespace used for the data parts of the ATOM entries</summary>
        private string dataNamespace;
 
        /// <summary>resolve type from a typename</summary>
        private Func<Type, string> resolveName;
 
        /// <summary>resolve typename from a type</summary>
        private Func<string, Type> resolveType;
 
#if !ASTORIA_LIGHT  // Timeout not available
        /// <summary>time-out value in seconds, 0 for default</summary>
        private int timeout;
#endif
 
        /// <summary>whether to use post-tunneling for PUT/DELETE</summary>
        private bool postTunneling;
 
        /// <summary>Options when deserializing properties to the target type.</summary>
        private bool ignoreMissingProperties;
 
        /// <summary>Used to specify a value synchronization strategy.</summary>
        private MergeOption mergeOption;
 
        /// <summary>Default options to be used while doing savechanges.</summary>
        private SaveChangesOptions saveChangesDefaultOptions;
 
        /// <summary>Override the namespace used for the scheme in the category for ATOM entries.</summary>
        private Uri typeScheme;
 
        /// <summary>Client will ignore 404 resource not found exception and return an empty set when this is set to true</summary>
        private bool ignoreResourceNotFoundException;
 
#if ASTORIA_LIGHT // Multiple HTTP stacks for Silverlight
        /// <summary>The HTTP stack to use for requests.</summary>
        private HttpStack httpStack;
#endif
 
        #region Resource state management
 
        /// <summary>change order</summary>
        private uint nextChange;
 
        /// <summary>Set of tracked resources</summary>
        private Dictionary<object, EntityDescriptor> entityDescriptors = new Dictionary<object, EntityDescriptor>(EqualityComparer<object>.Default);
 
        /// <summary>Set of tracked resources by Identity</summary>
        private Dictionary<String, EntityDescriptor> identityToDescriptor;
 
        /// <summary>Set of tracked bindings</summary>
        private Dictionary<LinkDescriptor, LinkDescriptor> bindings = new Dictionary<LinkDescriptor, LinkDescriptor>(LinkDescriptor.EquivalenceComparer);
 
        /// <summary>
        /// A flag indicating if the data service context is applying changes 
        /// </summary>
        private bool applyingChanges;
 
        #endregion
 
        #region ctor
 
        /// <summary>
        /// Instantiates a new context with the specified <paramref name="serviceRoot"/> Uri.
        /// The library expects the Uri to point to the root of a data service,
        /// but does not issue a request to validate it does indeed identify the root of a service.
        /// If the Uri does not identify the root of the service, the behavior of the client library is undefined.    
        /// </summary>
        /// <param name="serviceRoot">
        /// An absolute, well formed http or https URI without a query or fragment which identifies the root of a data service.
        /// A Uri provided with a trailing slash is equivalent to one without such a trailing character
        /// </param>
        /// <exception cref="ArgumentException">if the <paramref name="serviceRoot"/> is not an absolute, well formed http or https URI without a query or fragment</exception>
        /// <exception cref="ArgumentNullException">when the <paramref name="serviceRoot"/> is null</exception>
        /// <remarks>
        /// With Silverlight, the <paramref name="serviceRoot"/> can be a relative Uri
        /// that will be combined with System.Windows.Browser.HtmlPage.Document.DocumentUri.
        /// </remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "parameters are validated against null via CheckArgumentNull")]
        public DataServiceContext(Uri serviceRoot)
        {
            Util.CheckArgumentNull(serviceRoot, "serviceRoot");
 
#if ASTORIA_LIGHT
            if (!serviceRoot.IsAbsoluteUri)
            {
                // If we can use XHR, we will and thus we should use the old (V1) way of determining our
                //   base URI. That is, use the uri of the HTML page
                if (XHRHttpWebRequest.IsAvailable())
                {
                    serviceRoot = new Uri(System.Windows.Browser.HtmlPage.Document.DocumentUri, serviceRoot);
                }
                else
                {
                    // Otherwise we are going to use the new Client stack. In this case there might not be any
                    //   HTML page hosting us, or it might not be accessible, so the only base uri we can use
                    //   is the base uri of the xap we're running in.
                    System.Net.WebClient webClient = new System.Net.WebClient();
                    serviceRoot = new Uri(new Uri(webClient.BaseAddress), serviceRoot);
                }
            }
#endif
            if (!serviceRoot.IsAbsoluteUri ||
                !Uri.IsWellFormedUriString(CommonUtil.UriToString(serviceRoot), UriKind.Absolute) ||
                !String.IsNullOrEmpty(serviceRoot.Query) ||
                !string.IsNullOrEmpty(serviceRoot.Fragment) ||
                ((serviceRoot.Scheme != "http") && (serviceRoot.Scheme != "https")))
            {
                throw Error.Argument(Strings.Context_BaseUri, "serviceRoot");
            }
 
            this.baseUri = serviceRoot;
            this.baseUriWithSlash = serviceRoot;
            string serviceRootString = CommonUtil.UriToString(serviceRoot);
            if (!serviceRootString.EndsWith("/", StringComparison.Ordinal))
            {
                this.baseUriWithSlash = Util.CreateUri(serviceRootString + "/", UriKind.Absolute);
            }
 
            this.mergeOption = MergeOption.AppendOnly;
            this.DataNamespace = XmlConstants.DataWebNamespace;
#if ASTORIA_LIGHT
            this.UsePostTunneling = true;
#else
            this.UsePostTunneling = false;
#endif
            this.typeScheme = new Uri(XmlConstants.DataWebSchemeNamespace);
#if ASTORIA_LIGHT
            this.httpStack = HttpStack.Auto;
#endif
        }
 
        #endregion
 
        /// <summary>
        /// This event is fired before a request it sent to the server, giving
        /// the handler the opportunity to inspect, adjust and/or replace the
        /// WebRequest object used to perform the request.
        /// </summary>
        /// <remarks>
        /// When calling BeginSaveChanges and not using SaveChangesOptions.Batch,
        /// this event may be raised from a different thread.
        /// </remarks>
        public event EventHandler<SendingRequestEventArgs> SendingRequest;
 
        /// <summary>
        /// This event fires once an entry has been read into a .NET object
        /// but before the serializer returns to the caller, giving handlers
        /// an opporunity to read further information from the incoming ATOM
        /// entry and updating the object
        /// </summary>
        /// <remarks>
        /// This event should only be raised from the thread that was used to
        /// invoke Execute, EndExecute, SaveChanges, EndSaveChanges.
        /// </remarks>
        public event EventHandler<ReadingWritingEntityEventArgs> ReadingEntity;
 
        /// <summary>
        /// This event fires once an ATOM entry is ready to be written to
        /// the network for a request, giving handlers an opportunity to
        /// customize the entry with information from the corresponding
        /// .NET object or the environment.
        /// </summary>
        /// <remarks>
        /// When calling BeginSaveChanges and not using SaveChangesOptions.Batch,
        /// this event may be raised from a different thread.
        /// </remarks>
        public event EventHandler<ReadingWritingEntityEventArgs> WritingEntity;
 
        /// <summary>
        /// This event fires when SaveChanges or EndSaveChanges is called
        /// </summary>
        internal event EventHandler<SaveChangesEventArgs> ChangesSaved;
 
        #region BaseUri, Credentials, MergeOption, Timeout, Links, Entities
        /// <summary>
        /// Absolute Uri identifying the root of the target data service.
        /// A Uri provided with a trailing slash is equivalent to one without such a trailing character.
        /// </summary>
        /// <remarks>
        /// Example: http://server/host/myservice.svc
        /// </remarks>
        public Uri BaseUri
        {
            get { return this.baseUri; }
        }
 
#if !ASTORIA_LIGHT  // Credentials not available
        /// <summary>
        /// Gets and sets the authentication information used by each query created using the context object.
        /// </summary>
        public System.Net.ICredentials Credentials
        {
            get { return this.credentials; }
            set { this.credentials = value; }
        }
#endif
 
        /// <summary>
        /// Used to specify a synchronization strategy when sending/receiving entities to/from a data service.
        /// This value is read by the deserialization component of the client prior to materializing objects.
        /// As such, it is recommended to set this property to the appropriate materialization strategy
        /// before executing any queries/updates to the data service.
        /// </summary>
        /// <remarks>
        /// The default value is <see cref="MergeOption"/>.AppendOnly.
        /// </remarks>
        public MergeOption MergeOption
        {
            get { return this.mergeOption; }
            set { this.mergeOption = Util.CheckEnumerationValue(value, "MergeOption"); }
        }
 
        /// <summary>
        /// A flag indicating if the data service context is applying changes 
        /// </summary>
        public bool ApplyingChanges
        {
            get { return this.applyingChanges; }
            internal set { this.applyingChanges = value; }
        }
 
        /// <summary>
        /// Are properties missing from target type ignored? 
        /// </summary>
        /// <remarks>
        /// This also affects responses during SaveChanges.
        /// </remarks>
        public bool IgnoreMissingProperties
        {
            get { return this.ignoreMissingProperties; }
            set { this.ignoreMissingProperties = value; }
        }
 
        /// <summary>Override the namespace used for the data parts of the ATOM entries</summary>
        public string DataNamespace
        {
            get
            {
                return this.dataNamespace;
            }
 
            set
            {
                Util.CheckArgumentNull(value, "value");
                this.dataNamespace = value;
            }
        }
 
        /// <summary>
        /// Enables one to override the default type resolution strategy used by the client library.
        /// Set this property to a delegate which identifies a function that resolves
        /// a type within the client application to a namespace-qualified type name.
        /// This enables the client to perform custom mapping between the type name
        /// provided in a response from the server and a type on the client.
        /// </summary>
        /// <remarks>
        /// This method enables one to override the entity name that is serialized
        /// to the target representation (ATOM,JSON, etc) for the specified type.
        /// </remarks>
        public Func<Type, string> ResolveName
        {
            get { return this.resolveName; }
            set { this.resolveName = value; }
        }
 
        /// <summary>
        /// Enables one to override the default type resolution strategy used by the client library.
        /// Set this property to a delegate which identifies a function that resolves a 
        /// namespace-qualified type name to type within the client application.
        /// This enables the client to perform custom mapping between the type name
        /// provided in a response from the server and a type on the client.
        /// </summary>
        /// <remarks>
        /// Overriding type resolution enables inserting a custom type name to type mapping strategy.
        /// It does not enable one to affect how a response is materialized into the identified type.
        /// </remarks>
        public Func<string, Type> ResolveType
        {
            get { return this.resolveType; }
            set { this.resolveType = value; }
        }
 
#if !ASTORIA_LIGHT  // Timeout not available
        /// <summary>
        /// Get and sets the timeout span in seconds to use for the underlying HTTP request to the data service.
        /// </summary>
        /// <remarks>
        /// A value of 0 will use the default timeout of the underlying HTTP request.
        /// This value must be set before executing any query or update operations against
        /// the target data service for it to have effect on the on the request.
        /// The value may be changed between requests to a data service and the new value
        /// will be picked up by the next data service request.  
        /// </remarks>
        public int Timeout
        {
            get
            {
                return this.timeout;
            }
 
            set
            {
                if (value < 0)
                {
                    throw Error.ArgumentOutOfRange("Timeout");
                }
 
                this.timeout = value;
            }
        }
#endif
 
        /// <summary>Gets or sets the URI used to indicate what type scheme is used by the service.</summary>
        public Uri TypeScheme
        {
            get
            {
                return this.typeScheme;
            }
 
            set
            {
                Util.CheckArgumentNull(value, "value");
                this.typeScheme = value;
            }
        }
 
        /// <summary>whether to use post-tunneling for PUT/DELETE</summary>
        public bool UsePostTunneling
        {
            get { return this.postTunneling; }
            set { this.postTunneling = value; }
        }
 
        /// <summary>
        /// Returns a collection of all the links (ie. associations) currently being tracked by the context.
        /// If no links are being tracked, a collection with 0 elements is returned.
        /// </summary>
        public ReadOnlyCollection<LinkDescriptor> Links
        {
            get
            {
                return this.bindings.Values.OrderBy(l => l.ChangeOrder).ToList().AsReadOnly();
            }
        }
 
        /// <summary>
        /// Returns a collection of all the resources currently being tracked by the context.
        /// If no resources are being tracked, a collection with 0 elements is returned.
        /// </summary>
        public ReadOnlyCollection<EntityDescriptor> Entities
        {
            get
            {
                return this.entityDescriptors.Values.OrderBy(d => d.ChangeOrder).ToList().AsReadOnly();
            }
        }
 
        /// <summary>
        /// Default SaveChangesOptions that needs to be used when doing SaveChanges.
        /// </summary>
        public SaveChangesOptions SaveChangesDefaultOptions
        {
            get
            {
                return this.saveChangesDefaultOptions;
            }
 
            set
            {
                ValidateSaveChangesOptions(value);
                this.saveChangesDefaultOptions = value;
            }
        }
 
        #endregion
 
        /// <summary>
        /// When set to true, client will return an empty set instead of throwing when the
        /// server generates a HTTP 404: Resource Not Found exception
        /// </summary>
        public bool IgnoreResourceNotFoundException
        {
            get { return this.ignoreResourceNotFoundException; }
            set { this.ignoreResourceNotFoundException = value; }
        }
 
#if ASTORIA_LIGHT // Multiple HTTP stacks in Silverlight
        /// <summary>
        /// The HTTP stack to use in Silverlight.
        /// Default value is HttpStack.Auto.
        /// </summary>
        public HttpStack HttpStack
        {
            get { return this.httpStack; }
            set { this.httpStack = Util.CheckEnumerationValue(value, "HttpStack"); }
        }
#endif
 
        /// <summary>base uri with guranteed trailing slash</summary>
        internal Uri BaseUriWithSlash
        {
            get { return this.baseUriWithSlash; }
        }
 
        /// <summary>Indicates if there are subscribers for the ReadingEntity event</summary>
        internal bool HasReadingEntityHandlers
        {
            [DebuggerStepThrough]
            get { return this.ReadingEntity != null; }
        }
 
        #region Entity and Link Tracking
        
        /// <summary>Gets the entity descriptor corresponding to a particular entity</summary>
        /// <param name="entity">Entity for which to find the entity descriptor</param>
        /// <returns>EntityDescriptor for the <paramref name="entity"/> or null if not found</returns>
        public EntityDescriptor GetEntityDescriptor(object entity)
        {
            Util.CheckArgumentNull(entity, "entity");
 
            EntityDescriptor descriptor;
            if (this.entityDescriptors.TryGetValue(entity, out descriptor))
            {
                return descriptor;
            }
            else
            {
                return null;
            }
        }
        
        /// <summary>
        /// Gets the link descriptor corresponding to a particular link b/w source and target objects
        /// </summary>
        /// <param name="source">Source entity</param>
        /// <param name="sourceProperty">Property of <paramref name="source"/></param>
        /// <param name="target">Target entity</param>
        /// <returns>LinkDescriptor for the relationship b/w source and target entities or null if not found</returns>
        public LinkDescriptor GetLinkDescriptor(object source, string sourceProperty, object target)
        {
            Util.CheckArgumentNull(source, "source");
            Util.CheckArgumentNotEmpty(sourceProperty, "sourceProperty");
            Util.CheckArgumentNull(target, "target");
            
            LinkDescriptor link;
            
            if (this.bindings.TryGetValue(new LinkDescriptor(source, sourceProperty, target), out link))
            {
                return link;
            }
            else
            {
                return null;
            }
        }
        
        #endregion
 
        #region CancelRequest
        /// <summary>Best effort to abort the outstand request</summary>
        /// <param name="asyncResult">the current async request to cancel</param>
        /// <remarks>DataServiceContext is not safe to use until asyncResult.IsCompleted is true.</remarks>
        public void CancelRequest(IAsyncResult asyncResult)
        {
            Util.CheckArgumentNull(asyncResult, "asyncResult");
            BaseAsyncResult result = asyncResult as BaseAsyncResult;
 
            // verify this asyncResult orginated from this context or via query from this context
            if ((null == result) || (this != result.Source))
            {
                object context = null;
                DataServiceQuery query = null;
                if (null != result)
                {
                    query = result.Source as DataServiceQuery;
 
                    if (null != query)
                    {
                        DataServiceQueryProvider provider = query.Provider as DataServiceQueryProvider;
                        if (null != provider)
                        {
                            context = provider.Context;
                        }
                    }
                }
 
                if (this != context)
                {
                    throw Error.Argument(Strings.Context_DidNotOriginateAsync, "asyncResult");
                }
            }
 
            // at this point the result originated from this context or a query from this context
            if (!result.IsCompletedInternally)
            {
                result.SetAborted();
 
                WebRequest request = result.Abortable;
                if (null != request)
                {
                    // with Silverlight we can't wait around to check if the request was aborted
                    // because that would block callbacks for the abort from actually running.
                    request.Abort();
                }
            }
        }
        #endregion
 
        #region CreateQuery
        /// <summary>
        /// create a query based on (BaseUri + relativeUri)
        /// </summary>
        /// <typeparam name="T">type of object to materialize</typeparam>
        /// <param name="entitySetName">entitySetName</param>
        /// <returns>composible, enumerable query object</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "required for this feature")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1057:StringUriOverloadsCallSystemUriOverloads", Justification = "required for this feature")]
        public DataServiceQuery<T> CreateQuery<T>(string entitySetName)
        {
            Util.CheckArgumentNotEmpty(entitySetName, "entitySetName");
            this.ValidateEntitySetName(ref entitySetName);
 
            ResourceSetExpression rse = new ResourceSetExpression(typeof(IOrderedQueryable<T>), null, Expression.Constant(entitySetName), typeof(T), null, CountOption.None, null, null);
            return new DataServiceQuery<T>.DataServiceOrderedQuery(rse, new DataServiceQueryProvider(this));
        }
        #endregion
 
        #region GetMetadataUri
        /// <summary>
        /// Given the base URI, resolves the location of the metadata endpoint for the service by using an HTTP OPTIONS request or falling back to convention ($metadata)
        /// </summary>
        /// <returns>Uri to retrieve metadata from</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "required for this feature")]
        public Uri GetMetadataUri()
        {
            // 
            Uri metadataUri = Util.CreateUri(this.baseUriWithSlash.OriginalString + XmlConstants.UriMetadataSegment, UriKind.Absolute);
            return metadataUri;
        }
        #endregion
 
        #region LoadProperty
 
        /// <summary>
        /// Begin getting response to load a collection or reference property.
        /// </summary>
        /// <remarks>actually doesn't modify the property until EndLoadProperty is called.</remarks>
        /// <param name="entity">entity</param>
        /// <param name="propertyName">name of collection or reference property to load</param>
        /// <param name="callback">The AsyncCallback delegate.</param>
        /// <param name="state">The state object for this request.</param>
        /// <returns>An IAsyncResult that references the asynchronous request for a response.</returns>
        public IAsyncResult BeginLoadProperty(object entity, string propertyName, AsyncCallback callback, object state)
        {
            return this.BeginLoadProperty(entity, propertyName, (Uri)null, callback, state);
        }
 
        /// <summary>
        /// Begin getting response to load a page for a collection.
        /// </summary>
        /// <param name="entity">The entity</param>
        /// <param name="propertyName">name of collection or reference property to load</param>
        /// <param name="nextLinkUri">load the page from this URI</param>
        /// <param name="callback">The AsyncCallback delegate.</param>
        /// <param name="state">The state object for this request.</param>
        /// <returns>An IAsyncResult that references the asynchronous request for a response.</returns>
        public IAsyncResult BeginLoadProperty(object entity, string propertyName, Uri nextLinkUri, AsyncCallback callback, object state)
        {
            LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, callback, state, nextLinkUri, null);
            result.BeginExecute();
            return result;
        }
 
        /// <summary>
        /// Begin getting response to load a page for a collection.
        /// </summary>
        /// <param name="entity">The entity</param>
        /// <param name="propertyName">name of collection or reference property to load</param>
        /// <param name="continuation">Continuation from which the property should be loaded.</param>
        /// <param name="callback">The AsyncCallback delegate.</param>
        /// <param name="state">The state object for this request.</param>
        /// <returns>An IAsyncResult that references the asynchronous request for a response.</returns>
        public IAsyncResult BeginLoadProperty(object entity, string propertyName, DataServiceQueryContinuation continuation, AsyncCallback callback, object state)
        {
            Util.CheckArgumentNull(continuation, "continuation");
            LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, callback, state, null, continuation);
            result.BeginExecute();
            return result;
        }
 
        /// <summary>
        /// Load a collection or reference property from a async result.
        /// </summary>
        /// <param name="asyncResult">async result generated by BeginLoadProperty</param>
        /// <returns>QueryOperationResponse instance containing information about the response.</returns>
        public QueryOperationResponse EndLoadProperty(IAsyncResult asyncResult)
        {
            LoadPropertyResult response = QueryResult.EndExecute<LoadPropertyResult>(this, "LoadProperty", asyncResult);
            return response.LoadProperty();
        }
 
#if !ASTORIA_LIGHT // Synchronous methods not available
        /// <summary>
        /// Load a collection or reference property.
        /// </summary>
        /// <remarks>
        /// An entity in detached or added state will throw an InvalidOperationException
        /// since there is nothing it can load from the server.
        /// 
        /// An entity in unchanged or modified state will load its collection or
        /// reference elements as unchanged with unchanged bindings.
        ///
        /// An entity in deleted state will loads its collection or reference elements
        /// in the unchanged state with bindings in the deleted state.
        /// </remarks>
        /// <param name="entity">entity</param>
        /// <param name="propertyName">name of collection or reference property to load</param>
        /// <returns>QueryOperationResponse instance containing information about the response.</returns>
        public QueryOperationResponse LoadProperty(object entity, string propertyName)
        {
            return this.LoadProperty(entity, propertyName, (Uri)null);
        }
 
        /// <summary>
        /// Load a page for collection from an uri
        /// </summary>
        /// <remarks>
        /// An entity in detached or added state will throw an InvalidOperationException
        /// since there is nothing it can load from the server.
        /// 
        /// An entity in unchanged or modified state will load its collection or
        /// reference elements as unchanged with unchanged bindings.
        ///
        /// An entity in deleted state will loads its collection or reference elements
        /// in the unchanged state with bindings in the deleted state.
        /// </remarks>
        /// <param name="entity">entity</param>
        /// <param name="propertyName">name of collection or reference property to load</param>
        /// <param name="nextLinkUri">The uri to load the page from; possibly null.</param>
        /// <returns>QueryOperationResponse instance containing information about the response.</returns>
        public QueryOperationResponse LoadProperty(object entity, string propertyName, Uri nextLinkUri)
        {
            LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, null, null, nextLinkUri, null);
            result.Execute();
            return result.LoadProperty();
        }
 
        /// <summary>
        /// Load a page for collection from an uri
        /// </summary>
        /// <remarks>
        /// An entity in detached or added state will throw an InvalidOperationException
        /// since there is nothing it can load from the server.
        /// 
        /// An entity in unchanged or modified state will load its collection or
        /// reference elements as unchanged with unchanged bindings.
        ///
        /// An entity in deleted state will loads its collection or reference elements
        /// in the unchanged state with bindings in the deleted state.
        /// </remarks>
        /// <param name="entity">entity</param>
        /// <param name="propertyName">Name of collection or reference property to load.</param>
        /// <param name="continuation">The continuation object from a previous response.</param>
        /// <returns>QueryOperationResponse instance containing information about the response.</returns>
        public QueryOperationResponse LoadProperty(object entity, string propertyName, DataServiceQueryContinuation continuation)
        {
            LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, null, null, null, continuation);
            result.Execute();
            return result.LoadProperty();
        }
 
        /// <summary>Loads a page for a collection from a continuation object..</summary>
        /// <remarks>
        /// An entity in detached or added state will throw an InvalidOperationException
        /// since there is nothing it can load from the server.
        /// 
        /// An entity in unchanged or modified state will load its collection or
        /// reference elements as unchanged with unchanged bindings.
        ///
        /// An entity in deleted state will loads its collection or reference elements
        /// in the unchanged state with bindings in the deleted state.
        /// </remarks>
        /// <typeparam name='T'>Element type of collection to load.</typeparam>
        /// <param name="entity">entity</param>
        /// <param name="propertyName">name of collection or reference property to load</param>
        /// <param name="continuation">The continuation object from a previous response.</param>
        /// <returns>QueryOperationResponse instance containing information about the response.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011", Justification = "allows compiler to infer 'T'")]
        public QueryOperationResponse<T> LoadProperty<T>(object entity, string propertyName, DataServiceQueryContinuation<T> continuation)
        {
            LoadPropertyResult result = this.CreateLoadPropertyRequest(entity, propertyName, null, null, null, continuation);
            result.Execute();
            return (QueryOperationResponse<T>)result.LoadProperty();
        }
 
#endif
        #endregion
 
        #region GetReadStreamUri
        /// <summary>
        /// If the specified entity is a Media Link Entry, this method will return
        /// an URI which can be used to access the content of the Media Resource.
        /// This URI should only be used to GET/Read the content of the MR. It may not respond to POST/PUT/DELETE requests.
        /// </summary>
        /// <param name="entity">The entity to lookup the MR URI for.</param>
        /// <returns>The read URI of the MR.</returns>
        /// <exception cref="ArgumentNullException">If the entity specified is null.</exception>
        /// <exception cref="ArgumentException">If the entity specified is not being tracked.</exception>
        public Uri GetReadStreamUri(object entity) 
        {
            EntityDescriptor box = this.EnsureContained(entity, "entity");
            return box.GetMediaResourceUri(this.baseUriWithSlash);
        }
        #endregion
 
        #region GetReadStream, BeginGetReadStream, EndGetReadStream
 
        /// <summary>
        /// This method begins a request to get the read stream for a Media Resource
        /// associated with the media link entry represented by the entity object.
        /// </summary>
        /// <param name="entity">The entity which is the Media Link Entry for the requested Media Resource. Thist must specify
        /// a tracked entity in a non-added state.</param>
        /// <param name="args">Instance of <see cref="DataServiceRequestArgs"/> class with additional metadata for the request.
        /// Must not be null.</param>
        /// <param name="callback">User defined callback to be called when results are available. Can be null.</param>
        /// <param name="state">User state in IAsyncResult. Can be null.</param>
        /// <returns>The async result object to track the request.</returns>
        /// <exception cref="ArgumentNullException">Either entity or args parameters are null.</exception>
        /// <exception cref="ArgumentException">The specified entity is either not tracked, is in the added state or it's not an MLE.</exception>
        public IAsyncResult BeginGetReadStream(object entity, DataServiceRequestArgs args, AsyncCallback callback, object state)
        {
            GetReadStreamResult result;
            result = this.CreateGetReadStreamResult(entity, args, callback, state);
            result.Begin();
            return result;
        }
 
        /// <summary>
        /// Call this method when the results from the request for the Media Resource are required. 
        /// The method will block if the request have not finished yet.
        /// </summary>
        /// <param name="asyncResult">Async result object returned from BeginGetReadStream.</param>
        /// <returns>An instance of <see cref="DataServiceStreamResponse"/> which contains the response stream
        /// as well as its metadata.</returns>
        public DataServiceStreamResponse EndGetReadStream(IAsyncResult asyncResult)
        {
            GetReadStreamResult result = BaseAsyncResult.EndExecute<GetReadStreamResult>(this, "GetReadStream", asyncResult);
            return result.End();
        }
 
#if !ASTORIA_LIGHT
        /// <summary>
        /// This method executes synchronously a request to get the read stream for a Media Resource
        /// associated with the Media Link Entry represented by the entity object.
        /// </summary>
        /// <param name="entity">The entity which is the Media Link Entry for the requested Media Resource. Thist must specify
        /// a tracked entity in a non-added state.</param>
        /// <returns>An instance of <see cref="DataServiceStreamResponse"/> which represents the response.</returns>
        /// <exception cref="ArgumentNullException">entity parameter are null.</exception>
        /// <exception cref="ArgumentException">The specified entity is either not tracked, is in the added state or it's not an MLE.</exception>
        public DataServiceStreamResponse GetReadStream(object entity)
        {
            DataServiceRequestArgs args = new DataServiceRequestArgs();
            return this.GetReadStream(entity, args);
        }
 
        /// <summary>
        /// This method executes synchronously a request to get the read stream for a Media Resource
        /// associated with the Media Link Entry represented by the entity object.
        /// </summary>
        /// <param name="entity">The entity which is the Media Link Entry for the requested Media Resource. Thist must specify
        /// a tracked entity in a non-added state.</param>
        /// <param name="acceptContentType">The content type which should be requested from the server.</param>
        /// <returns>An instance of <see cref="DataServiceStreamResponse"/> which represents the response.</returns>
        /// <exception cref="ArgumentNullException">Either entity or contentType parameters are null.</exception>
        /// <exception cref="ArgumentException">The specified entity is either not tracked, is in the added state or it's not an MLE.
        /// Or the contentType is an empty string.</exception>
        public DataServiceStreamResponse GetReadStream(object entity, string acceptContentType)
        {
            Util.CheckArgumentNotEmpty(acceptContentType, "acceptContentType");
            DataServiceRequestArgs args = new DataServiceRequestArgs();
            args.AcceptContentType = acceptContentType;
            return this.GetReadStream(entity, args);
        }
 
        /// <summary>
        /// This method executes synchronously a request to get the read stream for a Media Resource
        /// associated with the Media Link Entry represented by the entity object.
        /// </summary>
        /// <param name="entity">The entity which is the Media Link Entry for the requested Media Resource. Thist must specify
        /// a tracked entity in a non-added state.</param>
        /// <param name="args">Instance of <see cref="DataServiceRequestArgs"/> class with additional metadata for the request.
        /// Must not be null.</param>
        /// <returns>An instance of <see cref="DataServiceStreamResponse"/> which represents the response.</returns>
        /// <exception cref="ArgumentNullException">Either entity or args parameters are null.</exception>
        /// <exception cref="ArgumentException">The specified entity is either not tracked, is in the added state or it's not an MLE.</exception>
        public DataServiceStreamResponse GetReadStream(object entity, DataServiceRequestArgs args)
        {
            GetReadStreamResult result = this.CreateGetReadStreamResult(entity, args, null, null);
            return result.Execute();
        }
 
#endif
        #endregion
 
        #region SetSaveStream
 
        /// <summary>
        /// Sets a new content for a Media Resource associated with the specified Media Link Entry entity.
        /// </summary>
        /// <param name="entity">The entity (MLE) for which to set the MR content.</param>
        /// <param name="stream">The stream from which to read the content. The stream will be read to its end.
        /// No Seek operation will be tried on the stream.</param>
        /// <param name="closeStream">If set to true SaveChanges will close the stream before it returns. It will close the stream
        /// even if it failed (throws) and even if it didn't get to use the stream.</param>
        /// <param name="contentType">The Content-Type header value to set for the MR request. The value is not validated
        /// in any way and it's the responsibility of the user to make sure it's a valid value for Content-Type header.</param>
        /// <param name="slug">The Slug header value to set for the MR request. The value is not validated in any way 
        /// and it's the responsibility of the user to make usre it's a valid value for Slug header.</param>
        /// <remarks>Calling this method marks the entity as media link resource (MLE). It also marks the entity as modified
        /// so that it will participate in the next call to SaveChanges.</remarks>
        /// <exception cref="ArgumentException">The entity is not being tracked or the contentType is an empty string. 
        /// The entity has the MediaEntry attribute marking it to use the older way of handling MRs.</exception>
        /// <exception cref="ArgumentNullException">Any of the arguments is null.</exception>
        public void SetSaveStream(object entity, Stream stream, bool closeStream, string contentType, string slug)
        {
            Util.CheckArgumentNull(contentType, "contentType");
            Util.CheckArgumentNull(slug, "slug");
 
            DataServiceRequestArgs args = new DataServiceRequestArgs();
            args.ContentType = contentType;
            args.Slug = slug;
            this.SetSaveStream(entity, stream, closeStream, args);
        }
 
        /// <summary>
        /// Sets a new content for a Media Resource associated with the specified Media Link Entry entity.
        /// </summary>
        /// <param name="entity">The entity (MLE) for which to set the MR content.</param>
        /// <param name="stream">The stream from which to read the content. The stream will be read to its end.
        /// No Seek operation will be tried on the stream.</param>
        /// <param name="closeStream">If set to true SaveChanges will close the stream before it returns. It will close the stream
        /// even if it failed (throws) and even if it didn't get to use the stream.</param>
        /// <param name="args">Instance of <see cref="DataServiceRequestArgs"/> class with additional metadata for the MR request.
        /// Must not be null.</param>
        /// <remarks>Calling this method marks the entity as media link resource (MLE). It also marks the entity as modified
        /// so that it will participate in the next call to SaveChanges.</remarks>
        /// <exception cref="ArgumentException">The entity is not being tracked. The entity has the MediaEntry attribute
        /// marking it to use the older way of handling MRs.</exception>
        /// <exception cref="ArgumentNullException">Any of the arguments is null.</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "parameters are validated against null via CheckArgumentNull")]
        public void SetSaveStream(object entity, Stream stream, bool closeStream, DataServiceRequestArgs args)
        {
            EntityDescriptor box = this.EnsureContained(entity, "entity");
            Util.CheckArgumentNull(stream, "stream");
            Util.CheckArgumentNull(args, "args");
 
            ClientType clientType = ClientType.Create(entity.GetType());
            if (clientType.MediaDataMember != null)
            { 
                throw new ArgumentException(
                    Strings.Context_SetSaveStreamOnMediaEntryProperty(clientType.ElementTypeName), 
                    "entity");
            }
 
            box.SaveStream = new DataServiceSaveStream(stream, closeStream, args);
 
            Debug.Assert(box.State != EntityStates.Detached, "We should never have a detached entity in the entityDescriptor dictionary.");
            switch (box.State)
            {
                case EntityStates.Added:
                    box.StreamState = StreamStates.Added;
                    break;
 
                case EntityStates.Modified:
                case EntityStates.Unchanged:
                    box.StreamState = StreamStates.Modified;
                    break;
 
                case EntityStates.Deleted:
                default:
                    // 
 
                    throw new DataServiceClientException(Strings.DataServiceException_GeneralError);
            }
 
            // Note that there's no need to mark the entity as updated because we consider the presense
            // of the save stream as the mark that the MR for this MLE has been updated.
        }
 
        #endregion
 
        #region ExecuteBatch, BeginExecuteBatch, EndExecuteBatch
 
        /// <summary>
        /// Batch multiple queries into a single async request.
        /// </summary>
        /// <param name="callback">User callback when results from batch are available.</param>
        /// <param name="state">user state in IAsyncResult</param>
        /// <param name="queries">queries to batch</param>
        /// <returns>async result object</returns>
        public IAsyncResult BeginExecuteBatch(AsyncCallback callback, object state, params DataServiceRequest[] queries)
        {
            Util.CheckArgumentNotEmpty(queries, "queries");
 
            SaveResult result = new SaveResult(this, "ExecuteBatch", queries, SaveChangesOptions.Batch, callback, state, true);
            result.BatchBeginRequest(false /*replaceOnUpdate*/);
            return result;
        }
 
        /// <summary>
        /// Call when results from batch are desired.
        /// </summary>
        /// <param name="asyncResult">async result object returned from BeginExecuteBatch</param>
        /// <returns>batch response from which query results can be enumerated.</returns>
        public DataServiceResponse EndExecuteBatch(IAsyncResult asyncResult)
        {
            SaveResult result = BaseAsyncResult.EndExecute<SaveResult>(this, "ExecuteBatch", asyncResult);
            return result.EndRequest();
        }
 
#if !ASTORIA_LIGHT // Synchronous methods not available
        /// <summary>
        /// Batch multiple queries into a single request.
        /// </summary>
        /// <param name="queries">queries to batch</param>
        /// <returns>batch response from which query results can be enumerated.</returns>
        public DataServiceResponse ExecuteBatch(params DataServiceRequest[] queries)
        {
            Util.CheckArgumentNotEmpty(queries, "queries");
 
            SaveResult result = new SaveResult(this, "ExecuteBatch", queries, SaveChangesOptions.Batch, null, null, false);
            result.BatchRequest(false /*replaceOnUpdate*/);
            return result.EndRequest();
        }
#endif
 
        #endregion
 
        #region Execute(Uri), BeginExecute(Uri), EndExecute(Uri)
 
        /// <summary>Begins the execution of the request uri.</summary>
        /// <typeparam name="TElement">element type of the result</typeparam>
        /// <param name="requestUri">request to execute</param>
        /// <param name="callback">User callback when results from execution are available.</param>
        /// <param name="state">user state in IAsyncResult</param>
        /// <returns>async result object</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")]
        public IAsyncResult BeginExecute<TElement>(Uri requestUri, AsyncCallback callback, object state)
        {
            requestUri = Util.CreateUri(this.baseUriWithSlash, requestUri);
            QueryComponents qc = new QueryComponents(requestUri, Util.DataServiceVersionEmpty, typeof(TElement), null, null);
            return (new DataServiceRequest<TElement>(qc, null)).BeginExecute(this, this, callback, state);
        }
 
        /// <summary>Begins the execution of spscified continuation.</summary>
        /// <typeparam name="T">Element type of the result</typeparam>
        /// <param name="continuation">Conti----ation to execute.</param>
        /// <param name="callback">User callback when results from execution are available.</param>
        /// <param name="state">user state in IAsyncResult</param>
        /// <returns>async result object</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "parameters are validated against null via CheckArgumentNull")]
        public IAsyncResult BeginExecute<T>(DataServiceQueryContinuation<T> continuation, AsyncCallback callback, object state)
        {
            Util.CheckArgumentNull(continuation, "continuation");
            QueryComponents qc = continuation.CreateQueryComponents();
            return (new DataServiceRequest<T>(qc, continuation.Plan)).BeginExecute(this, this, callback, state);
        }
 
        /// <summary>
        /// Call when results from batch are desired.
        /// </summary>
        /// <typeparam name="TElement">element type of the result</typeparam>
        /// <param name="asyncResult">async result object returned from BeginExecuteBatch</param>
        /// <returns>batch response from which query results can be enumerated.</returns>
        /// <exception cref="ArgumentNullException">asyncResult is null</exception>
        /// <exception cref="ArgumentException">asyncResult did not originate from this instance or End was previously called</exception>
        /// <exception cref="InvalidOperationException">problem in request or materializing results of query into objects</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")]
        public IEnumerable<TElement> EndExecute<TElement>(IAsyncResult asyncResult)
        {
            Util.CheckArgumentNull(asyncResult, "asyncResult");
            return DataServiceRequest.EndExecute<TElement>(this, this, asyncResult);
        }
 
#if !ASTORIA_LIGHT // Synchronous methods not available
        /// <summary>
        /// Execute the requestUri
        /// </summary>
        /// <typeparam name="TElement">element type of the result</typeparam>
        /// <param name="requestUri">request uri to execute</param>
        /// <returns>batch response from which query results can be enumerated.</returns>
        /// <exception cref="ArgumentNullException">null requestUri</exception>
        /// <exception cref="ArgumentException">!BaseUri.IsBaseOf(requestUri)</exception>
        /// <exception cref="InvalidOperationException">problem materializing results of query into objects</exception>
        /// <exception cref="WebException">failure to get response for requestUri</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "Type is used to infer result")]
        public IEnumerable<TElement> Execute<TElement>(Uri requestUri)
        {
            requestUri = Util.CreateUri(this.baseUriWithSlash, requestUri);
            QueryComponents qc = new QueryComponents(requestUri, Util.DataServiceVersionEmpty, typeof(TElement), null, null);
            DataServiceRequest request = new DataServiceRequest<TElement>(qc, null);
            return request.Execute<TElement>(this, qc);
        }
 
        /// <summary>Executes the specified <paramref name="continuation"/>.</summary>
        /// <typeparam name="T">Element type for response.</typeparam>
        /// <param name="continuation">Continuation for query to execute.</param>
        /// <returns>The response for the specified <paramref name="continuation"/>.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "parameters are validated against null via CheckArgumentNull")]
        public QueryOperationResponse<T> Execute<T>(DataServiceQueryContinuation<T> continuation)
        {
            Util.CheckArgumentNull(continuation, "continuation");
            QueryComponents qc = continuation.CreateQueryComponents();
            DataServiceRequest request = new DataServiceRequest<T>(qc, continuation.Plan);
            return request.Execute<T>(this, qc);
        }
#endif
        #endregion
 
        #region SaveChanges, BeginSaveChanges, EndSaveChanges
 
        /// <summary>
        /// submit changes to the server in a single change set
        /// </summary>
        /// <param name="callback">callback</param>
        /// <param name="state">state</param>
        /// <returns>async result</returns>
        public IAsyncResult BeginSaveChanges(AsyncCallback callback, object state)
        {
            return this.BeginSaveChanges(this.SaveChangesDefaultOptions, callback, state);
        }
 
        /// <summary>
        /// begin submitting changes to the server
        /// </summary>
        /// <param name="options">options on how to save changes</param>
        /// <param name="callback">The AsyncCallback delegate.</param>
        /// <param name="state">The state object for this request.</param>
        /// <returns>An IAsyncResult that references the asynchronous request for a response.</returns>
        /// <remarks>
        /// BeginSaveChanges will asynchronously attach identity Uri returned by server to sucessfully added entites.
        /// EndSaveChanges will apply updated values to entities, raise ReadingEntity events and change entity states.
        /// </remarks>
        public IAsyncResult BeginSaveChanges(SaveChangesOptions options, AsyncCallback callback, object state)
        {
            ValidateSaveChangesOptions(options);
            SaveResult result = new SaveResult(this, "SaveChanges", null, options, callback, state, true);
            bool replaceOnUpdate = IsFlagSet(options, SaveChangesOptions.ReplaceOnUpdate);
            if (IsFlagSet(options, SaveChangesOptions.Batch))
            {
                result.BatchBeginRequest(replaceOnUpdate);
            }
            else
            {
                result.BeginNextChange(replaceOnUpdate); // may invoke callback before returning
            }
 
            return result;
        }
 
        /// <summary>
        /// done submitting changes to the server
        /// </summary>
        /// <param name="asyncResult">The pending request for a response. </param>
        /// <returns>changeset response</returns>
        public DataServiceResponse EndSaveChanges(IAsyncResult asyncResult)
        {
            SaveResult result = BaseAsyncResult.EndExecute<SaveResult>(this, "SaveChanges", asyncResult);
            
            DataServiceResponse errors = result.EndRequest();
 
            if (this.ChangesSaved != null)
            {
                this.ChangesSaved(this, new SaveChangesEventArgs(errors));
            }
 
            return errors;
        }
 
#if !ASTORIA_LIGHT // Synchronous methods not available
        /// <summary>
        /// submit changes to the server in a single change set
        /// </summary>
        /// <returns>changeset response</returns>
        public DataServiceResponse SaveChanges()
        {
            return this.SaveChanges(this.SaveChangesDefaultOptions);
        }
 
        /// <summary>
        /// submit changes to the server
        /// </summary>
        /// <param name="options">options on how to save changes</param>
        /// <returns>changeset response</returns>
        /// <remarks>
        /// MergeOption.NoTracking is tricky but supported because to insert a relationship we need the identity
        /// of both ends and if one end was an inserted object then its identity is attached, but may not match its value
        /// 
        /// This initial implementation does not do batching.
        /// Things are sent to the server in the following order
        /// 1) delete relationships
        /// 2) delete objects
        /// 3) update objects
        /// 4) insert objects
        /// 5) insert relationship
        /// </remarks>
        public DataServiceResponse SaveChanges(SaveChangesOptions options)
        {
            DataServiceResponse errors = null;
            ValidateSaveChangesOptions(options);
 
            SaveResult result = new SaveResult(this, "SaveChanges", null, options, null, null, false);
            bool replaceOnUpdate = IsFlagSet(options, SaveChangesOptions.ReplaceOnUpdate);
            if (IsFlagSet(options, SaveChangesOptions.Batch))
            {
                result.BatchRequest(replaceOnUpdate);
            }
            else
            {
                result.BeginNextChange(replaceOnUpdate);
            }
 
            errors = result.EndRequest();
 
            Debug.Assert(null != errors, "null errors");
 
            if (this.ChangesSaved != null)
            {
                this.ChangesSaved(this, new SaveChangesEventArgs(errors));
            }
 
            return errors;
        }
#endif
        #endregion
 
        #region Add, Attach, Delete, Detach, Update, TryGetEntity, TryGetUri
 
        /// <summary>
        /// Notifies the context that a new link exists between the <paramref name="source"/> and <paramref name="target"/> objects
        /// and that the link is represented via the source.<paramref name="sourceProperty"/> which is a collection.
        /// The context adds this link to the set of newly created links to be sent to
        /// the data service on the next call to SaveChanges().
        /// </summary>
        /// <remarks>
        /// Links are one way relationships.  If a back pointer exists (ie. two way association),
        /// this method should be called a second time to notify the context object of the second link.
        /// </remarks>
        /// <param name="source">Source object participating in the link.</param>
        /// <param name="sourceProperty">The name of the property on the source object which represents a link from the source to the target object.</param>
        /// <param name="target">The target object involved in the link which is bound to the source object also specified in this call.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="source"/>, <paramref name="sourceProperty"/> or <paramref name="target"/> are null.</exception>
        /// <exception cref="InvalidOperationException">if link already exists</exception>
        /// <exception cref="InvalidOperationException">if source or target are detached</exception>
        /// <exception cref="InvalidOperationException">if source or target are in deleted state</exception>
        /// <exception cref="InvalidOperationException">if sourceProperty is not a collection</exception>
        public void AddLink(object source, string sourceProperty, object target)
        {
            this.EnsureRelatable(source, sourceProperty, target, EntityStates.Added);
 
            LinkDescriptor relation = new LinkDescriptor(source, sourceProperty, target);
            if (this.bindings.ContainsKey(relation))
            {
                throw Error.InvalidOperation(Strings.Context_RelationAlreadyContained);
            }
 
            relation.State = EntityStates.Added;
            this.bindings.Add(relation, relation);
            this.IncrementChange(relation);
        }
 
        /// <summary>
        /// Notifies the context to start tracking the specified link between source and the specified target entity.
        /// </summary>
        /// <param name="source">Source object participating in the link.</param>
        /// <param name="sourceProperty">The name of the property on the source object which represents a link from the source to the target object.</param>
        /// <param name="target">The target object involved in the link which is bound to the source object also specified in this call.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="source"/>, <paramref name="sourceProperty"/> or <paramref name="target"/> are null.</exception>
        /// <exception cref="InvalidOperationException">if binding already exists</exception>
        /// <exception cref="InvalidOperationException">if source or target are in added state</exception>
        /// <exception cref="InvalidOperationException">if source or target are in deleted state</exception>
        public void AttachLink(object source, string sourceProperty, object target)
        {
            this.AttachLink(source, sourceProperty, target, MergeOption.NoTracking);
        }
 
        /// <summary>
        /// Removes the specified link from the list of links being tracked by the context.
        /// Any link being tracked by the context, regardless of its current state, can be detached.   
        /// </summary>
        /// <param name="source">Source object participating in the link.</param>
        /// <param name="sourceProperty">The name of the property on the source object which represents a link from the source to the target object.</param>
        /// <param name="target">The target object involved in the link which is bound to the source object also specified in this call.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="source"/> or <paramref name="sourceProperty"/> are null.</exception>
        /// <exception cref="ArgumentException">if sourceProperty is empty</exception>
        /// <returns>true if binding was previously being tracked, false if not</returns>
        public bool DetachLink(object source, string sourceProperty, object target)
        {
            Util.CheckArgumentNull(source, "source");
            Util.CheckArgumentNotEmpty(sourceProperty, "sourceProperty");
 
            LinkDescriptor existing;
            LinkDescriptor relation = new LinkDescriptor(source, sourceProperty, target);
            if (!this.bindings.TryGetValue(relation, out existing))
            {
                return false;
            }
 
            this.DetachExistingLink(existing, false);
            return true;
        }
 
        /// <summary>
        /// Notifies the context that a link exists between the <paramref name="source"/> and <paramref name="target"/> object
        /// and that the link is represented via the source.<paramref name="sourceProperty"/> which is a collection.
        /// The context adds this link to the set of deleted links to be sent to
        /// the data service on the next call to SaveChanges().
        /// If the specified link exists in the "Added" state, then the link is detached (see DetachLink method) instead.
        /// </summary>
        /// <param name="source">Source object participating in the link.</param>
        /// <param name="sourceProperty">The name of the property on the source object which represents a link from the source to the target object.</param>
        /// <param name="target">The target object involved in the link which is bound to the source object also specified in this call.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="source"/>, <paramref name="sourceProperty"/> or <paramref name="target"/> are null.</exception>
        /// <exception cref="InvalidOperationException">if source or target are detached</exception>
        /// <exception cref="InvalidOperationException">if source or target are in added state</exception>
        /// <exception cref="InvalidOperationException">if sourceProperty is not a collection</exception>
        public void DeleteLink(object source, string sourceProperty, object target)
        {
            bool delay = this.EnsureRelatable(source, sourceProperty, target, EntityStates.Deleted);
 
            LinkDescriptor existing = null;
            LinkDescriptor relation = new LinkDescriptor(source, sourceProperty, target);
            if (this.bindings.TryGetValue(relation, out existing) && (EntityStates.Added == existing.State))
            {   // Added -> Detached
                this.DetachExistingLink(existing, false);
            }
            else
            {
                if (delay)
                {   // can't have non-added relationship when source or target is in added state
                    throw Error.InvalidOperation(Strings.Context_NoRelationWithInsertEnd);
                }
 
                if (null == existing)
                {   // detached -> deleted
                    this.bindings.Add(relation, relation);
                    existing = relation;
                }
 
                if (EntityStates.Deleted != existing.State)
                {
                    existing.State = EntityStates.Deleted;
 
                    // It is the users responsibility to delete the link
                    // before deleting the entity when required.
                    this.IncrementChange(existing);
                }
            }
        }
 
        /// <summary>
        /// Notifies the context that a modified link exists between the <paramref name="source"/> and <paramref name="target"/> objects
        /// and that the link is represented via the source.<paramref name="sourceProperty"/> which is a reference.
        /// The context adds this link to the set of modified created links to be sent to
        /// the data service on the next call to SaveChanges().
        /// </summary>
        /// <remarks>
        /// Links are one way relationships.  If a back pointer exists (ie. two way association),
        /// this method should be called a second time to notify the context object of the second link.
        /// </remarks>
        /// <param name="source">Source object participating in the link.</param>
        /// <param name="sourceProperty">The name of the property on the source object which represents a link from the source to the target object.</param>
        /// <param name="target">The target object involved in the link which is bound to the source object also specified in this call.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="source"/>, <paramref name="sourceProperty"/> or <paramref name="target"/> are null.</exception>
        /// <exception cref="InvalidOperationException">if link already exists</exception>
        /// <exception cref="InvalidOperationException">if source or target are detached</exception>
        /// <exception cref="InvalidOperationException">if source or target are in deleted state</exception>
        /// <exception cref="InvalidOperationException">if sourceProperty is not a reference property</exception>
        public void SetLink(object source, string sourceProperty, object target)
        {
            this.EnsureRelatable(source, sourceProperty, target, EntityStates.Modified);
 
            LinkDescriptor relation = this.DetachReferenceLink(source, sourceProperty, target, MergeOption.NoTracking);
            if (null == relation)
            {
                relation = new LinkDescriptor(source, sourceProperty, target);
                this.bindings.Add(relation, relation);
            }
 
            Debug.Assert(
                0 == relation.State ||
                IncludeLinkState(relation.State),
                "set link entity state");
 
            if (EntityStates.Modified != relation.State)
            {
                relation.State = EntityStates.Modified;
                this.IncrementChange(relation);
            }
        }
 
        #endregion
 
        #region AddObject, AttachTo, DeleteObject, Detach, TryGetEntity, TryGetUri
        /// <summary>
        /// Add entity into the context in the Added state for tracking.
        /// It does not follow the object graph and add related objects.
        /// </summary>
        /// <param name="entitySetName">EntitySet for the object to be added.</param>
        /// <param name="entity">entity graph to add</param>
        /// <exception cref="ArgumentNullException">if entitySetName is null</exception>
        /// <exception cref="ArgumentException">if entitySetName is empty</exception>
        /// <exception cref="ArgumentNullException">if entity is null</exception>
        /// <exception cref="ArgumentException">if entity does not have a key property</exception>
        /// <exception cref="InvalidOperationException">if entity is already being tracked by the context</exception>
        /// <remarks>
        /// Any leading or trailing forward slashes will automatically be trimmed from entitySetName.
        /// </remarks>
        public void AddObject(string entitySetName, object entity)
        {
            this.ValidateEntitySetName(ref entitySetName);
            ValidateEntityType(entity);
 
            EntityDescriptor resource = new EntityDescriptor(null, null /*selfLink*/, null /*editLink*/, entity, null, null, entitySetName, null, EntityStates.Added);
 
            try
            {
                this.entityDescriptors.Add(entity, resource);
            }
            catch (ArgumentException)
            {
                throw Error.InvalidOperation(Strings.Context_EntityAlreadyContained);
            }
 
            this.IncrementChange(resource);
        }
 
        /// <summary>
        /// This API enables adding the target object and setting the link between the source object and target object in one request.
        /// </summary>
        /// <param name="source">the parent object which is already tracked by the context.</param>
        /// <param name="sourceProperty">The name of the navigation property which forms the association between the source and target.</param>
        /// <param name="target">the target object which needs to be added.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "parameters are validated against null via CheckArgumentNull")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2", Justification = "parameters are validated against null via CheckArgumentNull")]
        public void AddRelatedObject(object source, string sourceProperty, object target)
        {
            Util.CheckArgumentNull(source, "source");
            Util.CheckArgumentNotEmpty(sourceProperty, "propertyName");
            Util.CheckArgumentNull(target, "target");
 
            // Validate that the source is an entity and is already tracked by the context.
            ValidateEntityType(source);
 
            EntityDescriptor sourceResource = this.EnsureContained(source, "source");
 
            // Check for deleted source entity
            if (sourceResource.State == EntityStates.Deleted)
            {
                throw Error.InvalidOperation(Strings.Context_AddRelatedObjectSourceDeleted);
            }
 
            // Validate that the property is valid and exists on the source
            ClientType parentType = ClientType.Create(source.GetType());
            ClientType.ClientProperty property = parentType.GetProperty(sourceProperty, false);
            if (property.IsKnownType || property.CollectionType == null)
            {
                throw Error.InvalidOperation(Strings.Context_AddRelatedObjectCollectionOnly);
            }
 
            // Validate that the target is an entity
            ClientType childType = ClientType.Create(target.GetType());
            ValidateEntityType(target);
 
            // Validate that the property type matches with the target type
            ClientType propertyElementType = ClientType.Create(property.CollectionType);
            if (!propertyElementType.ElementType.IsAssignableFrom(childType.ElementType))
            {
                // target is not of the correct type
                throw Error.Argument(Strings.Context_RelationNotRefOrCollection, "target");
            }
 
            EntityDescriptor targetResource = new EntityDescriptor(null, null, null, target, sourceResource, sourceProperty, null /*entitySetName*/, null, EntityStates.Added);
 
            try
            {
                this.entityDescriptors.Add(target, targetResource);
            }
            catch (ArgumentException)
            {
                throw Error.InvalidOperation(Strings.Context_EntityAlreadyContained);
            }
 
            // Add the link in the added state.
            LinkDescriptor end = targetResource.GetRelatedEnd();
            end.State = EntityStates.Added;
            this.bindings.Add(end, end);
 
            this.IncrementChange(targetResource);
        }
 
        /// <summary>
        /// Attach entity into the context in the Unchanged state for tracking.
        /// It does not follow the object graph and attach related objects.
        /// </summary>
        /// <param name="entitySetName">EntitySet for the object to be attached.</param>        
        /// <param name="entity">entity graph to attach</param>
        /// <exception cref="ArgumentNullException">if entitySetName is null</exception>
        /// <exception cref="ArgumentException">if entitySetName is empty</exception>
        /// <exception cref="ArgumentNullException">if entity is null</exception>
        /// <exception cref="ArgumentException">if entity does not have a key property</exception>
        /// <exception cref="InvalidOperationException">if entity is already being tracked by the context</exception>
        public void AttachTo(string entitySetName, object entity)
        {
            this.AttachTo(entitySetName, entity, null);
        }
 
        /// <summary>
        /// Attach entity into the context in the Unchanged state for tracking.
        /// It does not follow the object graph and attach related objects.
        /// </summary>
        /// <param name="entitySetName">EntitySet for the object to be attached.</param>        
        /// <param name="entity">entity graph to attach</param>
        /// <param name="etag">etag</param>
        /// <exception cref="ArgumentNullException">if entitySetName is null</exception>
        /// <exception cref="ArgumentException">if entitySetName is empty</exception>
        /// <exception cref="ArgumentNullException">if entity is null</exception>
        /// <exception cref="ArgumentException">if entity does not have a key property</exception>
        /// <exception cref="InvalidOperationException">if entity is already being tracked by the context</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704", MessageId = "etag", Justification = "represents ETag in request")]
        public void AttachTo(string entitySetName, object entity, string etag)
        {
            this.ValidateEntitySetName(ref entitySetName);
            Uri editLink = GenerateEditLinkUri(this.baseUriWithSlash, entitySetName, entity);
 
            // we fake the identity by using the generated edit link
            // ReferenceIdentity is a test hook to help verify we dont' use identity instead of editLink
            String identity = Util.ReferenceIdentity(editLink.AbsoluteUri);
 
            // EntitySetName need to be cached only when the entity is in added state. For all other states, entities self/edit link must be used.
            EntityDescriptor descriptor = new EntityDescriptor(identity, null /* selfLink */, editLink, entity, null /* parent */, null /* parent property */, null /*entitySetName*/, etag, EntityStates.Unchanged);
            this.InternalAttachEntityDescriptor(descriptor, true);
        }
 
        /// <summary>
        /// Mark an existing object being tracked by the context for deletion.
        /// </summary>
        /// <param name="entity">entity to be mark deleted</param>
        /// <exception cref="ArgumentNullException">if entity is null</exception>
        /// <exception cref="InvalidOperationException">if entity is not being tracked by the context</exception>
        /// <remarks>
        /// Existings objects in the Added state become detached.
        /// </remarks>
        public void DeleteObject(object entity)
        {
            Util.CheckArgumentNull(entity, "entity");
 
            EntityDescriptor resource = null;
            if (!this.entityDescriptors.TryGetValue(entity, out resource))
            {   // detached object
                throw Error.InvalidOperation(Strings.Context_EntityNotContained);
            }
 
            EntityStates state = resource.State;
            if (EntityStates.Added == state)
            {   // added -> detach
                this.DetachResource(resource);
            }
            else if (EntityStates.Deleted != state)
            {
                Debug.Assert(
                    IncludeLinkState(state),
                    "bad state transition to deleted");
 
                // Leave related links alone which means we can have a link in the Added
                // or Modified state referencing a source/target entity in the Deleted state.
                resource.State = EntityStates.Deleted;
                this.IncrementChange(resource);
            }
        }
 
        /// <summary>
        /// Detach entity from the context.
        /// </summary>
        /// <param name="entity">entity to detach.</param>
        /// <returns>true if object was detached</returns>
        /// <exception cref="ArgumentNullException">if entity is null</exception>
        public bool Detach(object entity)
        {
            Util.CheckArgumentNull(entity, "entity");
 
            EntityDescriptor resource = null;
            if (this.entityDescriptors.TryGetValue(entity, out resource))
            {
                return this.DetachResource(resource);
            }
 
            return false;
        }
 
        /// <summary>
        /// Mark an existing object for update in the context.
        /// </summary>
        /// <param name="entity">entity to be mark for update</param>
        /// <exception cref="ArgumentNullException">if entity is null</exception>
        /// <exception cref="ArgumentException">if entity is detached</exception>
        public void UpdateObject(object entity)
        {
            Util.CheckArgumentNull(entity, "entity");
 
            EntityDescriptor resource = null;
            if (!this.entityDescriptors.TryGetValue(entity, out resource))
            {
                throw Error.Argument(Strings.Context_EntityNotContained, "entity");
            }
 
            if (EntityStates.Unchanged == resource.State)
            {
                resource.State = EntityStates.Modified;
                this.IncrementChange(resource);
            }
        }
 
        /// <summary>
        /// Find tracked entity by its identity.
        /// </summary>
        /// <remarks>entities in added state are not likely to have a identity</remarks>
        /// <typeparam name="TEntity">entity type</typeparam>
        /// <param name="identity">identity</param>
        /// <param name="entity">entity being tracked by context</param>
        /// <returns>true if entity was found</returns>
        /// <exception cref="ArgumentNullException">identity is null</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "parameters are validated against null via CheckArgumentNull")]
        public bool TryGetEntity<TEntity>(Uri identity, out TEntity entity) where TEntity : class
        {
            entity = null;
            Util.CheckArgumentNull(identity, "relativeUri");
 
            EntityStates state;
 
            // ReferenceIdentity is a test hook to help verify we dont' use identity instead of editLink
            entity = (TEntity)this.TryGetEntity(Util.ReferenceIdentity(CommonUtil.UriToString(identity)), null, MergeOption.AppendOnly, out state);
            return (null != entity);
        }
 
        /// <summary>
        /// Identity uri for tracked entity.
        /// Though the identity might use a dereferencable scheme, you MUST NOT assume it can be dereferenced.
        /// </summary>
        /// <remarks>Entities in added state are not likely to have an identity.</remarks>
        /// <param name="entity">entity being tracked by context</param>
        /// <param name="identity">identity</param>
        /// <returns>true if entity is being tracked and has a identity</returns>
        /// <exception cref="ArgumentNullException">entity is null</exception>
        public bool TryGetUri(object entity, out Uri identity)
        {
            identity = null;
            Util.CheckArgumentNull(entity, "entity");
 
            // if the entity's identity does not map back to the entity, don't return it
            EntityDescriptor resource = null;
            if ((null != this.identityToDescriptor) &&
                this.entityDescriptors.TryGetValue(entity, out resource) &&
                (null != resource.Identity) &&
                Object.ReferenceEquals(resource, this.identityToDescriptor[resource.Identity]))
            {
                // DereferenceIdentity is a test hook to help verify we dont' use identity instead of editLink
                string identityUri = Util.DereferenceIdentity(resource.Identity);
                identity = Util.CreateUri(identityUri, UriKind.Absolute);
            }
 
            return (null != identity);
        }
 
        /// <summary>
        /// Handle response by looking at status and possibly throwing an exception.
        /// </summary>
        /// <param name="statusCode">response status code</param>
        /// <param name="responseVersion">Version string on the response header; possibly null.</param>
        /// <param name="getResponseStream">delegate to get response stream</param>
        /// <param name="throwOnFailure">throw or return on failure</param>
        /// <returns>exception on failure</returns>
        internal static Exception HandleResponse(
            HttpStatusCode statusCode,
            string responseVersion,
            Func<Stream> getResponseStream,
            bool throwOnFailure)
        {
            InvalidOperationException failure = null;
            if (!CanHandleResponseVersion(responseVersion))
            {
                string description = Strings.Context_VersionNotSupported(
                    responseVersion,
                    SerializeSupportedVersions());
 
                failure = Error.InvalidOperation(description);
            }
 
            if (failure == null && !WebUtil.SuccessStatusCode(statusCode))
            {
                failure = GetResponseText(getResponseStream, statusCode);
            }
 
            if (failure != null && throwOnFailure)
            {
                throw failure;
            }
 
            return failure;
        }
 
        /// <summary>
        /// get the response text into a string
        /// </summary>
        /// <param name="getResponseStream">method to get response stream</param>
        /// <param name="statusCode">status code</param>
        /// <returns>text</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031", Justification = "Cache exception so user can examine it later")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "underlying stream is disposed so wrapping StreamReader doesn't need to be disposed")]
        internal static DataServiceClientException GetResponseText(Func<Stream> getResponseStream, HttpStatusCode statusCode)
        {
            string message = null;
            using (System.IO.Stream stream = getResponseStream())
            {
                if ((null != stream) && stream.CanRead)
                {
                    // this StreamReader can go out of scope without dispose because the underly stream is disposed of
                    message = new StreamReader(stream).ReadToEnd();
                }
            }
 
            if (String.IsNullOrEmpty(message))
            {
                message = statusCode.ToString();
            }
 
            return new DataServiceClientException(message, (int)statusCode);
        }
 
        /// <summary>response materialization has an identity to attach to the inserted object</summary>
        /// <param name="identity">identity of entity</param>
        /// <param name="selfLink">query link of entity</param>
        /// <param name="editLink">edit link of entity</param>
        /// <param name="entity">inserted object</param>
        /// <param name="etag">etag of attached object</param>
        internal void AttachIdentity(String identity, Uri selfLink, Uri editLink, object entity, string etag)
        {   // insert->unchanged
            Debug.Assert(null != identity, "must have identity");
 
            this.EnsureIdentityToResource();
 
            // resource.State == EntityState.Added or Unchanged for second pass of media link
            EntityDescriptor resource = this.entityDescriptors[entity];
 
            this.DetachResourceIdentity(resource);
 
            // While processing the response, we need to find out if the given resource was inserted deep
            // If it was, then we need to change the link state from added to unchanged
            if (resource.IsDeepInsert)
            {
                LinkDescriptor end = this.bindings[resource.GetRelatedEnd()];
                end.State = EntityStates.Unchanged;
            }
 
            resource.ETag = etag;
            resource.Identity = identity; // always attach the identity
            resource.SelfLink = selfLink;
            resource.EditLink = editLink;
 
            resource.State = EntityStates.Unchanged;
 
            // scenario: sucessfully (1) delete an existing entity and (2) add a new entity where the new entity has the same identity as deleted entity
            // where the SaveChanges pass1 will now associate existing identity with new entity
            // but pass2 for the deleted entity will not blindly remove the identity that is now associated with the new identity
            this.identityToDescriptor[identity] = resource;
        }
 
        /// <summary>use location from header to generate initial edit and identity</summary>
        /// <param name="entity">entity in added state</param>
        /// <param name="location">location from post header</param>
        internal void AttachLocation(object entity, string location)
        {
            Debug.Assert(null != entity, "null != entity");
            Uri editLink = new Uri(location, UriKind.Absolute);
            String identity = Util.ReferenceIdentity(CommonUtil.UriToString(editLink));
 
            this.EnsureIdentityToResource();
 
            // resource.State == EntityState.Added or Unchanged for second pass of media link
            EntityDescriptor resource = this.entityDescriptors[entity];
            this.DetachResourceIdentity(resource);
 
            // While processing the response, we need to find out if the given resource was inserted deep
            // If it was, then we need to change the link state from added to unchanged
            if (resource.IsDeepInsert)
            {
                LinkDescriptor end = this.bindings[resource.GetRelatedEnd()];
                end.State = EntityStates.Unchanged;
            }
 
            resource.Identity = identity; // always attach the identity
            resource.EditLink = editLink;
 
            // scenario: sucessfully batch (1) add a new entity and (2) delete an existing entity where the new entity has the same identity as deleted entity
            // where the SaveChanges pass1 will now associate existing identity with new entity
            // but pass2 for the deleted entity will not blindly remove the identity that is now associated with the new identity
            this.identityToDescriptor[identity] = resource;
        }
 
        /// <summary>
        /// Track a binding.
        /// </summary>
        /// <param name="source">Source resource.</param>
        /// <param name="sourceProperty">Property on the source resource that relates to the target resource.</param>
        /// <param name="target">Target resource.</param>
        /// <param name="linkMerge">merge operation</param>
        internal void AttachLink(object source, string sourceProperty, object target, MergeOption linkMerge)
        {
            this.EnsureRelatable(source, sourceProperty, target, EntityStates.Unchanged);
 
            LinkDescriptor existing = null;
            LinkDescriptor relation = new LinkDescriptor(source, sourceProperty, target);
            if (this.bindings.TryGetValue(relation, out existing))
            {
                switch (linkMerge)
                {
                    case MergeOption.AppendOnly:
                        break;
 
                    case MergeOption.OverwriteChanges:
                        relation = existing;
                        break;
 
                    case MergeOption.PreserveChanges:
                        if ((EntityStates.Added == existing.State) ||
                            (EntityStates.Unchanged == existing.State) ||
                            (EntityStates.Modified == existing.State && null != existing.Target))
                        {
                            relation = existing;
                        }
 
                        break;
 
                    case MergeOption.NoTracking: // public API point should throw if link exists
                        throw Error.InvalidOperation(Strings.Context_RelationAlreadyContained);
                }
            }
            else
            {
                bool collectionProperty = (null != ClientType.Create(source.GetType()).GetProperty(sourceProperty, false).CollectionType);
                if (collectionProperty || (null == (existing = this.DetachReferenceLink(source, sourceProperty, target, linkMerge))))
                {
                    this.bindings.Add(relation, relation);
                    this.IncrementChange(relation);
                }
                else if (!((MergeOption.AppendOnly == linkMerge) ||
                           (MergeOption.PreserveChanges == linkMerge && EntityStates.Modified == existing.State)))
                {
                    // AppendOnly doesn't change state or target
                    // OverWriteChanges changes target and state
                    // PreserveChanges changes target if unchanged, leaves modified target and state alone
                    relation = existing;
                }
            }
 
            relation.State = EntityStates.Unchanged;
        }
 
        /// <summary>
        /// Attach entity into the context in the Unchanged state.
        /// </summary>
        /// <param name="descriptor">the descriptor to add</param>
        /// <param name="failIfDuplicated">fail for public api else change existing relationship to unchanged</param>
        /// <remarks>Caller should validate descriptor instance.</remarks>       
        /// <returns>The attached descriptor, if one already exists in the context and failIfDuplicated is set to false, then the existing instance is returned</returns>
        /// <exception cref="InvalidOperationException">if entity is already being tracked by the context</exception>
        /// <exception cref="InvalidOperationException">if identity is pointing to another entity</exception>
        internal EntityDescriptor InternalAttachEntityDescriptor(EntityDescriptor descriptor, bool failIfDuplicated)
        {
            Debug.Assert((null != descriptor.Identity), "must have identity");
            Debug.Assert(null != descriptor.Entity && ClientType.Create(descriptor.Entity.GetType()).IsEntityType, "must be entity type to attach");
 
            this.EnsureIdentityToResource();
 
            EntityDescriptor resource;
            this.entityDescriptors.TryGetValue(descriptor.Entity, out resource);
 
            EntityDescriptor existing;
            this.identityToDescriptor.TryGetValue(descriptor.Identity, out existing);
 
            // identity existing & pointing to something else
            if (failIfDuplicated && (null != resource))
            {
                throw Error.InvalidOperation(Strings.Context_EntityAlreadyContained);
            }
            else if (resource != existing)
            {
                throw Error.InvalidOperation(Strings.Context_DifferentEntityAlreadyContained);
            }
            else if (null == resource)
            {
                resource = descriptor;
                
                // if resource doesn't exist...
                this.IncrementChange(descriptor);
                this.entityDescriptors.Add(descriptor.Entity, descriptor);
                this.identityToDescriptor.Add(descriptor.Identity, descriptor);
            }
 
            // DEVNOTE(Microsoft):
            // we used to mark the descriptor as Unchanged
            // but it's now up to the caller to do that
            return resource;
        }
 
        #endregion
 
#if ASTORIA_LIGHT
        /// <summary>
        /// create the request object
        /// </summary>
        /// <param name="requestUri">requestUri</param>
        /// <param name="method">updating</param>
        /// <param name="allowAnyType">Whether the request/response should request/assume ATOM or any MIME type</param>
        /// <param name="contentType">content type for the request</param>
        /// <param name="requestVersion">The version associated with this request</param>
        /// <param name="sendChunked">Set to true if the request body should be sent using chunked encoding.</param>
        /// <returns>a request ready to get a response</returns>
        internal HttpWebRequest CreateRequest(Uri requestUri, string method, bool allowAnyType, string contentType, Version requestVersion, bool sendChunked)
        {
            return CreateRequest(requestUri, method, allowAnyType, contentType, requestVersion, sendChunked, HttpStack.Auto);
        }
#endif
 
#if !ASTORIA_LIGHT
        /// <summary>
        /// create the request object
        /// </summary>
        /// <param name="requestUri">requestUri</param>
        /// <param name="method">updating</param>
        /// <param name="allowAnyType">Whether the request/response should request/assume ATOM or any MIME type</param>
        /// <param name="contentType">content type for the request. Can be null which means don't set the header.</param>
        /// <param name="requestVersion">The version associated with this request</param>
        /// <param name="sendChunked">Set to true if the request body should be sent using chunked encoding.</param>
        /// <returns>a request ready to get a response</returns>
        internal HttpWebRequest CreateRequest(Uri requestUri, string method, bool allowAnyType, string contentType, Version requestVersion, bool sendChunked)
#else
        /// <summary>
        /// create the request object
        /// </summary>
        /// <param name="requestUri">requestUri</param>
        /// <param name="method">updating</param>
        /// <param name="allowAnyType">Whether the request/response should request/assume ATOM or any MIME type</param>
        /// <param name="contentType">content type for the request</param>
        /// <param name="requestVersion">The version associated with this request</param>
        /// <param name="sendChunked">Set to true if the request body should be sent using chunked encoding.</param>
        /// <param name="httpStackArg">If set to non-Auto it forces usage of that http stack in SL regardless of the context settings.</param>
        /// <returns>a request ready to get a response</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "sendChunked", Justification = "common parameter not used in silverlight")]
        internal HttpWebRequest CreateRequest(Uri requestUri, string method, bool allowAnyType, string contentType, Version requestVersion, bool sendChunked, HttpStack httpStackArg)
#endif
        {
            Debug.Assert(null != requestUri, "request uri is null");
            Debug.Assert(requestUri.IsAbsoluteUri, "request uri is not absolute uri");
            Debug.Assert(
                requestUri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) ||
                    requestUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase), 
                "request uri is not for HTTP");
 
            Debug.Assert(
                Object.ReferenceEquals(XmlConstants.HttpMethodDelete, method) ||
                Object.ReferenceEquals(XmlConstants.HttpMethodGet, method) ||
                Object.ReferenceEquals(XmlConstants.HttpMethodPost, method) ||
                Object.ReferenceEquals(XmlConstants.HttpMethodPut, method) ||
                Object.ReferenceEquals(XmlConstants.HttpMethodMerge, method),
                "unexpected http method string reference");
 
#if !ASTORIA_LIGHT
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri);
#else
            if (httpStackArg == HttpStack.Auto)
            {
                httpStackArg = this.httpStack;
            }
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri, httpStackArg);
#endif
 
#if !ASTORIA_LIGHT  // Credentials not available
            if (null != this.Credentials)
            {
                request.Credentials = this.Credentials;
            }
#endif
 
#if !ASTORIA_LIGHT  // Timeout not available
            if (0 != this.timeout)
            {
                request.Timeout = (int)Math.Min(Int32.MaxValue, new TimeSpan(0, 0, this.timeout).TotalMilliseconds);
            }
#endif
 
#if !ASTORIA_LIGHT // KeepAlive not available
            request.KeepAlive = true;
#endif
 
#if !ASTORIA_LIGHT // UserAgent not available
            request.UserAgent = "Microsoft ADO.NET Data Services";
#endif
 
            if (this.UsePostTunneling &&
                (!Object.ReferenceEquals(XmlConstants.HttpMethodPost, method)) &&
                (!Object.ReferenceEquals(XmlConstants.HttpMethodGet, method)))
            {
                request.Headers[XmlConstants.HttpXMethod] = method;
                request.Method = XmlConstants.HttpMethodPost;
            }
            else
            {
                request.Method = method;
            }
 
            // Always sending the version along allows the server to fail before processing.
            // devnote(Microsoft): this needs to be set before SendingRequest is fired, so client has a chance to modify them
            if (requestVersion != null && requestVersion.Major > 0)
            {
                // if request version is 0.x, then we don't put a DSV header 
                // in this case it's up to the server to decide what version this request is
                request.Headers[XmlConstants.HttpDataServiceVersion] = requestVersion.ToString() + Util.VersionSuffix;
            }
 
            request.Headers[XmlConstants.HttpMaxDataServiceVersion] = Util.MaxResponseVersion.ToString() + Util.VersionSuffix;
 
#if !ASTORIA_LIGHT // Silverlight doesn't support chunked encoding yet - so just ignore it
            if (sendChunked)
            {
                request.SendChunked = true;
            }
#endif
 
            // Fires whenever a new HttpWebRequest has been created
            // The event fires early - before the client library sets many of its required property values.
            // This ensures the client library has the last say on the value of mandated properties
            // such as the HTTP verb  being used for the request.
            if (this.SendingRequest != null)
            {
                System.Net.WebHeaderCollection requestHeaders;
#if !ASTORIA_LIGHT
                requestHeaders = request.Headers;
                SendingRequestEventArgs args = new SendingRequestEventArgs(request, requestHeaders);
#else
                requestHeaders = request.CreateEmptyWebHeaderCollection();
                SendingRequestEventArgs args = new SendingRequestEventArgs(null, requestHeaders);
#endif
                this.SendingRequest(this, args);
 
#if !ASTORIA_LIGHT
                if (!Object.ReferenceEquals(args.Request, request))
                {
                    request = (System.Net.HttpWebRequest)args.Request;
                }
#else
                // apply all headers to the request
                foreach (string key in requestHeaders.AllKeys)
                {
                    request.Headers[key] = requestHeaders[key];
                }
#endif
            }
 
            request.Accept = allowAnyType ?
                    XmlConstants.MimeAny :
                    (XmlConstants.MimeApplicationAtom + "," + XmlConstants.MimeApplicationXml);
 
            request.Headers[HttpRequestHeader.AcceptCharset] = XmlConstants.Utf8Encoding;
 
#if !ASTORIA_LIGHT // AllowWriteStreamBuffering not available
            bool allowStreamBuffering = false;
            bool removeXMethod = true;
#endif
 
            if (!Object.ReferenceEquals(XmlConstants.HttpMethodGet, method))
            {
                Debug.Assert(!String.IsNullOrEmpty(contentType), "Content-Type must be specified for non get operation");
                request.ContentType = contentType;
                if (Object.ReferenceEquals(XmlConstants.HttpMethodDelete, method))
                {
                    request.ContentLength = 0;
                }
#if !ASTORIA_LIGHT // AllowWriteStreamBuffering not available
                // else
                {   // always set to workaround NullReferenceException in HttpWebRequest.GetResponse when ContentLength = 0
                    allowStreamBuffering = true;
                }
#endif
 
                if (this.UsePostTunneling && (!Object.ReferenceEquals(XmlConstants.HttpMethodPost, method)))
                {
                    request.Headers[XmlConstants.HttpXMethod] = method;
                    method = XmlConstants.HttpMethodPost;
#if !ASTORIA_LIGHT
                    removeXMethod = false;
#endif
                }
            }
            else
            {
                Debug.Assert(contentType == null, "Content-Type for get methods should be null");
            }
 
#if !ASTORIA_LIGHT // AllowWriteStreamBuffering not available
            // When AllowWriteStreamBuffering is true, the data is buffered in memory so it is ready to be resent
            // in the event of redirections or authentication requests.
            request.AllowWriteStreamBuffering = allowStreamBuffering;
#endif
 
            ICollection<string> headers;
            headers = request.Headers.AllKeys;
 
#if !ASTORIA_LIGHT  // alternate IfMatch header doesn't work
            if (headers.Contains(XmlConstants.HttpRequestIfMatch))
            {
                request.Headers.Remove(HttpRequestHeader.IfMatch);
            }
#endif
 
#if !ASTORIA_LIGHT  // alternate HttpXMethod header doesn't work
            if (removeXMethod && headers.Contains(XmlConstants.HttpXMethod))
            {
                request.Headers.Remove(XmlConstants.HttpXMethod);
            }
#endif
 
            request.Method = method;
            return request;
        }
 
        /// <summary>
        /// Find tracked entity by its resourceUri and update its etag.
        /// </summary>
        /// <param name="resourceUri">resource id</param>
        /// <param name="etag">updated etag</param>
        /// <param name="merger">merge option</param>
        /// <param name="state">state of entity</param>
        /// <returns>entity if found else null</returns>
        internal object TryGetEntity(String resourceUri, string etag, MergeOption merger, out EntityStates state)
        {
            Debug.Assert(null != resourceUri, "null uri");
            state = EntityStates.Detached;
 
            EntityDescriptor resource = null;
            if ((null != this.identityToDescriptor) &&
                 this.identityToDescriptor.TryGetValue(resourceUri, out resource))
            {
                state = resource.State;
                if ((null != etag) && (MergeOption.AppendOnly != merger))
                {   // don't update the etag if AppendOnly
                    resource.ETag = etag;
                }
 
                Debug.Assert(null != resource.Entity, "null entity");
                return resource.Entity;
            }
 
            return null;
        }
 
        /// <summary>
        /// get the related links ignoring target entity
        /// </summary>
        /// <param name="source">source entity</param>
        /// <param name="sourceProperty">source entity's property</param>
        /// <returns>enumerable of related ends</returns>
        internal IEnumerable<LinkDescriptor> GetLinks(object source, string sourceProperty)
        {
            return this.bindings.Values.Where(o => (o.Source == source) && (o.SourceProperty == sourceProperty));
        }
 
        /// <summary>
        /// user hook to resolve name into a type
        /// </summary>
        /// <param name="wireName">name to resolve</param>
        /// <param name="userType">base type associated with name</param>
        /// <param name="checkAssignable">Whether to check that the resulting type is assignable.</param>
        /// <returns>null to skip node</returns>
        /// <exception cref="InvalidOperationException">if ResolveType function returns a type not assignable to the userType</exception>
        internal Type ResolveTypeFromName(string wireName, Type userType, bool checkAssignable)
        {
            Debug.Assert(null != userType, "null != baseType");
 
            if (String.IsNullOrEmpty(wireName))
            {
                return userType;
            }
 
            Type payloadType;
            if (!ClientConvert.ToNamedType(wireName, out payloadType))
            {
                payloadType = null;
 
                Func<string, Type> resolve = this.ResolveType;
                if (null != resolve)
                {
                    // if the ResolveType property is set, call the provided type resultion method
                    payloadType = resolve(wireName);
                }
 
                if (null == payloadType)
                {
                    // if the type resolution method returns null or the ResolveType property was not set
#if !ASTORIA_LIGHT
                    payloadType = ClientType.ResolveFromName(wireName, userType);
#else
                    payloadType = ClientType.ResolveFromName(wireName, userType, this.GetType());
#endif
                }
 
                if (checkAssignable && (null != payloadType) && (!userType.IsAssignableFrom(payloadType)))
                {
                    // throw an exception if the type from the resolver is not assignable to the expected type
                    throw Error.InvalidOperation(Strings.Deserialize_Current(userType, payloadType));
                }
            }
 
            return payloadType ?? userType;
        }
 
        /// <summary>
        /// The reverse of ResolveType, use for complex types and LINQ query expression building
        /// </summary>
        /// <param name="type">client type</param>
        /// <returns>type for the server</returns>
        internal string ResolveNameFromType(Type type)
        {
            Debug.Assert(null != type, "null type");
            Func<Type, string> resolve = this.ResolveName;
            return ((null != resolve) ? resolve(type) : (String)null);
        }
 
        /// <summary>
        /// Get from entity descriptor or resolve the server type name for the entitydescriptor
        /// </summary>
        /// <param name="descriptor">The entity descriptor</param>
        /// <returns>The server type name for the entity</returns>
        internal string GetServerTypeName(EntityDescriptor descriptor)
        {
            Debug.Assert(descriptor != null && descriptor.Entity != null, "Null descriptor or no entity in descriptor");
 
            if (this.resolveName != null)
            {
                // is resolveName the code-gen resolver?
                Type entityType = descriptor.Entity.GetType();
                var codegenAttr = this.resolveName.Method.GetCustomAttributes(false).OfType<System.CodeDom.Compiler.GeneratedCodeAttribute>().FirstOrDefault();
                if (codegenAttr == null || codegenAttr.Tool != Util.CodeGeneratorToolName)
                {
                    // User-supplied resolver, must call first
                    return this.resolveName(entityType) ?? descriptor.ServerTypeName;
                }
                else
                {
                    // V2+ codegen resolver, called last
                    return descriptor.ServerTypeName ?? this.resolveName(entityType);
                }
            }
            else
            {
                return descriptor.ServerTypeName;
            }
        }
 
        /// <summary>
        /// Fires the ReadingEntity event
        /// </summary>
        /// <param name="entity">Entity being (de)serialized</param>
        /// <param name="data">XML data of the ATOM entry</param>
        internal void FireReadingEntityEvent(object entity, XElement data)
        {
            Debug.Assert(entity != null, "entity != null");
            Debug.Assert(data != null, "data != null");
 
            ReadingWritingEntityEventArgs args = new ReadingWritingEntityEventArgs(entity, data);
            this.ReadingEntity(this, args);
        }
 
        #region Ensure
 
        /// <summary>modified or unchanged</summary>
        /// <param name="x">state to test</param>
        /// <returns>true if modified or unchanged</returns>
        private static bool IncludeLinkState(EntityStates x)
        {
            return ((EntityStates.Modified == x) || (EntityStates.Unchanged == x));
        }
 
        #endregion
 
        /// <summary>Checks whether a WCF Data Service version string can be handled.</summary>
        /// <param name="responseVersion">Version string on the response header; possibly null.</param>
        /// <returns>true if the version can be handled; false otherwise.</returns>
        private static bool CanHandleResponseVersion(string responseVersion)
        {
            if (!String.IsNullOrEmpty(responseVersion))
            {
                KeyValuePair<Version, string> version;
                if (!HttpProcessUtility.TryReadVersion(responseVersion, out version))
                {
                    return false;
                }
 
                if (!Util.SupportedResponseVersions.Contains(version.Key))
                {
                    return false;
                }
            }
 
            return true;
        }
 
        /// <summary>
        /// Serialize supported data service versions to a string that will be used in the exception message.
        /// The string contains versions in single quotes separated by comma followed by a single space (e.g. "'1.0', '2.0'").
        /// </summary>
        /// <returns>Supported data service versions in single quotes separated by comma followed by a space.</returns>
        private static string SerializeSupportedVersions()
        {
            Debug.Assert(Util.SupportedResponseVersions.Length > 0, "At least one supported version must exist.");
 
            StringBuilder supportedVersions = new StringBuilder("'").Append(Util.SupportedResponseVersions[0].ToString());
            for (int versionIdx = 1; versionIdx < Util.SupportedResponseVersions.Length; versionIdx++)
            {
                supportedVersions.Append("', '");
                supportedVersions.Append(Util.SupportedResponseVersions[versionIdx].ToString());
            }
 
            supportedVersions.Append("'");
 
            return supportedVersions.ToString();
        }
 
        /// <summary>generate a Uri based on key properties of the entity</summary>
        /// <param name="baseUriWithSlash">baseUri</param>
        /// <param name="entitySetName">entitySetName</param>
        /// <param name="entity">entity</param>
        /// <returns>absolute uri</returns>
        private static Uri GenerateEditLinkUri(Uri baseUriWithSlash, string entitySetName, object entity)
        {
            Debug.Assert(null != baseUriWithSlash && baseUriWithSlash.IsAbsoluteUri && CommonUtil.UriToString(baseUriWithSlash).EndsWith("/", StringComparison.Ordinal), "baseUriWithSlash");
            Debug.Assert(!String.IsNullOrEmpty(entitySetName) && !entitySetName.StartsWith("/", StringComparison.Ordinal), "entitySetName");
                        
            // to generate the edit link uri, we need keys
            // this also checks for null
            ValidateEntityTypeHasKeys(entity);
 
            StringBuilder builder = new StringBuilder();
            builder.Append(baseUriWithSlash.AbsoluteUri);
            builder.Append(entitySetName);
            builder.Append("(");
 
            string prefix = String.Empty;
            ClientType clientType = ClientType.Create(entity.GetType());
 
            ClientType.ClientProperty[] keys = clientType.Properties.Where<ClientType.ClientProperty>(ClientType.ClientProperty.GetKeyProperty).ToArray();
            foreach (ClientType.ClientProperty property in keys)
            {
#if ASTORIA_OPEN_OBJECT
                Debug.Assert(!property.OpenObjectProperty, "key property values can't be OpenProperties");
#endif
 
                builder.Append(prefix);
                if (1 < keys.Length)
                {
                    builder.Append(property.PropertyName).Append("=");
                }
 
                object value = property.GetValue(entity);
                if (null == value)
                {
                    throw Error.InvalidOperation(Strings.Serializer_NullKeysAreNotSupported(property.PropertyName));
                }
 
                string converted;
                if (!ClientConvert.TryKeyPrimitiveToString(value, out converted))
                {
                    throw Error.InvalidOperation(Strings.Context_CannotConvertKey(value));
                }
 
                builder.Append(converted);
                prefix = ",";
            }
 
            builder.Append(")");
 
            return Util.CreateUri(builder.ToString(), UriKind.Absolute);
        }
 
        /// <summary>Get http method string from entity resource state</summary>
        /// <param name="state">resource state</param>
        /// <param name="replaceOnUpdate">whether we need to update MERGE or PUT method for update.</param>
        /// <returns>http method string delete, put or post</returns>
        private static string GetEntityHttpMethod(EntityStates state, bool replaceOnUpdate)
        {
            switch (state)
            {
                case EntityStates.Deleted:
                    return XmlConstants.HttpMethodDelete;
                case EntityStates.Modified:
                    if (replaceOnUpdate)
                    {
                        return XmlConstants.HttpMethodPut;
                    }
                    else
                    {
                        return XmlConstants.HttpMethodMerge;
                    }
 
                case EntityStates.Added:
                    return XmlConstants.HttpMethodPost;
                default:
                    throw Error.InternalError(InternalError.UnvalidatedEntityState);
            }
        }
 
        /// <summary>Get http method string from link resource state</summary>
        /// <param name="link">resource</param>
        /// <returns>http method string put or post</returns>
        private static string GetLinkHttpMethod(LinkDescriptor link)
        {
            bool collection = (null != ClientType.Create(link.Source.GetType()).GetProperty(link.SourceProperty, false).CollectionType);
            if (!collection)
            {
                Debug.Assert(EntityStates.Modified == link.State, "not Modified state");
                if (null == link.Target)
                {   // REMOVE/DELETE a reference
                    return XmlConstants.HttpMethodDelete;
                }
                else
                {   // UPDATE/PUT a reference
                    return XmlConstants.HttpMethodPut;
                }
            }
            else if (EntityStates.Deleted == link.State)
            {   // you call DELETE on $links
                return XmlConstants.HttpMethodDelete;
            }
            else
            {   // you INSERT/POST into a collection
                Debug.Assert(EntityStates.Added == link.State, "not Added state");
                return XmlConstants.HttpMethodPost;
            }
        }
 
        /// <summary>Handle changeset response.</summary>
        /// <param name="entry">headers of changeset response</param>
        private static void HandleResponsePost(LinkDescriptor entry)
        {
            if (!((EntityStates.Added == entry.State) || (EntityStates.Modified == entry.State && null != entry.Target)))
            {
                Error.ThrowBatchUnexpectedContent(InternalError.LinkNotAddedState);
            }
 
            entry.State = EntityStates.Unchanged;
        }
 
        /// <summary>Handle changeset response.</summary>
        /// <param name="entry">updated entity or link</param>
        /// <param name="etag">updated etag</param>
        private static void HandleResponsePut(Descriptor entry, string etag)
        {
            if (entry.IsResource)
            {
                EntityDescriptor descriptor = (EntityDescriptor)entry;
                if (EntityStates.Modified != descriptor.State && StreamStates.Modified != descriptor.StreamState)
                {
                    Error.ThrowBatchUnexpectedContent(InternalError.EntryNotModified);
                }
 
                // We MUST process the MR before the MLE since we always issue the requests in that order.
                if (descriptor.StreamState == StreamStates.Modified)
                {
                    descriptor.StreamETag = etag;
                    descriptor.StreamState = StreamStates.NoStream;
                }
                else
                {
                    Debug.Assert(descriptor.State == EntityStates.Modified, "descriptor.State == EntityStates.Modified");
                    descriptor.ETag = etag;
                    descriptor.State = EntityStates.Unchanged;
                }
            }
            else
            {
                LinkDescriptor link = (LinkDescriptor)entry;
                if ((EntityStates.Added == entry.State) || (EntityStates.Modified == entry.State))
                {
                    link.State = EntityStates.Unchanged;
                }
                else if (EntityStates.Detached != entry.State)
                {   // this link may have been previously detached by a detaching entity
                    Error.ThrowBatchUnexpectedContent(InternalError.LinkBadState);
                }
            }
        }
 
        /// <summary>
        /// write out an individual property value which can be a primitive or link
        /// </summary>
        /// <param name="writer">writer</param>
        /// <param name="namespaceName">namespaceName in which we need to write the property element.</param>
        /// <param name="property">property which contains name, type, is key (if false and null value, will throw)</param>
        /// <param name="propertyValue">property value</param>
        private static void WriteContentProperty(XmlWriter writer, string namespaceName, ClientType.ClientProperty property, object propertyValue)
        {
            writer.WriteStartElement(property.PropertyName, namespaceName);
 
            string typename = ClientConvert.GetEdmType(property.PropertyType);
            if (null != typename)
            {
                writer.WriteAttributeString(XmlConstants.AtomTypeAttributeName, XmlConstants.DataWebMetadataNamespace, typename);
            }
 
            if (null == propertyValue)
            {   // <d:property adsm:null="true" />
                writer.WriteAttributeString(XmlConstants.AtomNullAttributeName, XmlConstants.DataWebMetadataNamespace, XmlConstants.XmlTrueLiteral);
 
                if (property.KeyProperty)
                {
                    throw Error.InvalidOperation(Strings.Serializer_NullKeysAreNotSupported(property.PropertyName));
                }
            }
            else
            {
                string convertedValue = ClientConvert.ToString(propertyValue, false /* atomDateConstruct */);
                if (0 == convertedValue.Length)
                {   // <d:property m:null="false" />
                    writer.WriteAttributeString(XmlConstants.AtomNullAttributeName, XmlConstants.DataWebMetadataNamespace, XmlConstants.XmlFalseLiteral);
                }
                else
                {   // <d:property>value</property>
                    if (Char.IsWhiteSpace(convertedValue[0]) ||
                        Char.IsWhiteSpace(convertedValue[convertedValue.Length - 1]))
                    {   // xml:space="preserve"
                        writer.WriteAttributeString(XmlConstants.XmlSpaceAttributeName, XmlConstants.XmlNamespacesNamespace, XmlConstants.XmlSpacePreserveValue);
                    }
 
                    writer.WriteValue(convertedValue);
                }
            }
 
            writer.WriteEndElement();
        }
 
        /// <summary>validate <paramref name="entity"/> is entity type</summary>
        /// <param name="entity">entity to validate</param>
        /// <exception cref="ArgumentNullException">if entity was null</exception>
        /// <exception cref="ArgumentException">if entity does not have a key property</exception>
        private static void ValidateEntityType(object entity)
        {
            Util.CheckArgumentNull(entity, "entity");
 
            if (!ClientType.Create(entity.GetType()).IsEntityType)
            {
                throw Error.Argument(Strings.Content_EntityIsNotEntityType, "entity");
            }
        }
 
        /// <summary>validate <paramref name="entity"/> has key properties</summary>
        /// <param name="entity">entity to validate</param>
        /// <exception cref="ArgumentNullException">if entity was null</exception>
        /// <exception cref="ArgumentException">if entity does not have a key property</exception>
        private static void ValidateEntityTypeHasKeys(object entity)
        {
            Util.CheckArgumentNull(entity, "entity");
 
            if (ClientType.Create(entity.GetType()).KeyCount <= 0)
            {
                throw Error.Argument(Strings.Content_EntityWithoutKey, "entity");
            }
        }
 
        /// <summary>
        /// Validate the SaveChanges Option
        /// </summary>
        /// <param name="options">options as specified by the user.</param>
        private static void ValidateSaveChangesOptions(SaveChangesOptions options)
        {
            const SaveChangesOptions All =
                SaveChangesOptions.ContinueOnError |
                SaveChangesOptions.Batch |
                SaveChangesOptions.ReplaceOnUpdate;
 
            // Make sure no higher order bits are set.
            if ((options | All) != All)
            {
                throw Error.ArgumentOutOfRange("options");
            }
 
            // Both batch and continueOnError can't be set together
            if (IsFlagSet(options, SaveChangesOptions.Batch | SaveChangesOptions.ContinueOnError))
            {
                throw Error.ArgumentOutOfRange("options");
            }
        }
 
        /// <summary>
        /// checks whether the given flag is set on the options
        /// </summary>
        /// <param name="options">options as specified by the user.</param>
        /// <param name="flag">whether the given flag is set on the options</param>
        /// <returns>true if the given flag is set, otherwise false.</returns>
        private static bool IsFlagSet(SaveChangesOptions options, SaveChangesOptions flag)
        {
            return ((options & flag) == flag);
        }
 
        /// <summary>
        /// Write the batch headers along with the first http header for the batch operation.
        /// </summary>
        /// <param name="writer">Stream writer which writes to the underlying stream.</param>
        /// <param name="methodName">HTTP method name for the operation.</param>
        /// <param name="uri">uri for the operation.</param>
        /// <param name="requestVersion">version for the request</param>
        private static void WriteOperationRequestHeaders(StreamWriter writer, string methodName, string uri, Version requestVersion)
        {
            writer.WriteLine("{0}: {1}", XmlConstants.HttpContentType, XmlConstants.MimeApplicationHttp);
            writer.WriteLine("{0}: {1}", XmlConstants.HttpContentTransferEncoding, XmlConstants.BatchRequestContentTransferEncoding);
            writer.WriteLine();
 
            writer.WriteLine("{0} {1} {2}", methodName, uri, XmlConstants.HttpVersionInBatching);
            if (requestVersion != Util.DataServiceVersion1 && requestVersion != Util.DataServiceVersionEmpty)
            {
                writer.WriteLine("{0}: {1}{2}", XmlConstants.HttpDataServiceVersion, requestVersion, Util.VersionSuffix);
            }
        }
 
        /// <summary>
        /// Write the batch headers along with the first http header for the batch operation.
        /// </summary>
        /// <param name="writer">Stream writer which writes to the underlying stream.</param>
        /// <param name="statusCode">status code for the response.</param>
        private static void WriteOperationResponseHeaders(StreamWriter writer, int statusCode)
        {
            writer.WriteLine("{0}: {1}", XmlConstants.HttpContentType, XmlConstants.MimeApplicationHttp);
            writer.WriteLine("{0}: {1}", XmlConstants.HttpContentTransferEncoding, XmlConstants.BatchRequestContentTransferEncoding);
            writer.WriteLine();
 
            writer.WriteLine("{0} {1} {2}", XmlConstants.HttpVersionInBatching, statusCode, (HttpStatusCode)statusCode);
        }
 
        /// <summary>the work to detach a resource</summary>
        /// <param name="resource">resource to detach</param>
        /// <returns>true if detached</returns>
        private bool DetachResource(EntityDescriptor resource)
        {
            // Since we are changing the list on the fly, we need to convert it into a list first
            // so that enumeration won't get effected.
            foreach (LinkDescriptor end in this.bindings.Values.Where(resource.IsRelatedEntity).ToList())
            {
                this.DetachExistingLink(
                        end, 
                        end.Target == resource.Entity && resource.State == EntityStates.Added);
            }
 
            resource.ChangeOrder = UInt32.MaxValue;
            resource.State = EntityStates.Detached;
            bool flag = this.entityDescriptors.Remove(resource.Entity);
            Debug.Assert(flag, "should have removed existing entity");
            this.DetachResourceIdentity(resource);
 
            return true;
        }
 
        /// <summary>remove the identity attached to the resource</summary>
        /// <param name="resource">resource with an identity to detach to detach</param>
        private void DetachResourceIdentity(EntityDescriptor resource)
        {
            EntityDescriptor existing = null;
            if ((null != resource.Identity) &&
                this.identityToDescriptor.TryGetValue(resource.Identity, out existing) &&
                Object.ReferenceEquals(existing, resource))
            {
                bool removed = this.identityToDescriptor.Remove(resource.Identity);
                Debug.Assert(removed, "should have removed existing identity");
            }
        }
 
        /// <summary>
        /// write out binding payload using POST with http method override for PUT
        /// </summary>
        /// <param name="binding">binding</param>
        /// <returns>for non-batching its a request object ready to get a response from else null when batching</returns>
        private HttpWebRequest CreateRequest(LinkDescriptor binding)
        {
            Debug.Assert(null != binding, "null binding");
            if (binding.ContentGeneratedForSave)
            {
                return null;
            }
 
            EntityDescriptor sourceResource = this.entityDescriptors[binding.Source];
            EntityDescriptor targetResource = (null != binding.Target) ? this.entityDescriptors[binding.Target] : null;
 
            // these failures should only with SaveChangesOptions.ContinueOnError
            if (null == sourceResource.Identity)
            {
                Debug.Assert(!binding.ContentGeneratedForSave, "already saved link");
                binding.ContentGeneratedForSave = true;
                Debug.Assert(EntityStates.Added == sourceResource.State, "expected added state");
                throw Error.InvalidOperation(Strings.Context_LinkResourceInsertFailure, sourceResource.SaveError);
            }
            else if ((null != targetResource) && (null == targetResource.Identity))
            {
                Debug.Assert(!binding.ContentGeneratedForSave, "already saved link");
                binding.ContentGeneratedForSave = true;
                Debug.Assert(EntityStates.Added == targetResource.State, "expected added state");
                throw Error.InvalidOperation(Strings.Context_LinkResourceInsertFailure, targetResource.SaveError);
            }
 
            Debug.Assert(null != sourceResource.Identity, "missing sourceResource.Identity");
            return this.CreateRequest(this.CreateRequestUri(sourceResource, binding), GetLinkHttpMethod(binding), false, XmlConstants.MimeApplicationXml, Util.DataServiceVersion1, false);
        }
 
        /// <summary>create the uri for a link</summary>
        /// <param name="sourceResource">edit link of source</param>
        /// <param name="binding">link</param>
        /// <returns>appropriate uri for link state</returns>
        private Uri CreateRequestUri(EntityDescriptor sourceResource, LinkDescriptor binding)
        {
            Uri requestUri = Util.CreateUri(sourceResource.GetResourceUri(this.baseUriWithSlash, false /*queryLink*/), this.CreateRequestRelativeUri(binding));
            return requestUri;
        }
 
        /// <summary>
        /// create the uri for the link relative to its source entity
        /// </summary>
        /// <param name="binding">link</param>
        /// <returns>uri</returns>
        private Uri CreateRequestRelativeUri(LinkDescriptor binding)
        {
            Uri relative;
            bool collection = (null != ClientType.Create(binding.Source.GetType()).GetProperty(binding.SourceProperty, false).CollectionType);
            if (collection && (EntityStates.Added != binding.State))
            {   // you DELETE(PUT NULL) from a collection
                Debug.Assert(null != binding.Target, "null target in collection");
                EntityDescriptor targetResource = this.entityDescriptors[binding.Target];
 
                // For collections, we need to generate the uri with the property name followed by the keys.
                // GenerateEditLinkUri generates an absolute uri
                //      First parameters is the base service uri
                //      Second parameter is the segment name (in this case, navigation property name)
                //      Third parameter is the resource whose key values need to be appended after the segment
                // For e.g. If the navigation property name is "Purchases" and the resource type is Order with key '1', then this method will generate 'baseuri/Purchases(1)'
                Uri navigationPropertyUri = this.BaseUriWithSlash.MakeRelativeUri(DataServiceContext.GenerateEditLinkUri(this.BaseUriWithSlash, binding.SourceProperty, targetResource.Entity));
 
                // Get the relative uri and appends links segment at the start.
                relative = Util.CreateUri(XmlConstants.UriLinkSegment + "/" + CommonUtil.UriToString(navigationPropertyUri), UriKind.Relative);
            }
            else
            {   // UPDATE(PUT ID) a reference && INSERT(POST ID) into a collection
                relative = Util.CreateUri(XmlConstants.UriLinkSegment + "/" + binding.SourceProperty, UriKind.Relative);
            }
 
            Debug.Assert(!relative.IsAbsoluteUri, "should be relative uri");
            return relative;
        }
 
        /// <summary>
        /// write content to batch text stream
        /// </summary>
        /// <param name="binding">link</param>
        /// <param name="text">batch text stream</param>
        private void CreateRequestBatch(LinkDescriptor binding, StreamWriter text)
        {
            EntityDescriptor sourceResource = this.entityDescriptors[binding.Source];
            string requestString;
            if (null != sourceResource.Identity)
            {
                requestString = this.CreateRequestUri(sourceResource, binding).AbsoluteUri;
            }
            else
            {
                Uri relative = this.CreateRequestRelativeUri(binding);
                requestString = "$" + sourceResource.ChangeOrder.ToString(CultureInfo.InvariantCulture) + "/" + CommonUtil.UriToString(relative);
            }
 
            WriteOperationRequestHeaders(text, GetLinkHttpMethod(binding), requestString, Util.DataServiceVersion1);
            text.WriteLine("{0}: {1}", XmlConstants.HttpContentID, binding.ChangeOrder);
 
            // if (EntityStates.Deleted || (EntityState.Modifed && null == TargetResource))
            // then the server will fail the batch section if content type exists
            if ((EntityStates.Added == binding.State) || (EntityStates.Modified == binding.State && (null != binding.Target)))
            {
                text.WriteLine("{0}: {1}", XmlConstants.HttpContentType, XmlConstants.MimeApplicationXml);
            }
        }
 
        /// <summary>
        /// create content memory stream for link
        /// </summary>
        /// <param name="binding">link</param>
        /// <param name="newline">should newline be written</param>
        /// <returns>memory stream</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Returning MemoryStream which doesn't require disposal")]
        private MemoryStream CreateRequestData(LinkDescriptor binding, bool newline)
        {
            Debug.Assert(
                (binding.State == EntityStates.Added) ||
                (binding.State == EntityStates.Modified && null != binding.Target),
                "This method must be called only when a binding is added or put");
            MemoryStream stream = new MemoryStream();
            XmlWriter writer = XmlUtil.CreateXmlWriterAndWriteProcessingInstruction(stream, HttpProcessUtility.EncodingUtf8NoPreamble);
            EntityDescriptor targetResource = this.entityDescriptors[binding.Target];
 
            #region <uri xmlns="metadata">
            writer.WriteStartElement(XmlConstants.UriElementName, XmlConstants.DataWebMetadataNamespace);
 
            string id;
            if (null != targetResource.Identity)
            {
                // When we write the uri in the payload, we need to make sure that we write the edit 
                // link in the payload, since the request uri is the edit link of the parent entity.
                // Think of a read/write service - since the uri is the target link to the parent entity
                // its better than we write the edit link of the child entity in the payload.
                id = CommonUtil.UriToString(targetResource.GetResourceUri(this.baseUriWithSlash, false /*queryLink*/));
            }
            else
            {
                id = "$" + targetResource.ChangeOrder.ToString(CultureInfo.InvariantCulture);
            }
 
            writer.WriteValue(id);
            writer.WriteEndElement(); // </uri>
            #endregion
 
            writer.Flush();
 
            if (newline)
            {
                // end the xml content stream with a newline
                for (int kk = 0; kk < NewLine.Length; ++kk)
                {
                    stream.WriteByte((byte)NewLine[kk]);
                }
            }
 
            // strip the preamble.
            stream.Position = 0;
            return stream;
        }
 
        /// <summary>
        /// Create HttpWebRequest from a resource
        /// </summary>
        /// <param name="box">resource</param>
        /// <param name="state">resource state</param>
        /// <param name="replaceOnUpdate">whether we need to update MERGE or PUT method for update.</param>
        /// <returns>web request</returns>
        private HttpWebRequest CreateRequest(EntityDescriptor box, EntityStates state, bool replaceOnUpdate)
        {
            Debug.Assert(null != box && ((EntityStates.Added == state) || (EntityStates.Modified == state) || (EntityStates.Deleted == state)), "unexpected entity ResourceState");
 
            string httpMethod = GetEntityHttpMethod(state, replaceOnUpdate);
            Uri requestUri = box.GetResourceUri(this.baseUriWithSlash, false /*queryLink*/);
 
            Version requestVersion = ClientType.Create(box.Entity.GetType()).EpmIsV1Compatible ? Util.DataServiceVersion1 : Util.DataServiceVersion2;
            HttpWebRequest request = this.CreateRequest(requestUri, httpMethod, false, XmlConstants.MimeApplicationAtom, requestVersion, false);
            if ((null != box.ETag) && ((EntityStates.Deleted == state) || (EntityStates.Modified == state)))
            {
                request.Headers.Set(HttpRequestHeader.IfMatch, box.ETag);
            }
 
            return request;
        }
 
        /// <summary>
        /// generate batch request for entity
        /// </summary>
        /// <param name="box">entity</param>
        /// <param name="text">batch stream to write to</param>
        /// <param name="replaceOnUpdate">whether we need to update MERGE or PUT method for update.</param>
        private void CreateRequestBatch(EntityDescriptor box, StreamWriter text, bool replaceOnUpdate)
        {
            Debug.Assert(null != box, "null box");
            Debug.Assert(null != text, "null text");
            Debug.Assert(box.State == EntityStates.Added || box.State == EntityStates.Deleted || box.State == EntityStates.Modified, "the entity must be in one of the 3 possible states");
 
            Uri requestUri = box.GetResourceUri(this.baseUriWithSlash, false /*queryLink*/);
 
            Debug.Assert(null != requestUri, "request uri is null");
            Debug.Assert(requestUri.IsAbsoluteUri, "request uri is not absolute uri");
 
            Version requestVersion = ClientType.Create(box.Entity.GetType()).EpmIsV1Compatible ? Util.DataServiceVersion1 : Util.DataServiceVersion2;
            WriteOperationRequestHeaders(text, GetEntityHttpMethod(box.State, replaceOnUpdate), requestUri.AbsoluteUri, requestVersion);
            text.WriteLine("{0}: {1}", XmlConstants.HttpContentID, box.ChangeOrder);
            if (EntityStates.Deleted != box.State)
            {
                text.WriteLine("{0}: {1}", XmlConstants.HttpContentType, XmlConstants.LinkMimeTypeEntry);
            }
 
            if ((null != box.ETag) && (EntityStates.Deleted == box.State || EntityStates.Modified == box.State))
            {
                text.WriteLine("{0}: {1}", XmlConstants.HttpRequestIfMatch, box.ETag);
            }
        }
 
        /// <summary>
        /// create memory stream with entity data
        /// </summary>
        /// <param name="box">entity</param>
        /// <param name="newline">should newline be written</param>
        /// <returns>memory stream containing data</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Returning MemoryStream which doesn't require disposal")]
        private MemoryStream CreateRequestData(EntityDescriptor box, bool newline)
        {
            Debug.Assert(null != box, "null box");
            MemoryStream stream = null;
            switch (box.State)
            {
                case EntityStates.Deleted:
                    break;
                case EntityStates.Modified:
                case EntityStates.Added:
                    stream = new MemoryStream();
                    break;
                default:
                    Error.ThrowInternalError(InternalError.UnvalidatedEntityState);
                    break;
            }
 
            if (null != stream)
            {
                XmlWriter writer;
                XDocument node = null;
                if (this.WritingEntity != null)
                {
                    // if we have to fire the WritingEntity event, buffer the content
                    // in an XElement so we can present the handler with the data
                    node = new XDocument();
                    writer = node.CreateWriter();
                }
                else
                {
                    writer = XmlUtil.CreateXmlWriterAndWriteProcessingInstruction(stream, HttpProcessUtility.EncodingUtf8NoPreamble);
                }
 
                ClientType type = ClientType.Create(box.Entity.GetType());
 
                string typeName = this.GetServerTypeName(box);
 
                #region <entry xmlns="Atom" xmlns:d="DataWeb", xmlns:m="DataWebMetadata">
                writer.WriteStartElement(XmlConstants.AtomEntryElementName, XmlConstants.AtomNamespace);
                writer.WriteAttributeString(XmlConstants.DataWebNamespacePrefix, XmlConstants.XmlNamespacesNamespace, this.DataNamespace);
                writer.WriteAttributeString(XmlConstants.DataWebMetadataNamespacePrefix, XmlConstants.XmlNamespacesNamespace, XmlConstants.DataWebMetadataNamespace);
 
                // <category scheme='http://scheme/' term='typeName' />
                if (!String.IsNullOrEmpty(typeName))
                {
                    writer.WriteStartElement(XmlConstants.AtomCategoryElementName, XmlConstants.AtomNamespace);
                    writer.WriteAttributeString(XmlConstants.AtomCategorySchemeAttributeName, this.typeScheme.OriginalString);
                    writer.WriteAttributeString(XmlConstants.AtomCategoryTermAttributeName, typeName);
                    writer.WriteEndElement();
                }
 
                if (type.HasEntityPropertyMappings)
                {
                    using (EpmSyndicationContentSerializer s = new EpmSyndicationContentSerializer(type.EpmTargetTree, box.Entity, writer))
                    {
                        s.Serialize();
                    }
                }
                else
                {
                    // <title />
                    // <updated>2008-05-05T21:44:55Z</updated>
                    // <author><name /></author>
                    writer.WriteElementString(XmlConstants.AtomTitleElementName, XmlConstants.AtomNamespace, String.Empty);
                    writer.WriteStartElement(XmlConstants.AtomAuthorElementName, XmlConstants.AtomNamespace);
                    writer.WriteElementString(XmlConstants.AtomNameElementName, XmlConstants.AtomNamespace, String.Empty);
                    writer.WriteEndElement();
 
                    writer.WriteElementString(XmlConstants.AtomUpdatedElementName, XmlConstants.AtomNamespace, XmlConvert.ToString(DateTime.UtcNow, XmlDateTimeSerializationMode.RoundtripKind));
                }
 
                if (EntityStates.Modified == box.State)
                {
                    // <id>http://host/service/entityset(key)</id>
                    writer.WriteElementString(XmlConstants.AtomIdElementName, Util.DereferenceIdentity(box.Identity));
                }
                else
                {
                    writer.WriteElementString(XmlConstants.AtomIdElementName, XmlConstants.AtomNamespace, String.Empty);
                }
 
                #region <link href=”%EditLink%” rel=”%DataWebRelatedNamespace%%AssociationName%” type=”application/atom+xml;feed” />
                if (EntityStates.Added == box.State)
                {
                    this.CreateRequestDataLinks(box, writer);
                }
                #endregion
 
                #region <content type="application/xml"><m:Properites> or <m:Properties>
                // For MLE we need to write the properties to the entry level, we also omit the content element completely
                //   as the server will ignore it anyway.
                if (!type.IsMediaLinkEntry && !box.IsMediaLinkEntry)
                {
                    writer.WriteStartElement(XmlConstants.AtomContentElementName, XmlConstants.AtomNamespace); // <atom:content>
                    writer.WriteAttributeString(XmlConstants.AtomTypeAttributeName, XmlConstants.MimeApplicationXml); // empty namespace
                }
 
                writer.WriteStartElement(XmlConstants.AtomPropertiesElementName, XmlConstants.DataWebMetadataNamespace); // <m:Properties>
 
                bool propertiesWritten;
                this.WriteContentProperties(writer, type, box.Entity, type.HasEntityPropertyMappings ? type.EpmSourceTree.Root : null, out propertiesWritten);
 
                writer.WriteEndElement(); // </m:Properties>
 
                if (!type.IsMediaLinkEntry && !box.IsMediaLinkEntry)
                {
                    writer.WriteEndElement(); // </atom:content>
                }
 
                if (type.HasEntityPropertyMappings)
                {
                    using (EpmCustomContentSerializer s = new EpmCustomContentSerializer(type.EpmTargetTree, box.Entity, writer))
                    {
                        s.Serialize();
                    }
                }
 
                writer.WriteEndElement(); // </atom:entry>
                writer.Flush();
                writer.Close();
                #endregion
                #endregion
 
                if (this.WritingEntity != null)
                {
                    ReadingWritingEntityEventArgs args = new ReadingWritingEntityEventArgs(box.Entity, node.Root);
                    this.WritingEntity(this, args);
 
                    // copy the buffered XDocument to the memory stream. no easy way of avoiding
                    // the copy given that we need to know the length before scanning the stream
                    XmlWriterSettings settings = XmlUtil.CreateXmlWriterSettings(HttpProcessUtility.EncodingUtf8NoPreamble);
                    settings.ConformanceLevel = ConformanceLevel.Auto;
                    using (XmlWriter streamWriter = XmlWriter.Create(stream, settings))
                    {
                        node.Save(streamWriter);
                    }
                }
 
                if (newline)
                {
                    // end the xml content stream with a newline
                    for (int kk = 0; kk < NewLine.Length; ++kk)
                    {
                        stream.WriteByte((byte)NewLine[kk]);
                    }
                }
 
                stream.Position = 0;
            }
 
            return stream;
        }
 
        /// <summary>
        /// add the related links for new entites to non-new entites
        /// </summary>
        /// <param name="box">entity in added state</param>
        /// <param name="writer">writer to add links to</param>
        private void CreateRequestDataLinks(EntityDescriptor box, XmlWriter writer)
        {
            Debug.Assert(EntityStates.Added == box.State, "entity not added state");
 
            ClientType clientType = null;
            foreach (LinkDescriptor end in this.RelatedLinks(box))
            {
                Debug.Assert(!end.ContentGeneratedForSave, "already saved link");
                end.ContentGeneratedForSave = true;
 
                if (null == clientType)
                {
                    clientType = ClientType.Create(box.Entity.GetType());
                }
 
                string typeAttributeValue;
                if (null != clientType.GetProperty(end.SourceProperty, false).CollectionType)
                {
                    typeAttributeValue = XmlConstants.LinkMimeTypeFeed;
                }
                else
                {
                    typeAttributeValue = XmlConstants.LinkMimeTypeEntry;
                }
 
                Debug.Assert(null != end.Target, "null is DELETE");
                String targetEditLink = CommonUtil.UriToString(this.entityDescriptors[end.Target].EditLink);
 
                writer.WriteStartElement(XmlConstants.AtomLinkElementName, XmlConstants.AtomNamespace);
                writer.WriteAttributeString(XmlConstants.AtomHRefAttributeName, targetEditLink);
                writer.WriteAttributeString(XmlConstants.AtomLinkRelationAttributeName, XmlConstants.DataWebRelatedNamespace + end.SourceProperty);
                writer.WriteAttributeString(XmlConstants.AtomTypeAttributeName, typeAttributeValue);
                writer.WriteEndElement();
            }
        }
 
        /// <summary>Handle response to deleted entity.</summary>
        /// <param name="entry">deleted entity</param>
        private void HandleResponseDelete(Descriptor entry)
        {
            if (EntityStates.Deleted != entry.State)
            {
                Error.ThrowBatchUnexpectedContent(InternalError.EntityNotDeleted);
            }
 
            if (entry.IsResource)
            {
                EntityDescriptor resource = (EntityDescriptor)entry;
                this.DetachResource(resource);
            }
            else
            {
                this.DetachExistingLink((LinkDescriptor)entry, false);
            }
        }
 
        /// <summary>Handle changeset response.</summary>
        /// <param name="entry">headers of changeset response</param>
        /// <param name="materializer">changeset response stream</param>
        /// <param name="editLink">editLink of the newly created item (non-null if materialize is null)</param>
        /// <param name="etag">ETag header value from the server response (or null if no etag or if there is an actual response)</param>
        private void HandleResponsePost(EntityDescriptor entry, MaterializeAtom materializer, Uri editLink, string etag)
        {
            Debug.Assert(editLink != null, "location header must be specified in POST responses.");
 
            if (EntityStates.Added != entry.State && StreamStates.Added != entry.StreamState)
            {
                Error.ThrowBatchUnexpectedContent(InternalError.EntityNotAddedState);
            }
 
            if (materializer == null)
            {
                // If the materializer is null, that means the POST request didn't send a response back. Hence we will use
                // the location header as the self and edit links.
                String identity = Util.ReferenceIdentity(CommonUtil.UriToString(editLink));
                this.AttachIdentity(identity, null /*queryLink*/, editLink, entry.Entity, etag);
            }
            else
            {
                materializer.SetInsertingObject(entry.Entity);
 
                foreach (object x in materializer)
                {
                    Debug.Assert(null != entry.Identity, "updated inserted should always gain an identity");
                    Debug.Assert(x == entry.Entity, "x == box.Entity, should have same object generated by response");
                    Debug.Assert(EntityStates.Unchanged == entry.State, "should have moved out of insert");
                    Debug.Assert((null != this.identityToDescriptor) && this.identityToDescriptor.ContainsKey(entry.Identity), "should have identity tracked");
 
                    // If there was no edit link specified in the payload, then we need to set the location header as the edit link
                    if (entry.EditLink == null)
                    {
                        entry.EditLink = editLink;
                    }
 
                    // If there was no etag specified in the payload, then we need to set the etag from the header
                    if (entry.ETag == null)
                    {
                        entry.ETag = etag;
                    }
                }
            }
 
            foreach (LinkDescriptor end in this.RelatedLinks(entry))
            {
                Debug.Assert(0 != end.SaveResultWasProcessed, "link should have been saved with the enty");
 
                // Since we allow link folding on collection properties also, we need to check if the link
                // was in added state also, and make sure we put that link in unchanged state.
                if (IncludeLinkState(end.SaveResultWasProcessed) || end.SaveResultWasProcessed == EntityStates.Added)
                {
                    HandleResponsePost(end);
                }
            }
        }
 
        /// <summary>flag results as being processed</summary>
        /// <param name="entry">result entry being processed</param>
        /// <returns>count of related links that were also processed</returns>
        private int SaveResultProcessed(Descriptor entry)
        {
            // media links will be processed twice
            entry.SaveResultWasProcessed = entry.State;
 
            int count = 0;
            if (entry.IsResource && (EntityStates.Added == entry.State))
            {
                foreach (LinkDescriptor end in this.RelatedLinks((EntityDescriptor)entry))
                {
                    Debug.Assert(end.ContentGeneratedForSave, "link should have been saved with the enty");
                    if (end.ContentGeneratedForSave)
                    {
                        Debug.Assert(0 == end.SaveResultWasProcessed, "this link already had a result");
                        end.SaveResultWasProcessed = end.State;
                        count++;
                    }
                }
            }
 
            return count;
        }
 
        /// <summary>
        /// enumerate the related Modified/Unchanged links for an added item
        /// </summary>
        /// <param name="box">entity</param>
        /// <returns>related links</returns>
        /// <remarks>
        /// During a non-batch SaveChanges, an Added entity can become an Unchanged entity
        /// and should be included in the set of related links for the second Added entity.
        /// </remarks>
        private IEnumerable<LinkDescriptor> RelatedLinks(EntityDescriptor box)
        {
            foreach (LinkDescriptor end in this.bindings.Values)
            {
                if (end.Source == box.Entity)
                {
                    if (null != end.Target)
                    {   // null TargetResource is equivalent to Deleted
                        EntityDescriptor target = this.entityDescriptors[end.Target];
 
                        // assumption: the source entity started in the Added state
                        // note: SaveChanges operates with two passes
                        //      a) first send the request and then attach identity and append the result into a batch response  (Example: BeginSaveChanges)
                        //      b) process the batch response (shared code with SaveChanges(Batch))  (Example: EndSaveChanges)
                        // note: SaveResultWasProcessed is set when to the pre-save state when the save result is sucessfully processed
 
                        // scenario #1 when target entity started in modified or unchanged state
                        // 1) the link target entity was modified and now implicitly assumed to be unchanged (this is true in second pass)
                        // 2) or link target entity has not been saved is in the modified or unchanged state (this is true in first pass)
 
                        // scenario #2 when target entity started in added state
                        // 1) target entity has an identity (true in first pass for non-batch)
                        // 2) target entity is processed before source to qualify (1) better during the second pass
                        // 3) the link target has not been saved and is in the added state
                        // 4) or the link target has been saved and was in the added state
                        if (IncludeLinkState(target.SaveResultWasProcessed) || ((0 == target.SaveResultWasProcessed) && IncludeLinkState(target.State)) ||
                            ((null != target.Identity) && (target.ChangeOrder < box.ChangeOrder) &&
                             ((0 == target.SaveResultWasProcessed && EntityStates.Added == target.State) ||
                              (EntityStates.Added == target.SaveResultWasProcessed))))
                        {
                            Debug.Assert(box.ChangeOrder < end.ChangeOrder, "saving is out of order");
                            yield return end;
                        }
                    }
                }
            }
        }
 
        /// <summary>
        /// create the load property request
        /// </summary>
        /// <param name="entity">entity</param>
        /// <param name="propertyName">name of collection or reference property to load</param>
        /// <param name="callback">The AsyncCallback delegate.</param>
        /// <param name="state">user state</param>
        /// <param name="requestUri">The request uri, or null if one is to be constructed</param>
        /// <param name="continuation">Continuation, if one is available.</param>
        /// <returns>a aync result that you can get a response from</returns>
        private LoadPropertyResult CreateLoadPropertyRequest(object entity, string propertyName, AsyncCallback callback, object state, Uri requestUri, DataServiceQueryContinuation continuation)
        {
            Debug.Assert(continuation == null || requestUri == null, "continuation == null || requestUri == null -- only one or the either (or neither) may be passed in");
            EntityDescriptor box = this.EnsureContained(entity, "entity");
            Util.CheckArgumentNotEmpty(propertyName, "propertyName");
 
            ClientType type = ClientType.Create(entity.GetType());
            Debug.Assert(type.IsEntityType, "must be entity type to be contained");
 
            if (EntityStates.Added == box.State)
            {
                throw Error.InvalidOperation(Strings.Context_NoLoadWithInsertEnd);
            }
 
            ClientType.ClientProperty property = type.GetProperty(propertyName, false);
            Debug.Assert(null != property, "should have thrown if propertyName didn't exist");
 
            ProjectionPlan plan;
            if (continuation == null)
            {
                plan = null;
            }
            else
            {
                plan = continuation.Plan;
                requestUri = continuation.NextLinkUri;
            }
 
            bool mediaLink = (type.MediaDataMember != null && propertyName == type.MediaDataMember.PropertyName);
            Version requestVersion;
            if (requestUri == null)
            {
                Uri relativeUri;
                if (mediaLink)
                {
                    // special case for requesting the "media" value of an ATOM media link entry
                    relativeUri = Util.CreateUri(XmlConstants.UriValueSegment, UriKind.Relative);
                }
                else
                {
                    relativeUri = Util.CreateUri(propertyName + (null != property.CollectionType ? "()" : String.Empty), UriKind.Relative);
                }
 
                requestUri = Util.CreateUri(box.GetResourceUri(this.baseUriWithSlash, true /*queryLink*/), relativeUri);
                requestVersion = Util.DataServiceVersion1;
            }
            else
            {
                // if we specify an URI, we need to keep the Execute<T> behaviour
                requestVersion = Util.DataServiceVersionEmpty;
            }
 
            HttpWebRequest request = this.CreateRequest(requestUri, XmlConstants.HttpMethodGet, mediaLink, null, requestVersion, false);
            DataServiceRequest dataServiceRequest = DataServiceRequest.GetInstance(property.PropertyType, requestUri);
            return new LoadPropertyResult(entity, propertyName, this, request, callback, state, dataServiceRequest, plan);
        }
 
        /// <summary>
        /// write the content section of the atom payload
        /// </summary>
        /// <param name="writer">writer</param>
        /// <param name="type">resource type</param>
        /// <param name="resource">resource value</param>
        /// <param name="currentSegment">Source EPM tree segment corresponding to <paramref name="type"/></param>
        /// <param name="propertiesWritten">True if we have written any property to the writer.</param>
        private void WriteContentProperties(XmlWriter writer, ClientType type, object resource, EpmSourcePathSegment currentSegment, out bool propertiesWritten)
        {
            #region <d:property>value</property>
            propertiesWritten = false;
            foreach (ClientType.ClientProperty property in type.Properties)
            {
                // don't write mime data member or the mime type member for it
                if (property == type.MediaDataMember ||
                    (type.MediaDataMember != null &&
                     type.MediaDataMember.MimeTypeProperty == property))
                {
                    continue;
                }
 
                object propertyValue = property.GetValue(resource);
 
                EpmSourcePathSegment matchedSegment = currentSegment != null ? currentSegment.SubProperties.SingleOrDefault(s => s.PropertyName == property.PropertyName) : null;
 
                if (property.IsKnownType)
                {
                    if (propertyValue == null || matchedSegment == null || matchedSegment.EpmInfo.Attribute.KeepInContent)
                    {
                        WriteContentProperty(writer, this.DataNamespace, property, propertyValue);
                        propertiesWritten = true;
                    }
                }
#if ASTORIA_OPEN_OBJECT
                else if (property.OpenObjectProperty)
                {
                    foreach (KeyValuePair<string, object> pair in (IDictionary<string, object>)propertyValue)
                    {
                        if ((null == pair.Value) || ClientConvert.IsKnownType(pair.Value.GetType()))
                        {
                            Type valueType = pair.Value != null ? pair.Value.GetType() : typeof(string);
                            ClientType.ClientProperty openProperty = new ClientType.ClientProperty(null, valueType, false, true);
                            WriteContentProperty(writer, this.DataNamespace, openProperty, pair.Value);
                            propertiesWritten = true;
                        }
                    }
                }
#endif
                else if (null == property.CollectionType)
                {
                    ClientType nested = ClientType.Create(property.PropertyType);
                    if (!nested.IsEntityType)
                    {
                        #region complex type
                        XElement complexProperty = new XElement(((XNamespace)this.DataNamespace) + property.PropertyName);
                        bool shouldWriteComplexProperty = false;
                        string typeName = this.ResolveNameFromType(nested.ElementType);
                        if (!String.IsNullOrEmpty(typeName))
                        {
                            complexProperty.Add(new XAttribute(((XNamespace)XmlConstants.DataWebMetadataNamespace) + XmlConstants.AtomTypeAttributeName, typeName));
                        }
                        
                        // Handle null values for complex types by putting m:null="true"
                        if (null == propertyValue)
                        {
                            complexProperty.Add(new XAttribute(((XNamespace)XmlConstants.DataWebMetadataNamespace) + XmlConstants.AtomNullAttributeName, XmlConstants.XmlTrueLiteral));
                            shouldWriteComplexProperty = true;
                        }
                        else
                        {
                            using (XmlWriter complexPropertyWriter = complexProperty.CreateWriter())
                            {
                                this.WriteContentProperties(complexPropertyWriter, nested, propertyValue, matchedSegment, out shouldWriteComplexProperty);
                            }
                        }
 
                        if (shouldWriteComplexProperty)
                        {
                            complexProperty.WriteTo(writer);
                            propertiesWritten = true;
                        }
                        #endregion
                    }
                }
            }
            #endregion
        }
 
        /// <summary>Detach existing link</summary>
        /// <param name="existingLink">link to detach</param>
        /// <param name="targetDelete">true if target is being deleted, false otherwise</param>
        private void DetachExistingLink(LinkDescriptor existingLink, bool targetDelete)
        {
            // The target can be null in which case we don't need this check
            if (existingLink.Target != null)
            {
                // Identify the target resource for the link
                EntityDescriptor targetResource = this.entityDescriptors[existingLink.Target];
                
                // Check if there is a dependency relationship b/w the source and target objects i.e. target can not exist without source link
                // Deep insert requires this check to be made but skip the check if the target object is being deleted
                if (targetResource.IsDeepInsert && !targetDelete)
                {
                    EntityDescriptor parentOfTarget = targetResource.ParentForInsert;
                    if (Object.ReferenceEquals(targetResource.ParentEntity, existingLink.Source) && 
                       (parentOfTarget.State != EntityStates.Deleted || 
                        parentOfTarget.State != EntityStates.Detached))
                    {
                        throw new InvalidOperationException(Strings.Context_ChildResourceExists);
                    }
                }
            }
        
            if (this.bindings.Remove(existingLink))
            {   // this link may have been previously detached by a detaching entity
                existingLink.State = EntityStates.Detached;
            }
        }
 
        /// <summary>
        /// find and detach link for reference property
        /// </summary>
        /// <param name="source">source entity</param>
        /// <param name="sourceProperty">source entity property name for target entity</param>
        /// <param name="target">target entity</param>
        /// <param name="linkMerge">link merge option</param>
        /// <returns>true if found and not removed</returns>
        private LinkDescriptor DetachReferenceLink(object source, string sourceProperty, object target, MergeOption linkMerge)
        {
            LinkDescriptor existing = this.GetLinks(source, sourceProperty).FirstOrDefault();
            if (null != existing)
            {
                if ((target == existing.Target) ||
                    (MergeOption.AppendOnly == linkMerge) ||
                    (MergeOption.PreserveChanges == linkMerge && EntityStates.Modified == existing.State))
                {
                    return existing;
                }
 
                // Since we don't support deep insert on reference property, no need to check for deep insert.
                this.DetachExistingLink(existing, false);
                Debug.Assert(!this.bindings.Values.Any(o => (o.Source == source) && (o.SourceProperty == sourceProperty)), "only expecting one");
            }
 
            return null;
        }
 
        /// <summary>
        /// verify the resource being tracked by context
        /// </summary>
        /// <param name="resource">resource</param>
        /// <param name="parameterName">parameter name to include in ArgumentException</param>
        /// <returns>The given resource.</returns>
        /// <exception cref="ArgumentException">if resource is not contained</exception>
        private EntityDescriptor EnsureContained(object resource, string parameterName)
        {
            Util.CheckArgumentNull(resource, parameterName);
 
            EntityDescriptor box = null;
            if (!this.entityDescriptors.TryGetValue(resource, out box))
            {
                throw Error.InvalidOperation(Strings.Context_EntityNotContained);
            }
 
            return box;
        }
 
        /// <summary>
        /// verify the source and target are relatable
        /// </summary>
        /// <param name="source">source Resource</param>
        /// <param name="sourceProperty">source Property</param>
        /// <param name="target">target Resource</param>
        /// <param name="state">destination state of relationship to evaluate for</param>
        /// <returns>true if DeletedState and one of the ends is in the added state</returns>
        /// <exception cref="ArgumentNullException">if source or target are null</exception>
        /// <exception cref="ArgumentException">if source or target are not contained</exception>
        /// <exception cref="ArgumentNullException">if source property is null</exception>
        /// <exception cref="ArgumentException">if source property empty</exception>
        /// <exception cref="InvalidOperationException">Can only relate ends with keys.</exception>
        /// <exception cref="ArgumentException">If target doesn't match property type.</exception>
        /// <exception cref="InvalidOperationException">If adding relationship where one of the ends is in the deleted state.</exception>
        /// <exception cref="InvalidOperationException">If attaching relationship where one of the ends is in the added or deleted state.</exception>
        private bool EnsureRelatable(object source, string sourceProperty, object target, EntityStates state)
        {
            EntityDescriptor sourceResource = this.EnsureContained(source, "source");
            EntityDescriptor targetResource = null;
            if ((null != target) || ((EntityStates.Modified != state) && (EntityStates.Unchanged != state)))
            {
                targetResource = this.EnsureContained(target, "target");
            }
 
            Util.CheckArgumentNotEmpty(sourceProperty, "sourceProperty");
 
            ClientType type = ClientType.Create(source.GetType());
            Debug.Assert(type.IsEntityType, "should be enforced by just adding an object");
 
            // will throw InvalidOperationException if property doesn't exist
            ClientType.ClientProperty property = type.GetProperty(sourceProperty, false);
 
            if (property.IsKnownType)
            {
                throw Error.InvalidOperation(Strings.Context_RelationNotRefOrCollection);
            }
 
            if ((EntityStates.Unchanged == state) && (null == target) && (null != property.CollectionType))
            {
                targetResource = this.EnsureContained(target, "target");
            }
 
            if (((EntityStates.Added == state) || (EntityStates.Deleted == state)) && (null == property.CollectionType))
            {
                throw Error.InvalidOperation(Strings.Context_AddLinkCollectionOnly);
            }
            else if ((EntityStates.Modified == state) && (null != property.CollectionType))
            {
                throw Error.InvalidOperation(Strings.Context_SetLinkReferenceOnly);
            }
 
            // if (property.IsCollection) then property.PropertyType is the collection elementType
            // either way you can only have a relation ship between keyed objects
            type = ClientType.Create(property.CollectionType ?? property.PropertyType);
            Debug.Assert(type.IsEntityType, "should be enforced by just adding an object");
 
            if ((null != target) && !type.ElementType.IsInstanceOfType(target))
            {
                // target is not of the correct type
                throw Error.Argument(Strings.Context_RelationNotRefOrCollection, "target");
            }
 
            if ((EntityStates.Added == state) || (EntityStates.Unchanged == state))
            {
                if ((sourceResource.State == EntityStates.Deleted) ||
                    ((targetResource != null) && (targetResource.State == EntityStates.Deleted)))
                {
                    // can't add/attach new relationship when source or target in deleted state
                    throw Error.InvalidOperation(Strings.Context_NoRelationWithDeleteEnd);
                }
            }
 
            if ((EntityStates.Deleted == state) || (EntityStates.Unchanged == state))
            {
                if ((sourceResource.State == EntityStates.Added) ||
                    ((targetResource != null) && (targetResource.State == EntityStates.Added)))
                {
                    // can't have non-added relationship when source or target is in added state
                    if (EntityStates.Deleted == state)
                    {
                        return true;
                    }
 
                    throw Error.InvalidOperation(Strings.Context_NoRelationWithInsertEnd);
                }
            }
 
            return false;
        }
 
        /// <summary>validate <paramref name="entitySetName"/> and trim leading and trailing forward slashes</summary>
        /// <param name="entitySetName">resource name to validate</param>
        /// <exception cref="ArgumentNullException">if entitySetName was null</exception>
        /// <exception cref="ArgumentException">if entitySetName was empty or contained only forward slash</exception>
        private void ValidateEntitySetName(ref string entitySetName)
        {
            Util.CheckArgumentNotEmpty(entitySetName, "entitySetName");
            entitySetName = entitySetName.Trim(Util.ForwardSlash);
 
            Util.CheckArgumentNotEmpty(entitySetName, "entitySetName");
 
            Uri tmp = Util.CreateUri(entitySetName, UriKind.RelativeOrAbsolute);
            if (tmp.IsAbsoluteUri ||
                !String.IsNullOrEmpty(Util.CreateUri(this.baseUriWithSlash, tmp)
                                     .GetComponents(UriComponents.Query | UriComponents.Fragment, UriFormat.SafeUnescaped)))
            {
                throw Error.Argument(Strings.Context_EntitySetName, "entitySetName");
            }
        }
 
        /// <summary>create this.identityToResource when necessary</summary>
        private void EnsureIdentityToResource()
        {
            if (null == this.identityToDescriptor)
            {
                System.Threading.Interlocked.CompareExchange(ref this.identityToDescriptor, new Dictionary<String, EntityDescriptor>(EqualityComparer<String>.Default), null);
            }
        }
 
        /// <summary>
        /// increment the resource change for sorting during submit changes
        /// </summary>
        /// <param name="descriptor">the resource to update the change order</param>
        private void IncrementChange(Descriptor descriptor)
        {
            descriptor.ChangeOrder = ++this.nextChange;
        }
 
        /// <summary>
        /// This method creates an async result object around a request to get the read stream for a Media Resource
        /// associated with the Media Link Entry represented by the entity object.
        /// </summary>
        /// <param name="entity">The entity which is the Media Link Entry for the requested Media Resource. Thist must specify
        /// a tracked entity in a non-added state.</param>
        /// <param name="args">Instance of <see cref="DataServiceRequestArgs"/> class with additional metadata for the request.
        /// Must not be null.</param>
        /// <param name="callback">User defined callback to be called when results are available. Can be null.</param>
        /// <param name="state">User state in IAsyncResult. Can be null.</param>
        /// <returns>The async result object for the request, the request hasn't been started yet.</returns>
        /// <exception cref="ArgumentNullException">Either entity or args parameters are null.</exception>
        /// <exception cref="ArgumentException">The specified entity is either not tracked, 
        /// is in the added state or it's not an MLE.</exception>
        private GetReadStreamResult CreateGetReadStreamResult(
            object entity, 
            DataServiceRequestArgs args,
            AsyncCallback callback, 
            object state)
        {
            EntityDescriptor box = this.EnsureContained(entity, "entity");
            Util.CheckArgumentNull(args, "args");
 
            Uri requestUri = box.GetMediaResourceUri(this.baseUriWithSlash);
            if (requestUri == null)
            {
                throw new ArgumentException(Strings.Context_EntityNotMediaLinkEntry, "entity");
            }
 
#if ASTORIA_LIGHT
            // For MR requests always use the ClientHtpp stack as the XHR doesn't support binary content
            HttpWebRequest request = this.CreateRequest(requestUri, XmlConstants.HttpMethodGet, true, null, null, false, HttpStack.ClientHttp);
#else
            HttpWebRequest request = this.CreateRequest(requestUri, XmlConstants.HttpMethodGet, true, null, null, false);
#endif
 
            WebUtil.ApplyHeadersToRequest(args.Headers, request, false);
 
            return new GetReadStreamResult(this, "GetReadStream", request, callback, state);
        }
 
        /// <summary>Stream wrapper for MR POST/PUT which also holds the information if the stream should be closed or not.</summary>
        internal class DataServiceSaveStream
        {
            /// <summary>The stream we are wrapping.
            /// Can be null in which case we didn't open it yet.</summary>
            private readonly Stream stream;
 
            // This class would one day hold a Func<Stream> to open the stream when needed
            //   instead of specifying the stream up front.
 
            /// <summary>Set to true if the stream should be closed once we're done with it.</summary>
            private readonly bool close;
 
            /// <summary>Arguments for the request when POST/PUT of the stream is issued.</summary>
            private readonly DataServiceRequestArgs args;
 
            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="stream">The stream to use.</param>
            /// <param name="close">Should the stream be closed before SaveChanges returns.</param>
            /// <param name="args">Additional arguments to apply to the request before sending it.</param>
            internal DataServiceSaveStream(Stream stream, bool close, DataServiceRequestArgs args)
            {
                Debug.Assert(stream != null, "stream must not be null.");
 
                this.stream = stream;
                this.close = close;
                this.args = args;
            }
 
            /// <summary>The stream to use.</summary>
            internal Stream Stream
            {
                get 
                {
                    return this.stream;
                }
            }
 
            /// <summary>
            /// Arguments to be used for creation of the HTTP request when POST/PUT for the MR is issued.
            /// </summary>
            internal DataServiceRequestArgs Args
            {
                get { return this.args; }
            }
 
            /// <summary>
            /// Close the stream if required.
            /// This is so that callers can simply call this method and don't have to care about the settings.
            /// </summary>
            internal void Close()
            {
                if (this.stream != null && this.close)
                {
                    this.stream.Close();
                }
            }
        }
 
        /// <summary>wrapper around loading a property from a response</summary>
        private class LoadPropertyResult : QueryResult
        {
            #region Private fields.
 
            /// <summary>entity whose property is being loaded</summary>
            private readonly object entity;
 
            /// <summary>Projection plan for loading results; possibly null.</summary>
            private readonly ProjectionPlan plan;
 
            /// <summary>name of the property on the entity that is being loaded</summary>
            private readonly string propertyName;
 
            #endregion Private fields.
 
            /// <summary>constructor</summary>
            /// <param name="entity">entity</param>
            /// <param name="propertyName">name of collection or reference property to load</param>
            /// <param name="context">Originating context</param>
            /// <param name="request">Originating WebRequest</param>
            /// <param name="callback">user callback</param>
            /// <param name="state">user state</param>
            /// <param name="dataServiceRequest">request object.</param>
            /// <param name="plan">Projection plan for materialization; possibly null.</param>
            internal LoadPropertyResult(object entity, string propertyName, DataServiceContext context, HttpWebRequest request, AsyncCallback callback, object state, DataServiceRequest dataServiceRequest, ProjectionPlan plan)
                : base(context, "LoadProperty", dataServiceRequest, request, callback, state)
            {
                this.entity = entity;
                this.propertyName = propertyName;
                this.plan = plan;
            }
 
            /// <summary>
            /// loading a property from a response
            /// </summary>
            /// <returns>QueryOperationResponse instance containing information about the response.</returns>
            internal QueryOperationResponse LoadProperty()
            {
                MaterializeAtom results = null;
 
                DataServiceContext context = (DataServiceContext)this.Source;
 
                ClientType type = ClientType.Create(this.entity.GetType());
                Debug.Assert(type.IsEntityType, "must be entity type to be contained");
 
                EntityDescriptor box = context.EnsureContained(this.entity, "entity");
 
                if (EntityStates.Added == box.State)
                {
                    throw Error.InvalidOperation(Strings.Context_NoLoadWithInsertEnd);
                }
 
                ClientType.ClientProperty property = type.GetProperty(this.propertyName, false);
                Type elementType = property.CollectionType ?? property.NullablePropertyType;
                try
                {
                    if (type.MediaDataMember == property)
                    {
                        results = this.ReadPropertyFromRawData(property);
                    }
                    else
                    {
                        results = this.ReadPropertyFromAtom(box, property);
                    }
                    
                    return this.GetResponseWithType(results, elementType);
                }
                catch (InvalidOperationException ex)
                {
                    QueryOperationResponse response = this.GetResponseWithType(results, elementType);
                    if (response != null)
                    {
                        response.Error = ex;
                        throw new DataServiceQueryException(Strings.DataServiceException_GeneralError, ex, response);
                    }
 
                    throw;
                }
            }
 
            /// <summary>
            /// Reads the data from the response stream into a buffer using the content length.
            /// </summary>
            /// <param name="responseStream">Response stream.</param>
            /// <param name="totalLength">Length of data to read.</param>
            /// <returns>byte array containing read data.</returns>
            private static byte[] ReadByteArrayWithContentLength(Stream responseStream, int totalLength)
            {
                byte[] buffer = new byte[totalLength];
                int read = 0;
                while (read < totalLength)
                {
                    int r = responseStream.Read(buffer, read, totalLength - read);
                    if (r <= 0)
                    {
                        throw Error.InvalidOperation(Strings.Context_UnexpectedZeroRawRead);
                    }
 
                    read += r;
                }
 
                return buffer;
            }
 
            /// <summary>Reads the data from the response stream in chunks.</summary>
            /// <param name="responseStream">Response stream.</param>
            /// <returns>byte array containing read data.</returns>
            private static byte[] ReadByteArrayChunked(Stream responseStream)
            {
                byte[] completeBuffer = null;
                using (MemoryStream m = new MemoryStream())
                {
                    byte[] buffer = new byte[4096];
                    int numRead = 0;
                    int totalRead = 0;
                    while (true)
                    {
                        numRead = responseStream.Read(buffer, 0, buffer.Length);
                        if (numRead <= 0)
                        {
                            break;
                        }
 
                        m.Write(buffer, 0, numRead);
                        totalRead += numRead;
                    }
 
                    completeBuffer = new byte[totalRead];
                    m.Position = 0;
                    numRead = m.Read(completeBuffer, 0, completeBuffer.Length);
                }
 
                return completeBuffer;
            }
 
            /// <summary>
            /// Load property data from an ATOM response
            /// </summary>
            /// <param name="box">Box pointing to the entity to load this to</param>
            /// <param name="property">The property being loaded</param>
            /// <returns>property values as IEnumerable.</returns>
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Returning MaterializeAtom, caller will dispose")]
            private MaterializeAtom ReadPropertyFromAtom(EntityDescriptor box, ClientType.ClientProperty property)
            {
                DataServiceContext context = (DataServiceContext)this.Source;
 
                bool merging = context.ApplyingChanges;
 
                try
                {
                    context.ApplyingChanges = true;
 
                    bool deletedState = (EntityStates.Deleted == box.State);
 
                    Type nestedType;
#if ASTORIA_OPEN_OBJECT
                if (property.OpenObjectProperty)
                {
                    nestedType = typeof(OpenObject);
                }
                else
#endif
                    {
                        nestedType = property.CollectionType ?? property.NullablePropertyType;
                    }
 
                    ClientType clientType = ClientType.Create(nestedType);
 
                    // when setting a reference, use the entity
                    // when adding an item to a collection, use the collection object referenced by the entity
                    bool setNestedValue = false;
                    object collection = this.entity;
                    if (null != property.CollectionType)
                    {   // get the collection that we actually add nested
                        collection = property.GetValue(this.entity);
                        if (null == collection)
                        {
                            setNestedValue = true;
                            if (BindingEntityInfo.IsDataServiceCollection(property.PropertyType))
                            {
                                Debug.Assert(WebUtil.GetDataServiceCollectionOfT(nestedType) != null, "DataServiceCollection<> must be available here.");
 
                                // new DataServiceCollection<nestedType>(null, TrackingMode.None)
                                collection = Activator.CreateInstance(
                                    WebUtil.GetDataServiceCollectionOfT(nestedType), 
                                    null,
                                    TrackingMode.None);
                            }
                            else
                            {
                                collection = Activator.CreateInstance(typeof(List<>).MakeGenericType(nestedType));
                            }
                        }
                    }
 
                    // store the results so that they can be there in the response body.
                    Type elementType = property.CollectionType ?? property.NullablePropertyType;
                    IList results = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType));
 
                    DataServiceQueryContinuation continuation = null;
 
                    // elementType.ElementType has Nullable stripped away, use nestedType for materializer
                    using (MaterializeAtom materializer = this.GetMaterializer(context, this.plan))
                    {
                        Debug.Assert(materializer != null, "materializer != null -- otherwise GetMaterializer() returned null rather than empty");
                        int count = 0;
#if ASTORIA_OPEN_OBJECT
                        object openProperties = null;
#endif
                        foreach (object child in materializer)
                        {
                            results.Add(child);
                            count++;
#if ASTORIA_OPEN_OBJECT
                            property.SetValue(collection, child, this.propertyName, ref openProperties, true);
#else
                            property.SetValue(collection, child, this.propertyName, true);
#endif
 
                            // via LoadProperty, you can have a property with <id> and null value
                            if ((null != child) && (MergeOption.NoTracking != materializer.MergeOptionValue) && clientType.IsEntityType)
                            {
                                if (deletedState)
                                {
                                    context.DeleteLink(this.entity, this.propertyName, child);
                                }
                                else
                                {   // put link into unchanged state
                                    context.AttachLink(this.entity, this.propertyName, child, materializer.MergeOptionValue);
                                }
                            }
                        }
 
                        // LoadProperty should only deal with single level collections
                        continuation = materializer.GetContinuation(null);
                        Util.SetNextLinkForCollection(collection, continuation);
 
                        // we don't do this because we are loading, not refreshing
                        // if ((0 == count) && (MergeOption.OverwriteChanges == this.mergeOption))
                        // { property.Clear(entity); }
                    }
 
                    if (setNestedValue)
                    {
#if ASTORIA_OPEN_OBJECT
                    object openProperties = null;
                    property.SetValue(this.entity, collection, this.propertyName, ref openProperties, false);
#else
                        property.SetValue(this.entity, collection, this.propertyName, false);
#endif
                    }
 
                    return MaterializeAtom.CreateWrapper(results, continuation);
                }
                finally
                {
                    context.ApplyingChanges = merging;
                }
            }
 
            /// <summary>
            /// Load property data form a raw response
            /// </summary>
            /// <param name="property">The property being loaded</param>
            /// <returns>property values as IEnumerable.</returns>
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Returning MaterializeAtom, caller will dispose")]
            private MaterializeAtom ReadPropertyFromRawData(ClientType.ClientProperty property)
            {
                DataServiceContext context = (DataServiceContext)this.Source;
 
                bool merging = context.ApplyingChanges;
 
                try
                {
                    context.ApplyingChanges = true;
 
                    // if this is the data property for a media entry, what comes back
                    // is the raw value (no markup)
#if ASTORIA_OPEN_OBJECT
                object openProps = null;
#endif
                    string mimeType = null;
                    Encoding encoding = null;
                    Type elementType = property.CollectionType ?? property.NullablePropertyType;
                    IList results = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType));
                    HttpProcessUtility.ReadContentType(this.ContentType, out mimeType, out encoding);
 
                    using (Stream responseStream = this.GetResponseStream())
                    {
                        // special case byte[], and for everything else let std conversion kick-in
                        if (property.PropertyType == typeof(byte[]))
                        {
                            int total = checked((int)this.ContentLength);
                            byte[] buffer = null;
                            if (total >= 0)
                            {
                                buffer = LoadPropertyResult.ReadByteArrayWithContentLength(responseStream, total);
                            }
                            else
                            {
                                buffer = LoadPropertyResult.ReadByteArrayChunked(responseStream);
                            }
 
                            results.Add(buffer);
#if ASTORIA_OPEN_OBJECT
                            property.SetValue(this.entity, buffer, this.propertyName, ref openProps, false);
#else
                            property.SetValue(this.entity, buffer, this.propertyName, false);
#endif
                        }
                        else
                        {
                            // responseStream will disposed, StreamReader doesn't need to dispose of it.
                            StreamReader reader = new StreamReader(responseStream, encoding);
                            object convertedValue = property.PropertyType == typeof(string) ?
                                                        reader.ReadToEnd() :
                                                        ClientConvert.ChangeType(reader.ReadToEnd(), property.PropertyType);
                            results.Add(convertedValue);
#if ASTORIA_OPEN_OBJECT
                            property.SetValue(this.entity, convertedValue, this.propertyName, ref openProps, false);
#else
                            property.SetValue(this.entity, convertedValue, this.propertyName, false);
#endif
                        }
                    }
 
#if ASTORIA_OPEN_OBJECT
                Debug.Assert(openProps == null, "These should not be set in this path");
#endif
                    if (property.MimeTypeProperty != null)
                    {
                        // an implication of this 3rd-arg-null is that mime type properties cannot be open props
#if ASTORIA_OPEN_OBJECT
                    property.MimeTypeProperty.SetValue(this.entity, mimeType, null, ref openProps, false);
                    Debug.Assert(openProps == null, "These should not be set in this path");
#else
                        property.MimeTypeProperty.SetValue(this.entity, mimeType, null, false);
#endif
                    }
 
                    return MaterializeAtom.CreateWrapper(results);
                }
                finally
                {
                    context.ApplyingChanges = merging;
                }
            }
        }
 
        /// <summary>
        /// implementation of IAsyncResult for SaveChanges
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Pending")]
        private class SaveResult : BaseAsyncResult
        {
            /// <summary>where to pull the changes from</summary>
            private readonly DataServiceContext Context;
 
            /// <summary>sorted list of entries by change order</summary>
            private readonly List<Descriptor> ChangedEntries;
 
            /// <summary>array of queries being executed</summary>
            private readonly DataServiceRequest[] Queries;
 
            /// <summary>operations</summary>
            private readonly List<OperationResponse> Responses;
 
            /// <summary>boundary used when generating batch boundary</summary>
            private readonly string batchBoundary;
 
            /// <summary>option in use for SaveChanges</summary>
            private readonly SaveChangesOptions options;
 
            /// <summary>if true then async, else sync</summary>
            private readonly bool executeAsync;
 
            /// <summary>debugging trick to track number of completed requests</summary>
            private int changesCompleted;
 
            /// <summary>wrapped request</summary>
            private PerRequest request;
 
            /// <summary>batch web response</summary>
            private HttpWebResponse batchResponse;
 
            /// <summary>response stream for the batch</summary>
            private Stream httpWebResponseStream;
 
            /// <summary>service response</summary>
            private DataServiceResponse service;
 
            /// <summary>The ResourceBox or RelatedEnd currently in flight</summary>
            private int entryIndex = -1;
 
            /// <summary>
            /// True if the current in-flight request is an MR POST or PUT
            /// that might need to be followed by a PUT for the MLE
            /// </summary>
            private bool processingMediaLinkEntry;
 
            /// <summary>
            /// True if the current in-flight request is an MR PUT
            /// that might need to be followed by a PUT for the MLE
            /// </summary>
            private bool processingMediaLinkEntryPut;
 
            /// <summary>
            /// If the <see cref="processingMediaLinkEntry"/> is set to true
            /// this field holds a stream which contains the body of the MR POST request
            /// to be sent.
            /// This can be null in the case where the content of MR is empty. (In which case
            /// we will not try to open the request stream and thus avoid additional async call).
            /// </summary>
            private Stream mediaResourceRequestStream;
 
            /// <summary>response stream</summary>
            private BatchStream responseBatchStream;
 
            /// <summary>temporary buffer when cache results from CUD op in non-batching save changes</summary>
            private byte[] buildBatchBuffer;
 
            /// <summary>temporary writer when cache results from CUD op in non-batching save changes</summary>
            private StreamWriter buildBatchWriter;
 
            /// <summary>count of data actually copied</summary>
            private long copiedContentLength;
 
            /// <summary>what is the changset boundary</summary>
            private string changesetBoundary;
 
            /// <summary>is a change set being cached</summary>
            private bool changesetStarted;
 
            #region constructors
            /// <summary>
            /// constructor for operations
            /// </summary>
            /// <param name="context">context</param>
            /// <param name="method">method</param>
            /// <param name="queries">queries</param>
            /// <param name="options">options</param>
            /// <param name="callback">user callback</param>
            /// <param name="state">user state object</param>
            /// <param name="async">async or sync</param>
            internal SaveResult(DataServiceContext context, string method, DataServiceRequest[] queries, SaveChangesOptions options, AsyncCallback callback, object state, bool async)
                : base(context, method, callback, state)
            {
                this.executeAsync = async;
                this.Context = context;
                this.Queries = queries;
                this.options = options;
 
                this.Responses = new List<OperationResponse>();
 
                if (null == queries)
                {
                    #region changed entries
                    this.ChangedEntries = context.entityDescriptors.Values.Cast<Descriptor>()
                                          .Union(context.bindings.Values.Cast<Descriptor>())
                                          .Where(o => o.IsModified && o.ChangeOrder != UInt32.MaxValue)
                                          .OrderBy(o => o.ChangeOrder)
                                          .ToList();
 
                    foreach (Descriptor e in this.ChangedEntries)
                    {
                        e.ContentGeneratedForSave = false;
                        e.SaveResultWasProcessed = 0;
                        e.SaveError = null;
 
                        if (!e.IsResource)
                        {
                            object target = ((LinkDescriptor)e).Target;
                            if (null != target)
                            {
                                Descriptor f = context.entityDescriptors[target];
                                if (EntityStates.Unchanged == f.State)
                                {
                                    f.ContentGeneratedForSave = false;
                                    f.SaveResultWasProcessed = 0;
                                    f.SaveError = null;
                                }
                            }
                        }
                    }
                    #endregion
                }
                else
                {
                    this.ChangedEntries = new List<Descriptor>();
                }
 
                if (IsFlagSet(options, SaveChangesOptions.Batch))
                {
                    this.batchBoundary = XmlConstants.HttpMultipartBoundaryBatch + "_" + Guid.NewGuid().ToString();
                }
                else
                {
                    this.batchBoundary = XmlConstants.HttpMultipartBoundaryBatchResponse + "_" + Guid.NewGuid().ToString();
                    this.DataServiceResponse = new DataServiceResponse(null, -1, this.Responses, false /*batchResponse*/);
                }
            }
            #endregion constructor
 
            #region end
 
            /// <summary>generate the batch request of all changes to save</summary>
            internal DataServiceResponse DataServiceResponse
            {
                get
                {
                    return this.service;
                }
 
                set
                {
                    this.service = value;
                }
            }
 
            /// <summary>process the batch</summary>
            /// <returns>data service response</returns>
            internal DataServiceResponse EndRequest()
            {
                // Close all Save streams before we return (and do this before we throw for any errors below)
                foreach (EntityDescriptor box in this.ChangedEntries.Where(e => e.IsResource).Cast<EntityDescriptor>())
                {
                    box.CloseSaveStream();
                }
 
                // This will process the responses and throw if failure was detected
                if ((null != this.responseBatchStream) || (null != this.httpWebResponseStream))
                {
                    this.HandleBatchResponse();
                }
 
                return this.DataServiceResponse;
            }
 
            #endregion
 
            #region start a batch
 
            /// <summary>initial the async batch save changeset</summary>
            /// <param name="replaceOnUpdate">whether we need to update MERGE or PUT method for update.</param>
            internal void BatchBeginRequest(bool replaceOnUpdate)
            {
                PerRequest pereq = null;
                try
                {
                    MemoryStream memory = this.GenerateBatchRequest(replaceOnUpdate);
                    if (null != memory)
                    {
                        HttpWebRequest httpWebRequest = this.CreateBatchRequest(memory);
                        this.Abortable = httpWebRequest;
 
                        this.request = pereq = new PerRequest();
                        pereq.Request = httpWebRequest;
                        pereq.RequestContentStream = new PerRequest.ContentStream(memory, true);
 
                        this.httpWebResponseStream = new MemoryStream();
 
                        IAsyncResult asyncResult = BaseAsyncResult.InvokeAsync(httpWebRequest.BeginGetRequestStream, this.AsyncEndGetRequestStream, pereq);
                        pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
                    }
                    else
                    {
                        Debug.Assert(this.CompletedSynchronously, "completedSynchronously");
                        Debug.Assert(this.IsCompletedInternally, "completed");
                    }
                }
                catch (Exception e)
                {
                    this.HandleFailure(pereq, e);
                    throw; // to user on BeginSaveChangeSet, will still invoke Callback
                }
                finally
                {
                    this.HandleCompleted(pereq); // will invoke user callback
                }
 
                Debug.Assert((this.CompletedSynchronously && this.IsCompleted) || !this.CompletedSynchronously, "sync without complete");
            }
 
#if !ASTORIA_LIGHT // Synchronous methods not available
            /// <summary>
            /// Synchronous batch request
            /// </summary>
            /// <param name="replaceOnUpdate">whether we need to update MERGE or PUT method for update.</param>
            internal void BatchRequest(bool replaceOnUpdate)
            {
                MemoryStream memory = this.GenerateBatchRequest(replaceOnUpdate);
                if ((null != memory) && (0 < memory.Length))
                {
                    HttpWebRequest httpWebRequest = this.CreateBatchRequest(memory);
                    using (System.IO.Stream requestStream = httpWebRequest.GetRequestStream())
                    {
                        byte[] buffer = memory.GetBuffer();
                        int bufferOffset = checked((int)memory.Position);
                        int bufferLength = checked((int)memory.Length) - bufferOffset;
 
                        // the following is useful in the debugging Immediate Window
                        // string x = System.Text.Encoding.UTF8.GetString(buffer, bufferOffset, bufferLength);
                        requestStream.Write(buffer, bufferOffset, bufferLength);
                    }
 
                    HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
                    this.batchResponse = httpWebResponse;
 
                    if (null != httpWebResponse)
                    {
                        this.httpWebResponseStream = httpWebResponse.GetResponseStream();
                    }
                }
            }
#endif
            #endregion
 
            #region start a non-batch requests
            /// <summary>
            /// This starts the next change
            /// </summary>
            /// <param name="replaceOnUpdate">whether we need to update MERGE or PUT method for update.</param>
            internal void BeginNextChange(bool replaceOnUpdate)
            {
                Debug.Assert(!this.IsCompletedInternally, "why being called if already completed?");
 
                // SaveCallback can't chain synchronously completed responses, caller will loop the to next change
                PerRequest pereq = null;
                IAsyncResult asyncResult = null;
                do
                {
                    HttpWebRequest httpWebRequest = null;
                    HttpWebResponse response = null;
                    try
                    {
                        if (null != this.request)
                        {
                            this.SetCompleted();
                            Error.ThrowInternalError(InternalError.InvalidBeginNextChange);
                        }
 
                        this.Abortable = httpWebRequest = this.CreateNextRequest(replaceOnUpdate);
                        if ((null != httpWebRequest) || (this.entryIndex < this.ChangedEntries.Count))
                        {
                            if (this.ChangedEntries[this.entryIndex].ContentGeneratedForSave)
                            {
                                Debug.Assert(this.ChangedEntries[this.entryIndex] is LinkDescriptor, "only expected RelatedEnd to presave");
                                Debug.Assert(
                                    this.ChangedEntries[this.entryIndex].State == EntityStates.Added ||
                                    this.ChangedEntries[this.entryIndex].State == EntityStates.Modified,
                                    "only expected added to presave");
                                continue;
                            }
 
                            PerRequest.ContentStream contentStream = this.CreateChangeData(this.entryIndex, false);
                            if (this.executeAsync)
                            {
                                #region async
                                this.request = pereq = new PerRequest();
                                pereq.Request = httpWebRequest;
 
                                if (null == contentStream || null == contentStream.Stream)
                                {
                                    asyncResult = BaseAsyncResult.InvokeAsync(httpWebRequest.BeginGetResponse, this.AsyncEndGetResponse, pereq);
                                }
                                else
                                {
                                    if (contentStream.IsKnownMemoryStream)
                                    {
                                        httpWebRequest.ContentLength = contentStream.Stream.Length - contentStream.Stream.Position;
                                    }
 
                                    pereq.RequestContentStream = contentStream;
                                    asyncResult = BaseAsyncResult.InvokeAsync(httpWebRequest.BeginGetRequestStream, this.AsyncEndGetRequestStream, pereq);
                                }
 
                                pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
                                this.CompletedSynchronously &= asyncResult.CompletedSynchronously;
                                #endregion
                            }
#if !ASTORIA_LIGHT // Synchronous methods not available
                            else
                            {
                                #region sync
                                if (null != contentStream && null != contentStream.Stream)
                                {
                                    if (contentStream.IsKnownMemoryStream)
                                    {
                                        httpWebRequest.ContentLength = contentStream.Stream.Length - contentStream.Stream.Position;
                                    }
 
                                    using (Stream stream = httpWebRequest.GetRequestStream())
                                    {
                                        byte[] buffer = new byte[64 * 1024];
                                        int read;
                                        do
                                        {
                                            read = contentStream.Stream.Read(buffer, 0, buffer.Length);
                                            if (read > 0)
                                            {
                                                stream.Write(buffer, 0, read);
                                            }
                                        }
                                        while (read > 0);
                                    }
                                }
 
                                response = (HttpWebResponse)httpWebRequest.GetResponse();
                                if (!this.processingMediaLinkEntry)
                                {
                                    this.changesCompleted++;
                                }
 
                                this.HandleOperationResponse(response);
                                this.HandleOperationResponseData(response);
                                this.HandleOperationEnd();
                                this.request = null;
                                #endregion
                            }
#endif
                        }
                        else
                        {
                            this.SetCompleted();
 
                            if (this.CompletedSynchronously)
                            {
                                this.HandleCompleted(pereq);
                            }
                        }
                    }
                    catch (InvalidOperationException e)
                    {
                        WebUtil.GetHttpWebResponse(e, ref response);
                        this.HandleOperationException(e, response);
                        this.HandleCompleted(pereq);
                    }
                    finally
                    {
                        if (null != response)
                        {
                            response.Close();
                        }
                    }
 
                    // either everything completed synchronously until a change is saved and its state changed
                    // and we don't return to this loop until then or something was asynchronous
                    // and we won't continue in this loop, instead letting the inner most loop start the next request
                }
                while (((null == pereq) || (pereq.RequestCompleted && asyncResult != null && asyncResult.CompletedSynchronously)) && !this.IsCompletedInternally);
 
                Debug.Assert(this.executeAsync || this.CompletedSynchronously, "sync !CompletedSynchronously");
                Debug.Assert((this.CompletedSynchronously && this.IsCompleted) || !this.CompletedSynchronously, "sync without complete");
                Debug.Assert(this.entryIndex < this.ChangedEntries.Count || this.ChangedEntries.All(o => o.ContentGeneratedForSave), "didn't generate content for all entities/links");
            }
 
            /// <summary>cleanup work to do once the batch / savechanges is complete</summary>
            protected override void CompletedRequest()
            {
                this.buildBatchBuffer = null;
                if (null != this.buildBatchWriter)
                {
                    Debug.Assert(!IsFlagSet(this.options, SaveChangesOptions.Batch), "should be non-batch");
                    this.HandleOperationEnd();
                    this.buildBatchWriter.WriteLine("--{0}--", this.batchBoundary);
 
                    this.buildBatchWriter.Flush();
                    Debug.Assert(Object.ReferenceEquals(this.httpWebResponseStream, this.buildBatchWriter.BaseStream), "expected different stream");
                    this.httpWebResponseStream.Position = 0;
 
                    this.buildBatchWriter = null;
 
                    // the following is useful in the debugging Immediate Window
                    // string x = System.Text.Encoding.UTF8.GetString(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
                    this.responseBatchStream = new BatchStream(this.httpWebResponseStream, this.batchBoundary, HttpProcessUtility.EncodingUtf8NoPreamble, false);
                }
            }
 
            /// <summary>verify non-null and not completed</summary>
            /// <param name="value">the request in progress</param>
            /// <param name="errorcode">error code if null or completed</param>
            private static void CompleteCheck(PerRequest value, InternalError errorcode)
            {
                if ((null == value) || value.RequestCompleted)
                {
                    // since PerRequest is nested, it won't get set true during Abort unlike BaseAsyncResult
                    // but like QueryAsyncResult, when the request is aborted it it lets the request throw on next operation
                    Error.ThrowInternalError(errorcode);
                }
            }
 
            /// <summary>verify they have the same reference</summary>
            /// <param name="actual">the actual thing</param>
            /// <param name="expected">the expected thing</param>
            /// <param name="errorcode">error code if they are not</param>
            private static void EqualRefCheck(PerRequest actual, PerRequest expected, InternalError errorcode)
            {
                if (!Object.ReferenceEquals(actual, expected))
                {
                    Error.ThrowInternalError(errorcode);
                }
            }
 
            /// <summary>Set the AsyncWait and invoke the user callback.</summary>
            /// <param name="pereq">the request object</param>
            private void HandleCompleted(PerRequest pereq)
            {
                if (null != pereq)
                {
                    this.CompletedSynchronously &= pereq.RequestCompletedSynchronously;
 
                    if (pereq.RequestCompleted)
                    {
                        System.Threading.Interlocked.CompareExchange(ref this.request, null, pereq);
                        if (IsFlagSet(this.options, SaveChangesOptions.Batch))
                        {   // all competing thread must complete this before user calback is invoked
                            System.Threading.Interlocked.CompareExchange(ref this.batchResponse, pereq.HttpWebResponse, null);
                            pereq.HttpWebResponse = null;
                        }
 
                        pereq.Dispose();
                    }
                }
 
                this.HandleCompleted();
            }
 
            /// <summary>Cache the exception that happened on the background thread for the caller of EndSaveChanges.</summary>
            /// <param name="pereq">the request object</param>
            /// <param name="e">exception object from background thread</param>
            /// <returns>true if the exception should be rethrown</returns>
            private bool HandleFailure(PerRequest pereq, Exception e)
            {
                if (null != pereq)
                {
                    if (IsAborted)
                    {
                        pereq.SetAborted();
                    }
                    else
                    {
                        pereq.SetComplete();
                    }
                }
 
                return this.HandleFailure(e);
            }
 
            /// <summary>
            /// Create HttpWebRequest from the next availabe resource
            /// </summary>
            /// <param name="replaceOnUpdate">whether we need to update MERGE or PUT method for update.</param>
            /// <returns>web request</returns>
            private HttpWebRequest CreateNextRequest(bool replaceOnUpdate)
            {
                if (!this.processingMediaLinkEntry)
                {
                    this.entryIndex++;
                }
                else
                {
                    // If the previous request was an MR request the next one might be a PUT for the MLE
                    //   but if the entity was not changed (just the MR changed) no PUT for MLE should be sent
                    Debug.Assert(this.ChangedEntries[this.entryIndex].IsResource, "Only resources can have MR's.");
                    EntityDescriptor box = (EntityDescriptor)this.ChangedEntries[this.entryIndex];
                    if (this.processingMediaLinkEntryPut && EntityStates.Unchanged == box.State)
                    {
                        // Only the MR changed. In this case we also need to mark the entry as processed to notify 
                        //   that the content for save has been generated as there's not going to be another request for it.
                        box.ContentGeneratedForSave = true;
                        this.entryIndex++;
                    }
 
                    this.processingMediaLinkEntry = false;
                    this.processingMediaLinkEntryPut = false;
 
                    // In any case also close the save stream if there's any and forget about it
                    // for POST this is just a good practice to do so as soon as possible
                    // for PUT it's actually required for us to recognize that we already processed the MR part of the change
                    box.CloseSaveStream();
                }
 
                if (unchecked((uint)this.entryIndex < (uint)this.ChangedEntries.Count))
                {
                    Descriptor entry = this.ChangedEntries[this.entryIndex];
                    if (entry.IsResource)
                    {
                        EntityDescriptor box = (EntityDescriptor)entry;
 
                        HttpWebRequest req;
                        if (((EntityStates.Unchanged == entry.State) || (EntityStates.Modified == entry.State)) &&
                            (null != (req = this.CheckAndProcessMediaEntryPut(box))))
                        {
                            this.processingMediaLinkEntry = true;
                            this.processingMediaLinkEntryPut = true;
                        }
                        else if ((EntityStates.Added == entry.State) && (null != (req = this.CheckAndProcessMediaEntryPost(box))))
                        {
                            this.processingMediaLinkEntry = true;
                            this.processingMediaLinkEntryPut = false;
                        }
                        else
                        {
                            Debug.Assert(!this.processingMediaLinkEntry || entry.State == EntityStates.Modified, "!this.processingMediaLinkEntry || entry.State == EntityStates.Modified");
                            req = this.Context.CreateRequest(box, entry.State, replaceOnUpdate);
                        }
 
                        return req;
                    }
 
                    return this.Context.CreateRequest((LinkDescriptor)entry);
                }
 
                return null;
            }
 
            /// <summary>
            /// Check to see if the resource to be inserted is a media entry, and if so
            /// setup a POST request for the media content first and turn the rest of 
            /// the operation into a PUT to update the rest of the properties.
            /// </summary>
            /// <param name="entityDescriptor">The resource to check/process</param>
            /// <returns>A web request setup to do POST the media resource</returns>
            private HttpWebRequest CheckAndProcessMediaEntryPost(EntityDescriptor entityDescriptor)
            {
                // 
                ClientType type = ClientType.Create(entityDescriptor.Entity.GetType());
 
                if (!type.IsMediaLinkEntry && !entityDescriptor.IsMediaLinkEntry)
                {
                    // this is not a media link entry, process normally
                    return null;
                }
 
                if (type.MediaDataMember == null && entityDescriptor.SaveStream == null)
                {
                    // The entity is marked as MLE but we don't have the content property
                    //   and the user didn't set the save stream.
                    throw Error.InvalidOperation(Strings.Context_MLEWithoutSaveStream(type.ElementTypeName));
                }
 
                Debug.Assert(
                    (type.MediaDataMember != null && entityDescriptor.SaveStream == null) ||
                    (type.MediaDataMember == null && entityDescriptor.SaveStream != null),
                    "Only one way of specifying the MR content is allowed.");
 
                HttpWebRequest mediaRequest = this.CreateMediaResourceRequest(
                    entityDescriptor.GetResourceUri(this.Context.baseUriWithSlash, false /*queryLink*/),
                    XmlConstants.HttpMethodPost,
                    type.MediaDataMember == null);
 
                if (type.MediaDataMember != null)
                {
                    if (type.MediaDataMember.MimeTypeProperty == null)
                    {
                        mediaRequest.ContentType = XmlConstants.MimeApplicationOctetStream;
                    }
                    else
                    {
                        object mimeTypeValue = type.MediaDataMember.MimeTypeProperty.GetValue(entityDescriptor.Entity);
                        String mimeType = mimeTypeValue != null ? mimeTypeValue.ToString() : null;
 
                        if (String.IsNullOrEmpty(mimeType))
                        {
                            throw Error.InvalidOperation(
                                Strings.Context_NoContentTypeForMediaLink(
                                    type.ElementTypeName,
                                    type.MediaDataMember.MimeTypeProperty.PropertyName));
                        }
 
                        mediaRequest.ContentType = mimeType;
                    }
 
                    object value = type.MediaDataMember.GetValue(entityDescriptor.Entity);
                    if (value == null)
                    {
                        mediaRequest.ContentLength = 0;
                        this.mediaResourceRequestStream = null;
                    }
                    else
                    {
                        byte[] buffer = value as byte[];
                        if (buffer == null)
                        {
                            string mime;
                            Encoding encoding;
                            HttpProcessUtility.ReadContentType(mediaRequest.ContentType, out mime, out encoding);
 
                            if (encoding == null)
                            {
                                encoding = Encoding.UTF8;
                                mediaRequest.ContentType += XmlConstants.MimeTypeUtf8Encoding;
                            }
 
                            buffer = encoding.GetBytes(ClientConvert.ToString(value, false /* atomDateConstruct */));
                        }
 
                        mediaRequest.ContentLength = buffer.Length;
 
                        // Need to specify that the buffer is publicly visible as we need to access it later on
                        this.mediaResourceRequestStream = new MemoryStream(buffer, 0, buffer.Length, false, true);
                    }
                }
                else
                {
                    this.SetupMediaResourceRequest(mediaRequest, entityDescriptor);
                }
 
                // Convert the insert into an update for the media link entry we just created
                // (note that the identity still needs to be fixed up on the resbox once
                // the response comes with the 'location' header; that happens during processing
                // of the response in SavedResource())
                entityDescriptor.State = EntityStates.Modified;
 
                return mediaRequest;
            }
 
            /// <summary>
            /// Checks if the resource box represents an MLE with modified MR and if so creates a PUT request
            ///   to update the MR.
            /// </summary>
            /// <param name="box">The resource box for the entity to be checked.</param>
            /// <returns>Newly created MR PUT request or null if the entity is not MLE or its MR hasn't changed.</returns>
            private HttpWebRequest CheckAndProcessMediaEntryPut(EntityDescriptor box)
            {
                // If there's no save stream associated with the entity it's not MLE or its MR hasn't changed
                //  (which for purposes of PUT is the same anyway)
                if (box.SaveStream == null)
                {
                    return null;
                }
 
                Uri requestUri = box.GetEditMediaResourceUri(this.Context.baseUriWithSlash);
                if (requestUri == null)
                {
                    throw Error.InvalidOperation(
                        Strings.Context_SetSaveStreamWithoutEditMediaLink);
                }
 
                HttpWebRequest mediaResourceRequest = this.CreateMediaResourceRequest(requestUri, XmlConstants.HttpMethodPut, true);
                this.SetupMediaResourceRequest(mediaResourceRequest, box);
 
                if (box.StreamETag != null)
                {
                    mediaResourceRequest.Headers.Set(HttpRequestHeader.IfMatch, box.StreamETag);
                }
 
                return mediaResourceRequest;
            }
 
            /// <summary>
            /// Creates HTTP request for the media resource (MR)
            /// </summary>
            /// <param name="requestUri">The URI to request</param>
            /// <param name="method">The HTTP method to use (POST or PUT)</param>
            /// <param name="sendChunked">Send the request using chunked encoding to avoid buffering.</param>
            /// <returns>The newly created HTTP request object.</returns>
            private HttpWebRequest CreateMediaResourceRequest(Uri requestUri, string method, bool sendChunked)
            {
#if ASTORIA_LIGHT
                // For MR requests always use the ClientHtpp stack as the XHR doesn't support binary content
                HttpWebRequest mediaResourceRequest = this.Context.CreateRequest(
                    requestUri,
                    method,
                    false,
                    XmlConstants.MimeAny,
                    Util.DataServiceVersion1,
                    sendChunked,
                    HttpStack.ClientHttp);
#else
                HttpWebRequest mediaResourceRequest = this.Context.CreateRequest(
                    requestUri,
                    method,
                    false,
                    XmlConstants.MimeAny,
                    Util.DataServiceVersion1,
                    sendChunked);
#endif
                return mediaResourceRequest;
            }
 
            /// <summary>
            /// Sets the content and the headers of the media resource request
            /// </summary>
            /// <param name="mediaResourceRequest">The request to setup</param>
            /// <param name="box">The resource box for the entity with the MT</param>
            /// <remarks>This only works with the V2 MR support (SetSaveStream), this will not setup
            /// the request for V1 property based MRs.</remarks>
            private void SetupMediaResourceRequest(HttpWebRequest mediaResourceRequest, EntityDescriptor box)
            {
                // Get the write stream for this MR
                this.mediaResourceRequestStream = box.SaveStream.Stream;
 
                // Apply the arguments for the request
                WebUtil.ApplyHeadersToRequest(box.SaveStream.Args.Headers, mediaResourceRequest, true);
 
                // Do NOT set the ContentLength since we don't know if the stream even supports reporting its length
            }
 
            /// <summary>
            /// create memory stream for entry (entity or link)
            /// </summary>
            /// <param name="index">index into changed entries</param>
            /// <param name="newline">include newline in output</param>
            /// <returns>stream of data for entry</returns>
            private PerRequest.ContentStream CreateChangeData(int index, bool newline)
            {
                Descriptor entry = this.ChangedEntries[index];
                Debug.Assert(!entry.ContentGeneratedForSave, "already saved entity/link");
 
                if (entry.IsResource)
                {
                    EntityDescriptor box = (EntityDescriptor)entry;
                    if (this.processingMediaLinkEntry)
                    {
                        Debug.Assert(
                            this.processingMediaLinkEntryPut || entry.State == EntityStates.Modified, 
                            "We should have modified the MLE state to Modified when we've created the MR POST request.");
                        Debug.Assert(
                            !this.processingMediaLinkEntryPut || (entry.State == EntityStates.Unchanged || entry.State == EntityStates.Modified),
                            "If we're processing MR PUT the entity must be either in Unchanged or Modified state.");
 
                        // media resource request - we already precreated the body of the request
                        // in the CheckAndProcessMediaEntryPost or CheckAndProcessMediaEntryPut method.
                        Debug.Assert(this.mediaResourceRequestStream != null, "We should have precreated the MR stream already.");
                        return new PerRequest.ContentStream(this.mediaResourceRequestStream, false);
                    }
                    else
                    {
                        // either normal entity or second call for media link entity, generate content payload
                        // else first call of media link entry where we only send the default value
                        entry.ContentGeneratedForSave = true;
                        return new PerRequest.ContentStream(this.Context.CreateRequestData(box, newline), true);
                    }
                }
                else
                {
                    entry.ContentGeneratedForSave = true;
                    LinkDescriptor link = (LinkDescriptor)entry;
                    if ((EntityStates.Added == link.State) ||
                        ((EntityStates.Modified == link.State) && (null != link.Target)))
                    {
                        return new PerRequest.ContentStream(this.Context.CreateRequestData(link, newline), true);
                    }
                }
 
                return null;
            }
            #endregion
 
            #region generate batch response from non-batch
 
            /// <summary>basic separator between response</summary>
            private void HandleOperationStart()
            {
                this.HandleOperationEnd();
 
                if (null == this.httpWebResponseStream)
                {
                    this.httpWebResponseStream = new MemoryStream();
                }
 
                if (null == this.buildBatchWriter)
                {
                    this.buildBatchWriter = new StreamWriter(this.httpWebResponseStream);     // defaults to UTF8 w/o preamble
#if TESTUNIXNEWLINE
                    this.buildBatchWriter.NewLine = NewLine;
#endif
                }
 
                if (null == this.changesetBoundary)
                {
                    this.changesetBoundary = XmlConstants.HttpMultipartBoundaryChangesetResponse + "_" + Guid.NewGuid().ToString();
                }
 
                this.changesetStarted = true;
                this.buildBatchWriter.WriteLine("--{0}", this.batchBoundary);
                this.buildBatchWriter.WriteLine("{0}: {1}; boundary={2}", XmlConstants.HttpContentType, XmlConstants.MimeMultiPartMixed, this.changesetBoundary);
                this.buildBatchWriter.WriteLine();
                this.buildBatchWriter.WriteLine("--{0}", this.changesetBoundary);
            }
 
            /// <summary>write the trailing --changesetboundary--</summary>
            private void HandleOperationEnd()
            {
                if (this.changesetStarted)
                {
                    Debug.Assert(null != this.buildBatchWriter, "buildBatchWriter");
                    Debug.Assert(null != this.changesetBoundary, "changesetBoundary");
                    this.buildBatchWriter.WriteLine();
                    this.buildBatchWriter.WriteLine("--{0}--", this.changesetBoundary);
                    this.changesetStarted = false;
                }
            }
 
            /// <summary>operation with exception</summary>
            /// <param name="e">exception object</param>
            /// <param name="response">response object</param>
            private void HandleOperationException(Exception e, HttpWebResponse response)
            {
                if (null != response)
                {
                    this.HandleOperationResponse(response);
                    this.HandleOperationResponseData(response);
                    this.HandleOperationEnd();
                }
                else
                {
                    this.HandleOperationStart();
                    WriteOperationResponseHeaders(this.buildBatchWriter, 500);
                    this.buildBatchWriter.WriteLine("{0}: {1}", XmlConstants.HttpContentType, XmlConstants.MimeTextPlain);
                    this.buildBatchWriter.WriteLine("{0}: {1}", XmlConstants.HttpContentID, this.ChangedEntries[this.entryIndex].ChangeOrder);
                    this.buildBatchWriter.WriteLine();
                    this.buildBatchWriter.WriteLine(e.ToString());
                    this.HandleOperationEnd();
                }
 
                this.request = null;
                if (!IsFlagSet(this.options, SaveChangesOptions.ContinueOnError))
                {
                    this.SetCompleted();
 
                    // if it was a media link entry don't even try to do a PUT if the POST didn't succeed
                    this.processingMediaLinkEntry = false;
 
                    // Need to set this to true since we check this even on error cases, but we're here
                    //   because exception was thrown during preparation of the request, so we might not have a chance
                    //   to generate the content for save yet.
                    this.ChangedEntries[this.entryIndex].ContentGeneratedForSave = true;
                }
            }
 
            /// <summary>operation with HttpWebResponse</summary>
            /// <param name="response">response object</param>
            private void HandleOperationResponse(HttpWebResponse response)
            {
                this.HandleOperationStart();
 
                Descriptor entry = this.ChangedEntries[this.entryIndex];
 
                // in the first pass, the http response is packaged into a batch response (which is then processed in second pass).
                // in this first pass, (all added entities and first call of modified media link entities) update their edit location
                // added entities - so entities that have not sent content yet w/ reference links can inline those reference links in their payload
                // media entities - because they can change edit location which is then necessary for second call that includes property content
                if (entry.IsResource)
                {
                    EntityDescriptor entityDescriptor = (EntityDescriptor)entry;
 
                    if (entry.State == EntityStates.Added ||
                         (entry.State == EntityStates.Modified &&
                          this.processingMediaLinkEntry && !this.processingMediaLinkEntryPut))
                    {
                        string location = response.Headers[XmlConstants.HttpResponseLocation];
 
                        if (WebUtil.SuccessStatusCode(response.StatusCode))
                        {
                            if (null != location)
                            {
                                this.Context.AttachLocation(entityDescriptor.Entity, location);
                            }
                            else
                            {
                                throw Error.NotSupported(Strings.Deserialize_NoLocationHeader);
                            }
                        }
                    }
 
                    if (this.processingMediaLinkEntry)
                    {
                        if (!WebUtil.SuccessStatusCode(response.StatusCode))
                        {
                            // If the request failed and it was the MR request we should not try to send the PUT MLE after it
                            //   for one we don't have the location to send it to (if it was POST MR)
 
                            // Just reset the processMLE flag - that means that we will not try to PUT the MLE and instead skip over
                            //   to the next change (if we are to ignore errors that is)
                            this.processingMediaLinkEntry = false;
 
                            if (!this.processingMediaLinkEntryPut)
                            {
                                // If this was the POST MR it means we tried to add the entity. Now its state is Modified but we need
                                //   to revert back to Added so that user can retry by calling SaveChanges again.
                                Debug.Assert(entry.State == EntityStates.Modified, "Entity state should be set to Modified once we've sent the POST MR");
                                entry.State = EntityStates.Added;
                                this.processingMediaLinkEntryPut = false;
                            }
 
                            // And we also need to mark it such that we generated the save content (which we did before the POST request in fact)
                            // to workaround the fact that we use the same entry object to track two requests.
                            entry.ContentGeneratedForSave = true;
                        }
                        else if (response.StatusCode == HttpStatusCode.Created)
                        {
                            // We just finished a POST MR request and the PUT MLE coming immediately after it will
                            // need the new etag value from the server to succeed.
                            entityDescriptor.ETag = response.Headers[XmlConstants.HttpResponseETag];
 
                            // else is not interesting and we intentionally do nothing.
                        }
                    }
                }
 
                WriteOperationResponseHeaders(this.buildBatchWriter, (int)response.StatusCode);
                foreach (string name in response.Headers.AllKeys)
                {
                    if (XmlConstants.HttpContentLength != name)
                    {
                        this.buildBatchWriter.WriteLine("{0}: {1}", name, response.Headers[name]);
                    }
                }
 
                this.buildBatchWriter.WriteLine("{0}: {1}", XmlConstants.HttpContentID, entry.ChangeOrder);
                this.buildBatchWriter.WriteLine();
            }
 
            /// <summary>
            /// copy the response data
            /// </summary>
            /// <param name="response">response object</param>
            private void HandleOperationResponseData(HttpWebResponse response)
            {
                using (Stream stream = response.GetResponseStream())
                {
                    if (null != stream)
                    {
                        this.buildBatchWriter.Flush();
                        if (0 == WebUtil.CopyStream(stream, this.buildBatchWriter.BaseStream, ref this.buildBatchBuffer))
                        {
                            this.HandleOperationResponseNoData();
                        }
                    }
                }
            }
 
            /// <summary>only call when no data was written to added "Content-Length: 0"</summary>
            private void HandleOperationResponseNoData()
            {
                Debug.Assert(null != this.buildBatchWriter, "null buildBatchWriter");
                this.buildBatchWriter.Flush();
#if DEBUG
                MemoryStream memory = this.buildBatchWriter.BaseStream as MemoryStream;
                Debug.Assert(null != memory, "expected MemoryStream");
                Debug.Assert(this.buildBatchWriter.NewLine == NewLine, "mismatch NewLine");
                for (int kk = 0; kk < NewLine.Length; ++kk)
                {
                    Debug.Assert((char)memory.GetBuffer()[memory.Length - (NewLine.Length - kk)] == NewLine[kk], "didn't end with newline");
                }
#endif
                this.buildBatchWriter.BaseStream.Position -= NewLine.Length;
                this.buildBatchWriter.WriteLine("{0}: {1}", XmlConstants.HttpContentLength, 0);
                this.buildBatchWriter.WriteLine();
            }
 
            #endregion
 
            /// <summary>
            /// create the web request for a batch
            /// </summary>
            /// <param name="memory">memory stream for length</param>
            /// <returns>httpweb request</returns>
            private HttpWebRequest CreateBatchRequest(MemoryStream memory)
            {
                Uri requestUri = Util.CreateUri(this.Context.baseUriWithSlash, Util.CreateUri("$batch", UriKind.Relative));
                string contentType = XmlConstants.MimeMultiPartMixed + "; " + XmlConstants.HttpMultipartBoundary + "=" + this.batchBoundary;
                HttpWebRequest httpWebRequest = this.Context.CreateRequest(requestUri, XmlConstants.HttpMethodPost, false, contentType, Util.DataServiceVersion1, false);
                httpWebRequest.ContentLength = memory.Length - memory.Position;
                return httpWebRequest;
            }
 
            /// <summary>generate the batch request of all changes to save</summary>
            /// <param name="replaceOnUpdate">whether we need to update MERGE or PUT method for update.</param>
            /// <returns>buffer containing data for request stream</returns>
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Disposal not required for MemoryStream which is being returned")]
            private MemoryStream GenerateBatchRequest(bool replaceOnUpdate)
            {
                this.changesetBoundary = null;
                if (null != this.Queries)
                {
                }
                else if (0 == this.ChangedEntries.Count)
                {
                    this.DataServiceResponse = new DataServiceResponse(null, (int)WebExceptionStatus.Success, this.Responses, true /*batchResponse*/);
                    this.SetCompleted();
                    return null;
                }
                else
                {
                    this.changesetBoundary = XmlConstants.HttpMultipartBoundaryChangeSet + "_" + Guid.NewGuid().ToString();
                }
 
                MemoryStream memory = new MemoryStream();
                StreamWriter text = new StreamWriter(memory);     // defaults to UTF8 w/o preamble
#if TESTUNIXNEWLINE
                text.NewLine = NewLine;
#endif
 
                if (null != this.Queries)
                {
                    for (int i = 0; i < this.Queries.Length; ++i)
                    {
                        Uri requestUri = Util.CreateUri(this.Context.baseUriWithSlash, this.Queries[i].QueryComponents.Uri);
 
                        Debug.Assert(null != requestUri, "request uri is null");
                        Debug.Assert(requestUri.IsAbsoluteUri, "request uri is not absolute uri");
 
                        text.WriteLine("--{0}", this.batchBoundary);
                        WriteOperationRequestHeaders(text, XmlConstants.HttpMethodGet, requestUri.AbsoluteUri, this.Queries[i].QueryComponents.Version);
                        text.WriteLine();
                    }
                }
                else if (0 < this.ChangedEntries.Count)
                {
                    text.WriteLine("--{0}", this.batchBoundary);
                    text.WriteLine("{0}: {1}; boundary={2}", XmlConstants.HttpContentType, XmlConstants.MimeMultiPartMixed, this.changesetBoundary);
                    text.WriteLine();
 
                    for (int i = 0; i < this.ChangedEntries.Count; ++i)
                    {
                        #region validate changeset boundary starts on newline
#if DEBUG
                        {
                            text.Flush();
                            for (int kk = 0; kk < NewLine.Length; ++kk)
                            {
                                Debug.Assert((char)memory.GetBuffer()[memory.Length - (NewLine.Length - kk)] == NewLine[kk], "boundary didn't start with newline");
                            }
                        }
#endif
                        #endregion
 
                        Descriptor entry = this.ChangedEntries[i];
                        if (entry.ContentGeneratedForSave)
                        {
                            continue;
                        }
 
                        text.WriteLine("--{0}", this.changesetBoundary);
 
                        EntityDescriptor entityDescriptor = entry as EntityDescriptor;
                        if (entry.IsResource)
                        {
                            if (entityDescriptor.State == EntityStates.Added)
                            {
                                // We don't support adding MLE/MR in batch mode
                                ClientType type = ClientType.Create(entityDescriptor.Entity.GetType());
                                if (type.IsMediaLinkEntry || entityDescriptor.IsMediaLinkEntry)
                                {
                                    throw Error.NotSupported(Strings.Context_BatchNotSupportedForMediaLink);
                                }
                            }
                            else if (entityDescriptor.State == EntityStates.Unchanged || entityDescriptor.State == EntityStates.Modified)
                            {
                                // We don't support PUT for the MR in batch mode
                                // It's OK to PUT the MLE alone inside a batch mode though
                                if (entityDescriptor.SaveStream != null)
                                {
                                    throw Error.NotSupported(Strings.Context_BatchNotSupportedForMediaLink);
                                }
                            }
                        }
 
                        PerRequest.ContentStream contentStream = this.CreateChangeData(i, true);
                        MemoryStream stream = null;
                        if (null != contentStream)
                        {
                            Debug.Assert(contentStream.IsKnownMemoryStream, "Batch requests don't support MRs yet");
                            stream = contentStream.Stream as MemoryStream;
                        }
 
                        if (entry.IsResource)
                        {
                            this.Context.CreateRequestBatch(entityDescriptor, text, replaceOnUpdate);
                        }
                        else
                        {
                            this.Context.CreateRequestBatch((LinkDescriptor)entry, text);
                        }
 
                        byte[] buffer = null;
                        int bufferOffset = 0, bufferLength = 0;
                        if (null != stream)
                        {
                            buffer = stream.GetBuffer();
                            bufferOffset = checked((int)stream.Position);
                            bufferLength = checked((int)stream.Length) - bufferOffset;
                        }
 
                        if (0 < bufferLength)
                        {
                            text.WriteLine("{0}: {1}", XmlConstants.HttpContentLength, bufferLength);
                        }
 
                        text.WriteLine(); // NewLine separates header from message
 
                        if (0 < bufferLength)
                        {
                            text.Flush();
                            text.BaseStream.Write(buffer, bufferOffset, bufferLength);
                        }
                    }
 
                    #region validate changeset boundary ended with newline
#if DEBUG
                    {
                        text.Flush();
 
                        for (int kk = 0; kk < NewLine.Length; ++kk)
                        {
                            Debug.Assert((char)memory.GetBuffer()[memory.Length - (NewLine.Length - kk)] == NewLine[kk], "post CreateRequest boundary didn't start with newline");
                        }
                    }
#endif
                    #endregion
 
                    // The boundary delimiter line following the last body part
                    // has two more hyphens after the boundary parameter value.
                    text.WriteLine("--{0}--", this.changesetBoundary);
                }
 
                text.WriteLine("--{0}--", this.batchBoundary);
 
                text.Flush();
                Debug.Assert(Object.ReferenceEquals(text.BaseStream, memory), "should be same");
                Debug.Assert(this.ChangedEntries.All(o => o.ContentGeneratedForSave), "didn't generated content for all entities/links");
 
                #region Validate batch format
#if DEBUG
                int testGetCount = 0;
                int testOpCount = 0;
                int testBeginSetCount = 0;
                int testEndSetCount = 0;
                memory.Position = 0;
                BatchStream testBatch = new BatchStream(memory, this.batchBoundary, HttpProcessUtility.EncodingUtf8NoPreamble, true);
                while (testBatch.MoveNext())
                {
                    switch (testBatch.State)
                    {
                        case BatchStreamState.StartBatch:
                        case BatchStreamState.EndBatch:
                        default:
                            Debug.Assert(false, "shouldn't happen");
                            break;
 
                        case BatchStreamState.Get:
                            testGetCount++;
                            break;
 
                        case BatchStreamState.BeginChangeSet:
                            testBeginSetCount++;
                            break;
                        case BatchStreamState.EndChangeSet:
                            testEndSetCount++;
                            break;
                        case BatchStreamState.Post:
                        case BatchStreamState.Put:
                        case BatchStreamState.Delete:
                        case BatchStreamState.Merge:
                            testOpCount++;
                            break;
                    }
                }
 
                Debug.Assert((null == this.Queries && 1 == testBeginSetCount) || (0 == testBeginSetCount), "more than one BeginChangeSet");
                Debug.Assert(testBeginSetCount == testEndSetCount, "more than one EndChangeSet");
                Debug.Assert((null == this.Queries && testGetCount == 0) || this.Queries.Length == testGetCount, "too many get count");
                // Debug.Assert(this.ChangedEntries.Count == testOpCount, "too many op count");
                Debug.Assert(BatchStreamState.EndBatch == testBatch.State, "should have ended propertly");
#endif
                #endregion
 
                this.changesetBoundary = null;
 
                memory.Position = 0;
                return memory;
            }
 
            #region handle batch response
 
            /// <summary>
            /// process the batch changeset response
            /// </summary>
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "BatchStream will be disposed after user iterates through final response enumerator")]
            private void HandleBatchResponse()
            {
                string boundary = this.batchBoundary;
                Encoding encoding = Encoding.UTF8;
                Dictionary<string, string> headers = null;
                Exception exception = null;
 
                try
                {
                    if (IsFlagSet(this.options, SaveChangesOptions.Batch))
                    {
                        if ((null == this.batchResponse) || (HttpStatusCode.NoContent == this.batchResponse.StatusCode))
                        {   // we always expect a response to our batch POST request
                            throw Error.InvalidOperation(Strings.Batch_ExpectedResponse(1));
                        }
 
                        headers = WebUtil.WrapResponseHeaders(this.batchResponse);
                        HandleResponse(
                            this.batchResponse.StatusCode,                                      // statusCode
                            this.batchResponse.Headers[XmlConstants.HttpDataServiceVersion],    // responseVersion
                            delegate() { return this.httpWebResponseStream; },                  // getResponseStream
                            true);                                                              // throwOnFailure
 
                        if (!BatchStream.GetBoundaryAndEncodingFromMultipartMixedContentType(this.batchResponse.ContentType, out boundary, out encoding))
                        {
                            string mime;
                            Exception inner = null;
                            HttpProcessUtility.ReadContentType(this.batchResponse.ContentType, out mime, out encoding);
                            if (String.Equals(XmlConstants.MimeTextPlain, mime))
                            {
                                inner = GetResponseText(this.batchResponse.GetResponseStream, this.batchResponse.StatusCode);
                            }
 
                            throw Error.InvalidOperation(Strings.Batch_ExpectedContentType(this.batchResponse.ContentType), inner);
                        }
 
                        if (null == this.httpWebResponseStream)
                        {
                            Error.ThrowBatchExpectedResponse(InternalError.NullResponseStream);
                        }
 
                        this.DataServiceResponse = new DataServiceResponse(headers, (int)this.batchResponse.StatusCode, this.Responses, true /*batchResponse*/);
                    }
 
                    bool close = true;
                    BatchStream batchStream = null;
                    try
                    {
                        // BatchStream takes ownership of the httpWebResponseStream (which may be a network stream)
                        // BatchStream will be disposed after user iterates through enumerator
                        batchStream = this.responseBatchStream ?? new BatchStream(this.httpWebResponseStream, boundary, encoding, false);
                        this.httpWebResponseStream = null;
                        this.responseBatchStream = null;
 
                        IEnumerable<OperationResponse> responses = this.HandleBatchResponse(batchStream);
                        if (IsFlagSet(this.options, SaveChangesOptions.Batch) && (null != this.Queries))
                        {
                            // ExecuteBatch, EndExecuteBatch
                            close = false;
                            this.responseBatchStream = batchStream;
 
                            this.DataServiceResponse = new DataServiceResponse(
                                (Dictionary<string, string>)this.DataServiceResponse.BatchHeaders,
                                this.DataServiceResponse.BatchStatusCode,
                                responses,
                                true /*batchResponse*/);
                        }
                        else
                        {   // SaveChanges, EndSaveChanges
                            // enumerate the entire response
                            foreach (ChangeOperationResponse response in responses)
                            {
                                if (exception == null && response.Error != null)
                                {
                                    exception = response.Error;
                                }
                            }
                        }
                    }
                    finally
                    {
                        if (close && (null != batchStream))
                        {
                            batchStream.Close();
                        }
                    }
                }
                catch (InvalidOperationException ex)
                {
                    exception = ex;
                }
 
                if (exception != null)
                {
                    if (this.DataServiceResponse == null)
                    {
                        int statusCode = this.batchResponse == null ? (int)HttpStatusCode.InternalServerError : (int)this.batchResponse.StatusCode;
                        this.DataServiceResponse = new DataServiceResponse(headers, statusCode, null, IsFlagSet(this.options, SaveChangesOptions.Batch));
                    }
 
                    throw new DataServiceRequestException(Strings.DataServiceException_GeneralError, exception, this.DataServiceResponse);
                }
            }
 
            /// <summary>
            /// process the batch changeset response
            /// </summary>
            /// <param name="batch">batch stream</param>
            /// <returns>enumerable of QueryResponse or null</returns>
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506", Justification = "Central method of the API, likely to have many cross-references")]
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031", Justification = "Cache exception so user can examine it later")]
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "MaterializeAtom is responsible for closing XmlReader which will close the stream")]
            private IEnumerable<OperationResponse> HandleBatchResponse(BatchStream batch)
            {
                if (!batch.CanRead)
                {
                    yield break;
                }
 
                string contentType;
                string location;
                string etag;
 
                Uri editLink = null;
 
                HttpStatusCode status;
                int changesetIndex = 0;
                int queryCount = 0;
                int operationCount = 0;
                this.entryIndex = 0;
                while (batch.MoveNext())
                {
                    var contentHeaders = batch.ContentHeaders; // get the headers before materialize clears them
 
                    Descriptor entry;
                    switch (batch.State)
                    {
                        #region BeginChangeSet
                        case BatchStreamState.BeginChangeSet:
                            if ((IsFlagSet(this.options, SaveChangesOptions.Batch) && (0 != changesetIndex)) ||
                                (0 != operationCount))
                            {   // for now, we only send a single batch, single changeset
                                Error.ThrowBatchUnexpectedContent(InternalError.UnexpectedBeginChangeSet);
                            }
 
                            break;
                        #endregion
 
                        #region EndChangeSet
                        case BatchStreamState.EndChangeSet:
                            // move forward to next expected changelist
                            changesetIndex++;
                            operationCount = 0;
                            break;
                        #endregion
 
                        #region GetResponse
                        case BatchStreamState.GetResponse:
                            Debug.Assert(0 == operationCount, "missing an EndChangeSet 2");
 
                            contentHeaders.TryGetValue(XmlConstants.HttpContentType, out contentType);
                            status = (HttpStatusCode)(-1);
 
                            Exception ex = null;
                            QueryOperationResponse qresponse = null;
                            try
                            {
                                status = batch.GetStatusCode();
 
                                ex = HandleResponse(status, batch.GetResponseVersion(), batch.GetContentStream, false);
                                if (null == ex)
                                {
                                    DataServiceRequest query = this.Queries[queryCount];
                                    MaterializeAtom materializer = DataServiceRequest.Materialize(this.Context, query.QueryComponents, null, contentType, batch.GetContentStream());
                                    qresponse = QueryOperationResponse.GetInstance(query.ElementType, contentHeaders, query, materializer);
                                }
                            }
                            catch (ArgumentException e)
                            {
                                ex = e;
                            }
                            catch (FormatException e)
                            {
                                ex = e;
                            }
                            catch (InvalidOperationException e)
                            {
                                ex = e;
                            }
 
                            if (null == qresponse)
                            {
                                if (null != this.Queries)
                                {
                                    // this is the normal ExecuteBatch response
                                    DataServiceRequest query = this.Queries[queryCount];
 
                                    if (this.Context.ignoreResourceNotFoundException && status == HttpStatusCode.NotFound)
                                    {
                                        qresponse = QueryOperationResponse.GetInstance(query.ElementType, contentHeaders, query, MaterializeAtom.EmptyResults);
                                    }
                                    else
                                    {
                                        qresponse = QueryOperationResponse.GetInstance(query.ElementType, contentHeaders, query, MaterializeAtom.EmptyResults);
                                        qresponse.Error = ex;
                                    }
                                }
                                else
                                {
                                    // This is top-level failure SaveChanges(SaveChangesOptions.Batch) response
                                    // example: server doesn't support batching or number of batch objects exceeded an allowed limit.
                                    // ex could be null if the server responded to SaveChanges with an unexpected success with
                                    // response of batched GETS that did not correspond the original POST/MERGE/PUT/DELETE requests.
                                    // we expect non-null since server should have failed with a non-success code
                                    // and HandleResponse(status, ...) should generate the exception object
                                    throw ex;
                                }
                            }
 
                            qresponse.StatusCode = (int)status;
                            queryCount++;
                            yield return qresponse;
                            break;
                        #endregion
 
                        #region ChangeResponse
                        case BatchStreamState.ChangeResponse:
 
                            HttpStatusCode statusCode = batch.GetStatusCode();
                            Exception error = HandleResponse(statusCode, batch.GetResponseVersion(), batch.GetContentStream, false);
                            int index = this.ValidateContentID(contentHeaders);
 
                            try
                            {
                                entry = this.ChangedEntries[index];
                                operationCount += this.Context.SaveResultProcessed(entry);
 
                                if (null != error)
                                {
                                    throw error;
                                }
 
                                StreamStates streamState = StreamStates.NoStream;
                                if (entry.IsResource)
                                {
                                    EntityDescriptor descriptor = (EntityDescriptor)entry;
                                    streamState = descriptor.StreamState;
#if DEBUG
                                    if (descriptor.StreamState == StreamStates.Added)
                                    {
                                        Debug.Assert(
                                            statusCode == HttpStatusCode.Created && entry.State == EntityStates.Modified && descriptor.IsMediaLinkEntry,
                                            "statusCode == HttpStatusCode.Created && entry.State == EntityStates.Modified && descriptor.IsMediaLinkEntry -- Processing Post MR");
                                    }
                                    else if (descriptor.StreamState == StreamStates.Modified)
                                    {
                                        Debug.Assert(
                                            statusCode == HttpStatusCode.NoContent && descriptor.IsMediaLinkEntry,
                                            "statusCode == HttpStatusCode.NoContent && descriptor.IsMediaLinkEntry -- Processing Put MR");
                                    }
#endif
                                }
 
                                if (streamState == StreamStates.Added || entry.State == EntityStates.Added)
                                {
                                    #region Post
                                    if (entry.IsResource)
                                    {
                                        string mime = null;
                                        Encoding postEncoding = null;
                                        contentHeaders.TryGetValue(XmlConstants.HttpContentType, out contentType);
                                        contentHeaders.TryGetValue(XmlConstants.HttpResponseLocation, out location);
                                        contentHeaders.TryGetValue(XmlConstants.HttpResponseETag, out etag);
                                        EntityDescriptor entityDescriptor = (EntityDescriptor)entry;
 
                                        // If the location header is specified, we need to set the edit link
                                        // for the entity descriptor to that value.
                                        if (location != null)
                                        {
                                            editLink = Util.CreateUri(location, UriKind.Absolute);
                                        }
                                        else
                                        {
                                            throw Error.NotSupported(Strings.Deserialize_NoLocationHeader);
                                        }
 
                                        Stream stream = batch.GetContentStream();
                                        if (null != stream)
                                        {
                                            HttpProcessUtility.ReadContentType(contentType, out mime, out postEncoding);
                                            if (!String.Equals(XmlConstants.MimeApplicationAtom, mime, StringComparison.OrdinalIgnoreCase))
                                            {
                                                throw Error.InvalidOperation(Strings.Deserialize_UnknownMimeTypeSpecified(mime));
                                            }
 
                                            XmlReader reader = XmlUtil.CreateXmlReader(stream, postEncoding);
                                            QueryComponents qc = new QueryComponents(null, Util.DataServiceVersionEmpty, entityDescriptor.Entity.GetType(), null, null);
                                            EntityDescriptor descriptor = (EntityDescriptor)entry;
                                            MergeOption mergeOption = MergeOption.OverwriteChanges;
 
                                            // If we are processing a POST MR, we want to materialize the payload to get the metadata for the stream.
                                            // However we must not modify the MLE properties with the server initialized properties.  The next request
                                            // will be a Put MLE operation and we will set the server properties with values from the client entity.
                                            if (descriptor.StreamState == StreamStates.Added)
                                            {
                                                mergeOption = MergeOption.PreserveChanges;
                                                Debug.Assert(descriptor.State == EntityStates.Modified, "The MLE state must be Modified.");
                                            }
 
                                            try
                                            {
                                                using (MaterializeAtom atom = new MaterializeAtom(this.Context, reader, qc, null, mergeOption))
                                                {
                                                    this.Context.HandleResponsePost(entityDescriptor, atom, editLink, etag);
                                                }
                                            }
                                            finally
                                            {
                                                if (descriptor.StreamState == StreamStates.Added)
                                                {
                                                    // The materializer will always set the entity state to Unchanged.  We just processed Post MR, we
                                                    // need to restore the entity state to Modified to process the Put MLE.
                                                    Debug.Assert(descriptor.State == EntityStates.Unchanged, "The materializer should always set the entity state to Unchanged.");
                                                    descriptor.State = EntityStates.Modified;
 
                                                    // Need to clear the stream state so the next iteration we will always process the Put MLE operation.
                                                    descriptor.StreamState = StreamStates.NoStream;
                                                }
                                            }
                                        }
                                        else
                                        {
                                            this.Context.HandleResponsePost(entityDescriptor, null /*materializer*/, editLink, etag);
                                        }
                                    }
                                    else
                                    {
                                        HandleResponsePost((LinkDescriptor)entry);
                                    }
                                    #endregion
                                }
                                else if (streamState == StreamStates.Modified || entry.State == EntityStates.Modified)
                                {
                                    #region Put, Merge
                                    contentHeaders.TryGetValue(XmlConstants.HttpResponseETag, out etag);
                                    HandleResponsePut(entry, etag);
                                    #endregion
                                }
                                else if (entry.State == EntityStates.Deleted)
                                {
                                    #region Delete
                                    this.Context.HandleResponseDelete(entry);
                                    #endregion
                                }
 
                                // else condition is not interesting here and we intentionally do nothing.
                            }
                            catch (Exception e)
                            {
                                this.ChangedEntries[index].SaveError = e;
                                error = e;
                            }
 
                            ChangeOperationResponse changeOperationResponse = 
                                new ChangeOperationResponse(contentHeaders, this.ChangedEntries[index]);
                            changeOperationResponse.StatusCode = (int)statusCode;
                            if (error != null)
                            {
                                changeOperationResponse.Error = error;
                            }
 
                            this.Responses.Add(changeOperationResponse);
                            operationCount++;
                            this.entryIndex++;
                            yield return changeOperationResponse;
                            break;
                        #endregion
 
                        default:
                            Error.ThrowBatchExpectedResponse(InternalError.UnexpectedBatchState);
                            break;
                    }
                }
 
                Debug.Assert(batch.State == BatchStreamState.EndBatch, "unexpected batch state");
 
                // Check for a changeset without response (first line) or GET request without response (second line).
                // either all saved entries must be processed or it was a batch and one of the entries has the error
                if ((null == this.Queries && 
                    (0 == changesetIndex || 
                     0 < queryCount || 
                     this.ChangedEntries.Any(o => o.ContentGeneratedForSave && 0 == o.SaveResultWasProcessed) &&
                     (!IsFlagSet(this.options, SaveChangesOptions.Batch) || null == this.ChangedEntries.FirstOrDefault(o => null != o.SaveError)))) ||
                    (null != this.Queries && queryCount != this.Queries.Length))
                {
                    throw Error.InvalidOperation(Strings.Batch_IncompleteResponseCount);
                }
 
                batch.Dispose();
            }
 
            /// <summary>
            /// validate the content-id
            /// </summary>
            /// <param name="contentHeaders">headers</param>
            /// <returns>return the correct ChangedEntries index</returns>
            private int ValidateContentID(Dictionary<string, string> contentHeaders)
            {
                int contentID = 0;
                string contentValueID;
 
                if (!contentHeaders.TryGetValue(XmlConstants.HttpContentID, out contentValueID) ||
                    !Int32.TryParse(contentValueID, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out contentID))
                {
                    Error.ThrowBatchUnexpectedContent(InternalError.ChangeResponseMissingContentID);
                }
 
                for (int i = 0; i < this.ChangedEntries.Count; ++i)
                {
                    if (this.ChangedEntries[i].ChangeOrder == contentID)
                    {
                        return i;
                    }
                }
 
                Error.ThrowBatchUnexpectedContent(InternalError.ChangeResponseUnknownContentID);
                return -1;
            }
 
            #endregion Batch
 
            #region callback handlers
 
            /// <summary>handle request.BeginGetRequestStream with request.EndGetRquestStream and then write out request stream</summary>
            /// <param name="asyncResult">async result</param>
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")]
            private void AsyncEndGetRequestStream(IAsyncResult asyncResult)
            {
                Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted");
                PerRequest pereq = asyncResult == null ? null : asyncResult.AsyncState as PerRequest;
                try
                {
                    CompleteCheck(pereq, InternalError.InvalidEndGetRequestCompleted);
                    pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously; // BeginGetRequestStream
 
                    EqualRefCheck(this.request, pereq, InternalError.InvalidEndGetRequestStream);
                    HttpWebRequest httpWebRequest = Util.NullCheck(pereq.Request, InternalError.InvalidEndGetRequestStreamRequest);
 
                    Stream stream = Util.NullCheck(httpWebRequest.EndGetRequestStream(asyncResult), InternalError.InvalidEndGetRequestStreamStream);
                    pereq.RequestStream = stream;
 
                    PerRequest.ContentStream contentStream = pereq.RequestContentStream;
                    Util.NullCheck(contentStream, InternalError.InvalidEndGetRequestStreamContent);
                    Util.NullCheck(contentStream.Stream, InternalError.InvalidEndGetRequestStreamContent);
                    if (contentStream.IsKnownMemoryStream)
                    {
                        MemoryStream memoryStream = contentStream.Stream as MemoryStream;
                        byte[] buffer = memoryStream.GetBuffer();
                        int bufferOffset = checked((int)memoryStream.Position);
                        int bufferLength = checked((int)memoryStream.Length) - bufferOffset;
                        if ((null == buffer) || (0 == bufferLength))
                        {
                            Error.ThrowInternalError(InternalError.InvalidEndGetRequestStreamContentLength);
                        }
                    }
 
                    // Start the Read on the request content stream.
                    // Note that we don't deal with synchronous results here.
                    // If the read finishes synchronously the AsyncRequestContentEndRead will be called from inside the BeginRead
                    //   call below. In there we will call BeginWrite. If that completes synchronously we will loop
                    //   and call BeginRead again. If that completes synchronously as well we will call BeginWrite and so on.
                    //   AsyncEndWrite will return immedially if it finished synchronously (otherwise it calls BeginRead).
                    // So in the worst case we will have a stack like this:
                    //   AsyncEndGetRequestStream
                    //     AsyncRequestContentEndRead
                    //       AsyncRequestContentEndRead or AsyncEndWrite
 
                    // We just need to differentiate between the first AsyncRequestContentEndRead and the others (the first one
                    //   must not return even if it completed synchronously, otherwise we would have to do the loop here as well).
                    //   We'll use the RequestContentBufferValidLength as the notification. It will start with -1 which means
                    //   we didn't read anything at all and thus it's the first read ending.
                    pereq.RequestContentBufferValidLength = -1;
 
                    Util.DebugInjectFault("SaveAsyncResult::AsyncEndGetRequestStream_BeforeBeginRead");
                    asyncResult = BaseAsyncResult.InvokeAsync(contentStream.Stream.BeginRead, pereq.RequestContentBuffer, 0, pereq.RequestContentBuffer.Length, this.AsyncRequestContentEndRead, pereq);
                    pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
                }
                catch (Exception e)
                {
                    if (this.HandleFailure(pereq, e))
                    {
                        throw;
                    }
                }
                finally
                {
                    this.HandleCompleted(pereq);
                }
            }
 
            /// <summary>
            /// Callback for Stream.BeginRead on the request content input stream. Calls request content output stream BeginWrite
            /// and in case of synchronous also the next BeginRead.
            /// </summary>
            /// <param name="asyncResult">The asynchronous result associated with the completed operation.</param>
            private void AsyncRequestContentEndRead(IAsyncResult asyncResult)
            {
                Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted");
                PerRequest pereq = asyncResult == null ? null : asyncResult.AsyncState as PerRequest;
                try
                {
                    CompleteCheck(pereq, InternalError.InvalidEndReadCompleted);
                    pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously; // BeginRead
 
                    EqualRefCheck(this.request, pereq, InternalError.InvalidEndRead);
                    PerRequest.ContentStream contentStream = pereq.RequestContentStream;
                    Util.NullCheck(contentStream, InternalError.InvalidEndReadStream);
                    Util.NullCheck(contentStream.Stream, InternalError.InvalidEndReadStream);
                    Stream stream = Util.NullCheck(pereq.RequestStream, InternalError.InvalidEndReadStream);
 
                    Util.DebugInjectFault("SaveAsyncResult::AsyncRequestContentEndRead_BeforeEndRead");
                    int count = contentStream.Stream.EndRead(asyncResult);
                    if (0 < count)
                    {
                        bool firstEndRead = (pereq.RequestContentBufferValidLength == -1);
                        pereq.RequestContentBufferValidLength = count;
 
                        // If we completed synchronously then just return. Our caller will take care of processing the results.
                        // First EndRead must not return even if completed synchronously.
                        if (!asyncResult.CompletedSynchronously || firstEndRead)
                        {
                            do
                            {
                                // Write the data we've read to the request stream
                                Util.DebugInjectFault("SaveAsyncResult::AsyncRequestContentEndRead_BeforeBeginWrite");
                                asyncResult = BaseAsyncResult.InvokeAsync(stream.BeginWrite, pereq.RequestContentBuffer, 0, pereq.RequestContentBufferValidLength, this.AsyncEndWrite, pereq);
                                pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
 
                                // If the write above completed synchronously
                                //   immediately start the next read so that we loop instead of recursion.
                                // If it completed asynchronously we just return as we will deal with the results in the EndWrite
                                if (asyncResult.CompletedSynchronously && !pereq.RequestCompleted && !this.IsCompletedInternally)
                                {
                                    Util.DebugInjectFault("SaveAsyncResult::AsyncRequestContentEndRead_BeforeBeginRead");
                                    asyncResult = BaseAsyncResult.InvokeAsync(contentStream.Stream.BeginRead, pereq.RequestContentBuffer, 0, pereq.RequestContentBuffer.Length, this.AsyncRequestContentEndRead, pereq);
                                    pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
                                }
 
                                // If the read completed synchronously as well we loop to write the data to the request stream without recursion.
                                // Only loop if there's actually some data to be processed. If there's no more data then return.
                                // The request will continue inside the inner call to AsyncRequestContentEndRead (which will get 0 data
                                //   and will end up in the else branch of the big if).
                            }
                            while (asyncResult.CompletedSynchronously && !pereq.RequestCompleted && !this.IsCompletedInternally &&
                                pereq.RequestContentBufferValidLength > 0);
                        }
                    }
                    else
                    {
                        // Done reading data (and writing them)
                        pereq.RequestContentBufferValidLength = 0;
                        pereq.RequestStream = null;
                        stream.Close();
 
                        HttpWebRequest httpWebRequest = Util.NullCheck(pereq.Request, InternalError.InvalidEndWriteRequest);
                        asyncResult = BaseAsyncResult.InvokeAsync(httpWebRequest.BeginGetResponse, this.AsyncEndGetResponse, pereq);
                        pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously; // BeginGetResponse
                    }
                }
                catch (Exception e)
                {
                    if (this.HandleFailure(pereq, e))
                    {
                        throw;
                    }
                }
                finally
                {
                    this.HandleCompleted(pereq);
                }
            }
                        
            /// <summary>handle reqestStream.BeginWrite with requestStream.EndWrite then BeginGetResponse</summary>
            /// <param name="asyncResult">async result</param>
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")]
            private void AsyncEndWrite(IAsyncResult asyncResult)
            {
                Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted");
                PerRequest pereq = asyncResult == null ? null : asyncResult.AsyncState as PerRequest;
                try
                {
                    CompleteCheck(pereq, InternalError.InvalidEndWriteCompleted);
                    pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously; // BeginWrite
 
                    EqualRefCheck(this.request, pereq, InternalError.InvalidEndWrite);
 
                    PerRequest.ContentStream contentStream = pereq.RequestContentStream;
                    Util.NullCheck(contentStream, InternalError.InvalidEndWriteStream);
                    Util.NullCheck(contentStream.Stream, InternalError.InvalidEndWriteStream);
                    Stream stream = Util.NullCheck(pereq.RequestStream, InternalError.InvalidEndWriteStream);
                    Util.DebugInjectFault("SaveAsyncResult::AsyncEndWrite_BeforeEndWrite");
                    stream.EndWrite(asyncResult);
 
                    // If the write completed synchronously just return. The caller (AsyncRequestContentEndRead)
                    //   will loop and initiate the next read.
                    // If the write completed asynchronously we need to start the next read here. Note that we start the read
                    //   regardless if the stream has other data to offer or not. This is to avoid dealing with the end
                    //   of the read/write loop in several places. We simply issue a read which (if the stream is at the end)
                    //   will return 0 bytes and we will deal with that in the AsyncRequestContentEndRead method.
                    if (!asyncResult.CompletedSynchronously)
                    {
                        Util.DebugInjectFault("SaveAsyncResult::AsyncEndWrite_BeforeBeginRead");
                        asyncResult = BaseAsyncResult.InvokeAsync(contentStream.Stream.BeginRead, pereq.RequestContentBuffer, 0, pereq.RequestContentBuffer.Length, this.AsyncRequestContentEndRead, pereq);
                        pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously;
                    }
                }
                catch (Exception e)
                {
                    if (this.HandleFailure(pereq, e))
                    {
                        throw;
                    }
                }
                finally
                {
                    this.HandleCompleted(pereq);
                }
            }
 
            /// <summary>handle request.BeginGetResponse with request.EndGetResponse and then copy response stream</summary>
            /// <param name="asyncResult">async result</param>
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")]
            private void AsyncEndGetResponse(IAsyncResult asyncResult)
            {
                Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted");
                PerRequest pereq = asyncResult == null ? null : asyncResult.AsyncState as PerRequest;
                try
                {
                    CompleteCheck(pereq, InternalError.InvalidEndGetResponseCompleted);
                    pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously; // BeginGetResponse
 
                    EqualRefCheck(this.request, pereq, InternalError.InvalidEndGetResponse);
                    HttpWebRequest httpWebRequest = Util.NullCheck(pereq.Request, InternalError.InvalidEndGetResponseRequest);
 
                    // the httpWebResponse is kept for batching, discarded by non-batch
                    HttpWebResponse response = null;
                    try
                    {
                        Util.DebugInjectFault("SaveAsyncResult::AsyncEndGetResponse::BeforeEndGetResponse");
                        response = (HttpWebResponse)httpWebRequest.EndGetResponse(asyncResult);
                    }
                    catch (WebException e)
                    {
                        response = (HttpWebResponse)e.Response;
                        if (null == response)
                        {
                            throw;
                        }
                    }
 
                    pereq.HttpWebResponse = Util.NullCheck(response, InternalError.InvalidEndGetResponseResponse);
 
                    if (!IsFlagSet(this.options, SaveChangesOptions.Batch))
                    {
                        this.HandleOperationResponse(response);
                    }
 
                    this.copiedContentLength = 0;
                    Util.DebugInjectFault("SaveAsyncResult::AsyncEndGetResponse_BeforeGetStream");
                    Stream stream = response.GetResponseStream();
                    pereq.ResponseStream = stream;
                    if ((null != stream) && stream.CanRead)
                    {
                        if (null != this.buildBatchWriter)
                        {
                            this.buildBatchWriter.Flush();
                        }
 
                        if (null == this.buildBatchBuffer)
                        {
                            this.buildBatchBuffer = new byte[8000];
                        }
 
                        do
                        {
                            Util.DebugInjectFault("SaveAsyncResult::AsyncEndGetResponse_BeforeBeginRead");
                            asyncResult = BaseAsyncResult.InvokeAsync(stream.BeginRead, this.buildBatchBuffer, 0, this.buildBatchBuffer.Length, this.AsyncEndRead, pereq);
                            pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously; // BeginRead
                        }
                        while (asyncResult.CompletedSynchronously && !pereq.RequestCompleted && !this.IsCompletedInternally && stream.CanRead);
                    }
                    else
                    {
                        pereq.SetComplete();
 
                        // BeginGetResponse could fail and callback still invoked
                        if (!this.IsCompletedInternally)
                        {
                            this.SaveNextChange(pereq);
                        }
                    }
                }
                catch (Exception e)
                {
                    if (this.HandleFailure(pereq, e))
                    {
                        throw;
                    }
                }
                finally
                {
                    this.HandleCompleted(pereq);
                }
            }
 
            /// <summary>handle responseStream.BeginRead with responseStream.EndRead</summary>
            /// <param name="asyncResult">async result</param>
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "required for this feature")]
            private void AsyncEndRead(IAsyncResult asyncResult)
            {
                Debug.Assert(asyncResult != null && asyncResult.IsCompleted, "asyncResult.IsCompleted");
                PerRequest pereq = asyncResult.AsyncState as PerRequest;
                int count = 0;
                try
                {
                    CompleteCheck(pereq, InternalError.InvalidEndReadCompleted);
                    pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously; // BeginRead
 
                    EqualRefCheck(this.request, pereq, InternalError.InvalidEndRead);
                    Stream stream = Util.NullCheck(pereq.ResponseStream, InternalError.InvalidEndReadStream);
 
                    Util.DebugInjectFault("SaveAsyncResult::AsyncEndRead_BeforeEndRead");
                    count = stream.EndRead(asyncResult);
                    if (0 < count)
                    {
                        Stream outputResponse = Util.NullCheck(this.httpWebResponseStream, InternalError.InvalidEndReadCopy);
                        outputResponse.Write(this.buildBatchBuffer, 0, count);
                        this.copiedContentLength += count;
 
                        if (!asyncResult.CompletedSynchronously && stream.CanRead)
                        {   
                            // if CompletedSynchronously then caller will call and we reduce risk of stack overflow
                            do
                            {
                                asyncResult = BaseAsyncResult.InvokeAsync(stream.BeginRead, this.buildBatchBuffer, 0, this.buildBatchBuffer.Length, this.AsyncEndRead, pereq);
                                pereq.RequestCompletedSynchronously &= asyncResult.CompletedSynchronously; // BeginRead
                            }
                            while (asyncResult.CompletedSynchronously && !pereq.RequestCompleted && !this.IsCompletedInternally && stream.CanRead);
                        }
                    }
                    else
                    {
                        pereq.SetComplete();
 
                        // BeginRead could fail and callback still invoked
                        if (!this.IsCompletedInternally)
                        {
                            this.SaveNextChange(pereq);
                        }
                    }
                }
                catch (Exception e)
                {
                    if (this.HandleFailure(pereq, e))
                    {
                        throw;
                    }
                }
                finally
                {
                    this.HandleCompleted(pereq);
                }
            }
 
            /// <summary>continue with the next change</summary>
            /// <param name="pereq">the completed per request object</param>
            private void SaveNextChange(PerRequest pereq)
            {
                Debug.Assert(this.executeAsync, "should be async");
                if (!pereq.RequestCompleted)
                {
                    Error.ThrowInternalError(InternalError.SaveNextChangeIncomplete);
                }
 
                EqualRefCheck(this.request, pereq, InternalError.InvalidSaveNextChange);
 
                if (IsFlagSet(this.options, SaveChangesOptions.Batch))
                {
                    this.httpWebResponseStream.Position = 0;
                    this.request = null;
                    this.SetCompleted();
                }
                else
                {
                    if (0 == this.copiedContentLength)
                    {
                        this.HandleOperationResponseNoData();
                    }
 
                    this.HandleOperationEnd();
 
                    if (!this.processingMediaLinkEntry)
                    {
                        this.changesCompleted++;
                    }
 
                    pereq.Dispose();
                    this.request = null;
                    if (!pereq.RequestCompletedSynchronously)
                    {   // you can't chain synchronously completed responses without risking StackOverflow, caller will loop to next
                        if (!this.IsCompletedInternally)
                        {
                            this.BeginNextChange(IsFlagSet(this.options, SaveChangesOptions.ReplaceOnUpdate));
                        }
                    }
                }
            }
            #endregion
 
            /// <summary>wrap the full request</summary>
            private sealed class PerRequest
            {
                /// <summary>
                /// did the sequence (BeginGetRequest, EndGetRequest, ... complete. 0 = In Progress, 1 = Completed, 2 = Aborted
                /// </summary>
                private int requestStatus;
 
                /// <summary>
                /// Buffer used when pumping data from the write stream to the request content stream
                /// </summary>
                private byte[] requestContentBuffer;
 
                /// <summary>ctor</summary>
                internal PerRequest()
                {
                    this.RequestCompletedSynchronously = true;
                }
 
                /// <summary>active web request</summary>
                internal HttpWebRequest Request
                {
                    get;
                    set;
                }
 
                /// <summary>active web request stream</summary>
                internal Stream RequestStream
                {
                    get;
                    set;
                }
 
                /// <summary>content to write to request stream</summary>
                internal ContentStream RequestContentStream
                {
                    get;
                    set;
                }
 
                /// <summary>web response</summary>
                internal HttpWebResponse HttpWebResponse
                {
                    get;
                    set;
                }
 
                /// <summary>async web response stream</summary>
                internal Stream ResponseStream
                {
                    get;
                    set;
                }
 
                /// <summary>did the request complete all of its steps synchronously?</summary>
                internal bool RequestCompletedSynchronously
                {
                    get;
                    set;
                }
 
                /// <summary>
                /// Short cut for testing if request has finished (either completed or aborted)
                /// </summary>
                internal bool RequestCompleted
                {
                    get { return this.requestStatus != 0; }
                }
 
                /// <summary>
                /// Short cut for testing request status is 2 (Aborted)
                /// </summary>
                internal bool RequestAborted
                {
                    get { return this.requestStatus == 2; }
                }
 
                /// <summary>
                /// Buffer used when pumping data from the write stream to the request content stream
                /// </summary>
                internal byte[] RequestContentBuffer
                {
                    get
                    {
                        if (this.requestContentBuffer == null)
                        {
                            this.requestContentBuffer = new byte[64 * 1024];
                        }
 
                        return this.requestContentBuffer;
                    }
                }
 
                /// <summary>
                /// The length of the valid content in the RequestContentBuffer
                /// Once the data is read from the request content stream into the RequestContent buffer
                /// this length is set to the amount of data read.
                /// When the data is written into the request stream it is set back to 0.
                /// </summary>
                internal int RequestContentBufferValidLength
                {
                    get;
                    set;
                }
 
                /// <summary>
                /// Change the request status to completed
                /// </summary>
                internal void SetComplete()
                {
                    System.Threading.Interlocked.CompareExchange(ref this.requestStatus, 1, 0);
                }
 
                /// <summary>
                /// Change the request status to aborted
                /// </summary>
                internal void SetAborted()
                {
                    System.Threading.Interlocked.Exchange(ref this.requestStatus, 2);
                }
                
                /// <summary>
                /// dispose of the request object
                /// </summary>
                internal void Dispose()
                {
                    Stream stream = null;
 
                    if (null != (stream = this.ResponseStream))
                    {
                        this.ResponseStream = null;
                        stream.Dispose();
                    }
 
                    if (null != this.RequestContentStream)
                    {
                        if (this.RequestContentStream.Stream != null && this.RequestContentStream.IsKnownMemoryStream)
                        {
                            this.RequestContentStream.Stream.Dispose();
                        }
 
                        // We must not dispose the stream which came from outside
                        //   the disposing/closing of that stream depends on parameter passed to us and is dealt with
                        //   at the end of SaveChanges process.
                        this.RequestContentStream = null;
                    }
                    
                    if (null != (stream = this.RequestStream))
                    {
                        this.RequestStream = null;
                        try
                        {
                            Util.DebugInjectFault("PerRequest::Dispose_BeforeRequestStreamDisposed");
                            stream.Dispose();
                        }
                        catch (WebException)
                        {
                            // if the request is aborted, then the connect stream 
                            // cannot be disposed - since not all bytes are written to it yet
                            // In this case, we eat the exception
                            // Otherwise, keep throwing
                            if (!this.RequestAborted)
                            {
                                throw;
                            }
 
                            // Call Injector to report the exception so test code can verify it is thrown
                            Util.DebugInjectFault("PerRequest::Dispose_WebExceptionThrown");
                        }
                    }
 
                    HttpWebResponse response = this.HttpWebResponse;
                    if (null != response)
                    {
                        response.Close();
                    }
 
                    this.Request = null;
                    this.SetComplete();
                }
 
                /// <summary>
                /// Helper class to wrap the stream with the content of the request.
                /// We need to remember if the stream came from us (IsKnownMemoryStream is true)
                /// or if it came from outside. For backward compatibility we set the Content-Length for our streams
                /// since they are always MemoryStream and thus know their length.
                /// For outside streams (content of the MR requests) we don't set Content-Length since the stream
                /// might not be able to answer to the Length call.
                /// </summary>
                internal class ContentStream
                {
                    /// <summary>
                    /// The stream with the content of the request
                    /// </summary>
                    private readonly Stream stream;
 
                    /// <summary>
                    /// Set to true if the stream is a MemoryStream and we produced it (so it does have the buffer accesible)
                    /// </summary>
                    private readonly bool isKnownMemoryStream;
 
                    /// <summary>
                    /// Constructor
                    /// </summary>
                    /// <param name="stream">The stream with the request content</param>
                    /// <param name="isKnownMemoryStream">The stream was create by us and it's a MemoryStream</param>
                    public ContentStream(Stream stream, bool isKnownMemoryStream)
                    {
                        this.stream = stream;
                        this.isKnownMemoryStream = isKnownMemoryStream;
                    }
 
                    /// <summary>
                    /// The stream with the content of the request
                    /// </summary>
                    public Stream Stream
                    {
                        get { return this.stream; }
                    }
 
                    /// <summary>
                    /// Set to true if the stream is a MemoryStream and we produced it (so it does have the buffer accesible)
                    /// </summary>
                    public bool IsKnownMemoryStream
                    {
                        get { return this.isKnownMemoryStream; }
                    }
                }
            }
        }
    }
}