public void Init() { this.context = new DataServiceContext(new Uri("http://temp.org/"), ODataProtocolVersion.V4); this.responseInfo = new ResponseInfo(new RequestInfo(this.context), MergeOption.NoTracking); this.readingHelper = new ODataMessageReadingHelper(this.responseInfo); this.atomResponseMessage = new ODataResponseMessageSimulator(); this.atomResponseMessage.SetHeader(XmlConstants.HttpContentType, "ApplIcAtIOn/AtOm"); this.jsonResponseMessage = new ODataResponseMessageSimulator(); this.jsonResponseMessage.SetHeader(XmlConstants.HttpContentType, "ApplIcAtIOn/jsOn"); }
internal static MaterializeAtom Materialize( ResponseInfo responseInfo, QueryComponents queryComponents, ProjectionPlan plan, string contentType, IODataResponseMessage message, ODataPayloadKind expectedPayloadKind) { Debug.Assert(null != queryComponents, "querycomponents"); Debug.Assert(null != message, "message"); // If there is no content (For e.g. /Customers(1)/BestFriend is null), we need to return empty results. if (message.StatusCode == (int)HttpStatusCode.NoContent || String.IsNullOrEmpty(contentType)) { return(MaterializeAtom.EmptyResults); } return(new MaterializeAtom(responseInfo, queryComponents, plan, message, expectedPayloadKind)); }
/// <summary>cleanup work to do once the request has completed</summary> protected override void CompletedRequest() { byte[] buffer = this.asyncStreamCopyBuffer; this.asyncStreamCopyBuffer = null; if ((null != buffer) && !this.usingBuffer) { this.PutAsyncResponseStreamCopyBuffer(buffer); } if (this.responseStreamOwner) { if (null != this.outputResponseStream) { this.outputResponseStream.Position = 0; } } Debug.Assert(null != this.responseMessage || null != this.Failure || this.IsAborted, "should have response or exception"); if (null != this.responseMessage) { // we've cached off what we need, headers still accessible after close WebUtil.DisposeMessage(this.responseMessage); Version responseVersion; Exception ex = SaveResult.HandleResponse( this.RequestInfo, this.StatusCode, this.responseMessage.GetHeader(XmlConstants.HttpODataVersion), this.GetResponseStream, false, out responseVersion); if (null != ex) { this.HandleFailure(ex); } else { this.responseInfo = this.CreateResponseInfo(); } } }
#pragma warning restore 649 #endif #endregion Private fields /// <summary> /// constructor /// </summary> /// <param name="responseInfo">originating context</param> /// <param name="queryComponents">Query components (projection, expected type)</param> /// <param name="plan">Projection plan (if compiled in an earlier query).</param> /// <param name="responseMessage">responseMessage</param> /// <param name="payloadKind">The kind of the payload to materialize.</param> internal MaterializeAtom( ResponseInfo responseInfo, QueryComponents queryComponents, ProjectionPlan plan, IODataResponseMessage responseMessage, ODataPayloadKind payloadKind) { Debug.Assert(queryComponents != null, "queryComponents != null"); this.responseInfo = responseInfo; this.elementType = queryComponents.LastSegmentType; this.expectingPrimitiveValue = PrimitiveType.IsKnownNullableType(elementType); Debug.Assert(responseMessage != null, "Response message is null! Did you mean to use Materializer.ResultsWrapper/EmptyResults?"); Type implementationType; Type materializerType = GetTypeForMaterializer(this.expectingPrimitiveValue, this.elementType, responseInfo.Model, out implementationType); this.materializer = ODataMaterializer.CreateMaterializerForMessage(responseMessage, responseInfo, materializerType, queryComponents, plan, payloadKind); }
private void MaterializeTest(HttpStatusCode statusCode, ODataPayloadKind payloadKind) { var uri = new Uri("http://any"); var context = new DataServiceContext(); var requestInfo = new RequestInfo(context); var responseInfo = new ResponseInfo(requestInfo, MergeOption.OverwriteChanges); var queryComponents = new QueryComponents(uri, new Version(4, 0), typeof(Product), null, null); var responseMessage = new HttpWebResponseMessage( new HeaderCollection(), (int)statusCode, () => new MemoryStream()); var materialize = DataServiceRequest.Materialize( responseInfo, queryComponents, null, "application/json", responseMessage, payloadKind); Assert.IsNull(materialize.Context); Assert.IsNull(materialize.Current); var enumerable = materialize.Cast<object>(); Assert.AreEqual(0, enumerable.Count()); }
/// <summary> /// Get the materializer to process the response. /// </summary> /// <param name="entityDescriptor">entity descriptor whose response is getting materialized.</param> /// <param name="responseInfo">information about the response to be materialized.</param> /// <returns>an instance of MaterializeAtom, that can be used to materialize the response.</returns> /// <remarks> /// This can only be called from inside the HandleBatchResponse or during enumeration of the responses. /// This is used when processing responses for update operations. /// </remarks> protected override MaterializeAtom GetMaterializer(EntityDescriptor entityDescriptor, ResponseInfo responseInfo) { // check if the batch stream is empty or not Debug.Assert(this.currentOperationResponse != null, "There must be an active operation response for this method to work correctly."); Debug.Assert(!this.currentOperationResponse.HasEmptyContent, "We should not get here if the response is empty."); // Since this is used for processing responses to update operations there are no projections to apply. QueryComponents queryComponents = new QueryComponents( /*uri*/ null, Util.ODataVersionEmpty, entityDescriptor.Entity.GetType(), /*projection*/ null, /*normalizerRewrites*/ null); return(new MaterializeAtom( responseInfo, queryComponents, /*projectionPlan*/ null, this.currentOperationResponse.CreateResponseMessage(), ODataPayloadKind.Resource)); }
/// <summary> /// process the batch response /// </summary> /// <param name="batchReader">The batch reader to use for reading the batch response.</param> /// <returns>enumerable of QueryResponse or null</returns> /// <remarks> /// The batch message reader for the entire batch response is stored in this.batchMessageReader. /// Note that this method takes over the ownership of this reader and must Dispose it if it successfully returns. /// </remarks> private IEnumerable <OperationResponse> HandleBatchResponse(ODataBatchReader batchReader) { try { if (this.batchMessageReader == null) { // The enumerable returned by this method can be enumerated multiple times. // In that case it looks like if the method is called multiple times. // This didn't fail in previous versions, it simply returned no results, so we need to do the same. yield break; } Debug.Assert(batchReader != null, "batchReader != null"); bool changesetFound = false; bool insideChangeset = false; int queryCount = 0; int operationCount = 0; this.entryIndex = 0; while (batchReader.Read()) { switch (batchReader.State) { #region ChangesetStart case ODataBatchReaderState.ChangesetStart: if ((Util.IsBatchWithSingleChangeset(this.Options) && changesetFound) || (operationCount != 0)) { // Throw if we encounter multiple changesets when running in batch with single changeset mode // or if we encounter operations outside of a changeset. Error.ThrowBatchUnexpectedContent(InternalError.UnexpectedBeginChangeSet); } insideChangeset = true; break; #endregion #region ChangesetEnd case ODataBatchReaderState.ChangesetEnd: changesetFound = true; operationCount = 0; insideChangeset = false; break; #endregion #region Operation case ODataBatchReaderState.Operation: Exception exception = this.ProcessCurrentOperationResponse(batchReader, insideChangeset); if (!insideChangeset) { #region Get response Debug.Assert(operationCount == 0, "missing an EndChangeSet 2"); QueryOperationResponse qresponse = null; try { if (exception == null) { DataServiceRequest query = this.Queries[queryCount]; ResponseInfo responseInfo = this.RequestInfo.GetDeserializationInfo(null /*mergeOption*/); MaterializeAtom materializer = DataServiceRequest.Materialize( responseInfo, query.QueryComponents(this.RequestInfo.Model), null, this.currentOperationResponse.Headers.GetHeader(XmlConstants.HttpContentType), this.currentOperationResponse.CreateResponseMessage(), query.PayloadKind); qresponse = QueryOperationResponse.GetInstance(query.ElementType, this.currentOperationResponse.Headers, query, materializer); } } catch (ArgumentException e) { exception = e; } catch (FormatException e) { exception = e; } catch (InvalidOperationException e) { exception = e; } if (qresponse == null) { if (this.Queries != null) { // this is the normal ExecuteBatch response DataServiceRequest query = this.Queries[queryCount]; if (this.RequestInfo.IgnoreResourceNotFoundException && this.currentOperationResponse.StatusCode == HttpStatusCode.NotFound) { qresponse = QueryOperationResponse.GetInstance(query.ElementType, this.currentOperationResponse.Headers, query, MaterializeAtom.EmptyResults); } else { qresponse = QueryOperationResponse.GetInstance(query.ElementType, this.currentOperationResponse.Headers, query, MaterializeAtom.EmptyResults); qresponse.Error = exception; } } else { // This is top-level failure for SaveChanges(SaveChangesOptions.BatchWithSingleChangeset) or SaveChanges(SaveChangesOptions.BatchWithIndependentOperations) operations. // 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/PATCH/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 exception; } } qresponse.StatusCode = (int)this.currentOperationResponse.StatusCode; queryCount++; yield return(qresponse); #endregion } else { #region Update response try { Descriptor descriptor = this.ChangedEntries[this.entryIndex]; operationCount += this.SaveResultProcessed(descriptor); if (exception != null) { throw exception; } this.HandleOperationResponseHeaders(this.currentOperationResponse.StatusCode, this.currentOperationResponse.Headers); #if DEBUG this.HandleOperationResponse(descriptor, this.currentOperationResponse.Headers, this.currentOperationResponse.StatusCode); #else this.HandleOperationResponse(descriptor, this.currentOperationResponse.Headers); #endif } catch (Exception e) { this.ChangedEntries[this.entryIndex].SaveError = e; exception = e; if (!CommonUtil.IsCatchableExceptionType(e)) { throw; } } ChangeOperationResponse changeOperationResponse = new ChangeOperationResponse(this.currentOperationResponse.Headers, this.ChangedEntries[this.entryIndex]); changeOperationResponse.StatusCode = (int)this.currentOperationResponse.StatusCode; if (exception != null) { changeOperationResponse.Error = exception; } operationCount++; this.entryIndex++; yield return(changeOperationResponse); #endregion } break; #endregion default: Error.ThrowBatchExpectedResponse(InternalError.UnexpectedBatchState); break; } } Debug.Assert(batchReader.State == ODataBatchReaderState.Completed, "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 ((this.Queries == null && (!changesetFound || 0 < queryCount || this.ChangedEntries.Any(o => o.ContentGeneratedForSave && o.SaveResultWasProcessed == 0) && (!this.IsBatchRequest || this.ChangedEntries.FirstOrDefault(o => o.SaveError != null) == null))) || (this.Queries != null && queryCount != this.Queries.Length)) { throw Error.InvalidOperation(Strings.Batch_IncompleteResponseCount); } } finally { // Note that this will be called only once the enumeration of all responses is finished and the Dispose // was called on the IEnumerator used for that enumeration. It is not called when the method returns, // since the compiler change this method to return the compiler-generated IEnumerable. Util.Dispose(ref this.batchMessageReader); } }
/// <summary> /// This method is for parsing CUD operation payloads which should contain /// 1 a single entry /// 2 An Error /// </summary> /// <param name="message">the message for the payload</param> /// <param name="responseInfo">The current ResponseInfo object</param> /// <param name="expectedType">The expected type</param> /// <returns>the MaterializerEntry that was read</returns> internal static MaterializerEntry ParseSingleEntityPayload(IODataResponseMessage message, ResponseInfo responseInfo, Type expectedType) { ODataPayloadKind messageType = ODataPayloadKind.Entry; using (ODataMessageReader messageReader = CreateODataMessageReader(message, responseInfo, ref messageType)) { IEdmType edmType = responseInfo.TypeResolver.ResolveExpectedTypeForReading(expectedType); ODataReaderWrapper reader = ODataReaderWrapper.Create(messageReader, messageType, edmType, responseInfo.ResponsePipeline); FeedAndEntryMaterializerAdapter parser = new FeedAndEntryMaterializerAdapter(messageReader, reader, responseInfo.Model, responseInfo.MergeOption); ODataEntry entry = null; bool readFeed = false; while (parser.Read()) { readFeed |= parser.CurrentFeed != null; if (parser.CurrentEntry != null) { if (entry != null) { throw new InvalidOperationException(DSClient.Strings.AtomParser_SingleEntry_MultipleFound); } entry = parser.CurrentEntry; } } if (entry == null) { if (readFeed) { throw new InvalidOperationException(DSClient.Strings.AtomParser_SingleEntry_NoneFound); } else { throw new InvalidOperationException(DSClient.Strings.AtomParser_SingleEntry_ExpectedFeedOrEntry); } } return MaterializerEntry.GetEntry(entry); } }
protected static ODataMessageReader CreateODataMessageReader(IODataResponseMessage responseMessage, ResponseInfo responseInfo, ref ODataPayloadKind payloadKind) { ODataMessageReaderSettings settings = responseInfo.ReadHelper.CreateSettings(); ODataMessageReader odataMessageReader = responseInfo.ReadHelper.CreateReader(responseMessage, settings); if (payloadKind == ODataPayloadKind.Unsupported) { var payloadKinds = odataMessageReader.DetectPayloadKind().ToList(); if (payloadKinds.Count == 0) { throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidResponsePayload(XmlConstants.DataWebNamespace)); } // Pick the first payload kind detected by ODataLib and use that to parse the exception. // The only exception being payload with entity reference link(s). If one of the payload kinds // is reference links, then we need to give preference to reference link payloads. ODataPayloadKindDetectionResult detectionResult = payloadKinds.FirstOrDefault(k => k.PayloadKind == ODataPayloadKind.EntityReferenceLink || k.PayloadKind == ODataPayloadKind.EntityReferenceLinks); if (detectionResult == null) { detectionResult = payloadKinds.First(); } // Astoria client only supports atom, jsonlight and raw value payloads. #pragma warning disable 618 if (detectionResult.Format != ODataFormat.Atom && detectionResult.Format != ODataFormat.Json && detectionResult.Format != ODataFormat.RawValue) #pragma warning restore 618 { throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidContentTypeEncountered(responseMessage.GetHeader(XmlConstants.HttpContentType))); } payloadKind = detectionResult.PayloadKind; } return odataMessageReader; }
/// <summary> /// Initializes a new instance of the <see cref="ODataMessageReadingHelper"/> class. /// </summary> /// <param name="responseInfo">The response info.</param> internal ODataMessageReadingHelper(ResponseInfo responseInfo) { Debug.Assert(responseInfo != null, "responseInfo != null"); this.responseInfo = responseInfo; }
/// <summary> /// Get the materializer to process the response. /// </summary> /// <param name="entityDescriptor">entity descriptor whose response is getting materialized.</param> /// <param name="responseInfo">information about the response to be materialized.</param> /// <returns>an instance of MaterializeAtom, that can be used to materialize the response.</returns> protected override MaterializeAtom GetMaterializer(EntityDescriptor entityDescriptor, ResponseInfo responseInfo) { Debug.Assert(this.cachedResponse.Exception == null && this.cachedResponse.MaterializerEntry != null, "this.cachedResponse.Exception == null && this.cachedResponse.Entry != null"); ODataResource entry = this.cachedResponse.MaterializerEntry == null ? null : this.cachedResponse.MaterializerEntry.Entry; return(new MaterializeAtom(responseInfo, new[] { entry }, entityDescriptor.Entity.GetType(), this.cachedResponse.MaterializerEntry.Format)); }
/// <summary> /// Get the materializer to process the response. /// </summary> /// <param name="entityDescriptor">entity descriptor whose response is getting materialized.</param> /// <param name="responseInfo">information about the response to be materialized.</param> /// <returns>an instance of MaterializeAtom, that can be used to materialize the response.</returns> /// <remarks> /// This can only be called from inside the HandleBatchResponse or during enumeration of the responses. /// This is used when processing responses for update operations. /// </remarks> protected override MaterializeAtom GetMaterializer(EntityDescriptor entityDescriptor, ResponseInfo responseInfo) { // check if the batch stream is empty or not Debug.Assert(this.currentOperationResponse != null, "There must be an active operation response for this method to work correctly."); Debug.Assert(!this.currentOperationResponse.HasEmptyContent, "We should not get here if the response is empty."); // Since this is used for processing responses to update operations there are no projections to apply. QueryComponents queryComponents = new QueryComponents( /*uri*/ null, Util.ODataVersionEmpty, entityDescriptor.Entity.GetType(), /*projection*/ null, /*normalizerRewrites*/ null); return new MaterializeAtom( responseInfo, queryComponents, /*projectionPlan*/ null, this.currentOperationResponse.CreateResponseMessage(), ODataPayloadKind.Entry); }
public static ODataMaterializer CreateMaterializerForMessage( IODataResponseMessage responseMessage, ResponseInfo responseInfo, Type materializerType, QueryComponents queryComponents, ProjectionPlan plan, ODataPayloadKind payloadKind) { ODataMessageReader messageReader = CreateODataMessageReader(responseMessage, responseInfo, ref payloadKind); ODataMaterializer result; IEdmType edmType = null; try { ODataMaterializerContext materializerContext = new ODataMaterializerContext(responseInfo); // Since in V1/V2, astoria client allowed Execute<object> and depended on the typeresolver or the wire type name // to get the clr type to materialize. Hence if we see the materializer type as object, we should set the edmtype // to null, since there is no expected type. if (materializerType != typeof(System.Object)) { edmType = responseInfo.TypeResolver.ResolveExpectedTypeForReading(materializerType); } if (payloadKind == ODataPayloadKind.Entry || payloadKind == ODataPayloadKind.Feed) { // In V1/V2, we allowed System.Object type to be allowed to pass to ExecuteQuery. // Hence we need to explicitly check for System.Object to allow this if (edmType != null && edmType.TypeKind != EdmTypeKind.Entity) { throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidNonEntityType(materializerType.FullName)); } ODataReaderWrapper reader = ODataReaderWrapper.Create(messageReader, payloadKind, edmType, responseInfo.ResponsePipeline); EntityTrackingAdapter entityTrackingAdapter = new EntityTrackingAdapter(responseInfo.EntityTracker, responseInfo.MergeOption, responseInfo.Model, responseInfo.Context); LoadPropertyResponseInfo loadPropertyResponseInfo = responseInfo as LoadPropertyResponseInfo; if (loadPropertyResponseInfo != null) { result = new ODataLoadNavigationPropertyMaterializer( messageReader, reader, materializerContext, entityTrackingAdapter, queryComponents, materializerType, plan, loadPropertyResponseInfo); } else { result = new ODataReaderEntityMaterializer( messageReader, reader, materializerContext, entityTrackingAdapter, queryComponents, materializerType, plan); } } else { switch (payloadKind) { case ODataPayloadKind.Value: result = new ODataValueMaterializer(messageReader, materializerContext, materializerType, queryComponents.SingleResult); break; case ODataPayloadKind.Collection: result = new ODataCollectionMaterializer(messageReader, materializerContext, materializerType, queryComponents.SingleResult); break; case ODataPayloadKind.Property: case ODataPayloadKind.IndividualProperty: // Top level properties cannot be of entity type. if (edmType != null && edmType.TypeKind == EdmTypeKind.Entity) { throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidEntityType(materializerType.FullName)); } result = new ODataPropertyMaterializer(messageReader, materializerContext, materializerType, queryComponents.SingleResult); break; case ODataPayloadKind.EntityReferenceLinks: case ODataPayloadKind.EntityReferenceLink: result = new ODataLinksMaterializer(messageReader, materializerContext, materializerType, queryComponents.SingleResult); break; case ODataPayloadKind.Error: var odataError = messageReader.ReadError(); throw new ODataErrorException(odataError.Message, odataError); default: throw DSClient.Error.InvalidOperation(DSClient.Strings.AtomMaterializer_InvalidResponsePayload(XmlConstants.DataWebNamespace)); } } return result; } catch (Exception ex) { if (CommonUtil.IsCatchableExceptionType(ex)) { // Dispose the message reader in all error scenarios. messageReader.Dispose(); } throw; } }
/// <summary> /// Get the materializer to process the response. /// </summary> /// <param name="entityDescriptor">entity descriptor whose response is getting materialized.</param> /// <param name="responseInfo">information about the response to be materialized.</param> /// <returns>an instance of MaterializeAtom, that can be used to materialize the response.</returns> protected abstract MaterializeAtom GetMaterializer(EntityDescriptor entityDescriptor, ResponseInfo responseInfo);
/// <summary> /// Materialize the response payload. /// </summary> /// <param name="entityDescriptor">entity descriptor whose response is getting materialized.</param> /// <param name="responseInfo">information about the response to be materialized.</param> /// <param name="etag">etag value, if specified in the response header.</param> private void MaterializeResponse(EntityDescriptor entityDescriptor, ResponseInfo responseInfo, string etag) { using (MaterializeAtom materializer = this.GetMaterializer(entityDescriptor, responseInfo)) { materializer.SetInsertingObject(entityDescriptor.Entity); object materializedEntity = null; foreach (object x in materializer) { Debug.Assert(materializedEntity == null, "entity == null"); if (materializedEntity != null) { Error.ThrowInternalError(InternalError.MaterializerReturningMoreThanOneEntity); } materializedEntity = x; } Debug.Assert(null != entityDescriptor.GetLatestIdentity(), "updated inserted should always gain an identity"); Debug.Assert(materializedEntity == entityDescriptor.Entity, "x == entityDescriptor.Entity, should have same object generated by response"); Debug.Assert(EntityStates.Unchanged == entityDescriptor.State, "should have moved out of insert"); Debug.Assert(this.RequestInfo.EntityTracker.TryGetEntityDescriptor(entityDescriptor.GetLatestIdentity()) != null, "should have identity tracked"); // If there was no etag specified in the payload, then we need to set the etag from the header if (entityDescriptor.GetLatestETag() == null) { entityDescriptor.ETag = etag; } } }
/// <summary> /// Get the materializer to process the response. /// </summary> /// <param name="entityDescriptor">entity descriptor whose response is getting materialized.</param> /// <param name="responseInfo">information about the response to be materialized.</param> /// <returns>an instance of MaterializeAtom, that can be used to materialize the response.</returns> protected override MaterializeAtom GetMaterializer(EntityDescriptor entityDescriptor, ResponseInfo responseInfo) { Debug.Assert(this.cachedResponse.Exception == null && this.cachedResponse.MaterializerEntry != null, "this.cachedResponse.Exception == null && this.cachedResponse.Entry != null"); ODataEntry entry = this.cachedResponse.MaterializerEntry == null ? null : this.cachedResponse.MaterializerEntry.Entry; return new MaterializeAtom(responseInfo, new[] { entry }, entityDescriptor.Entity.GetType(), this.cachedResponse.MaterializerEntry.Format); }
/// <summary> /// Handle the response payload. /// </summary> /// <param name="responseMsg">httpwebresponse instance.</param> /// <param name="responseStream">stream containing the response payload.</param> private void HandleOperationResponseData(IODataResponseMessage responseMsg, Stream responseStream) { Debug.Assert(this.entryIndex >= 0 && this.entryIndex < this.ChangedEntries.Count, string.Format(System.Globalization.CultureInfo.InvariantCulture, "this.entryIndex = '{0}', this.ChangedEntries.Count() = '{1}'", this.entryIndex, this.ChangedEntries.Count)); // Parse the response Descriptor current = this.ChangedEntries[this.entryIndex]; MaterializerEntry entry = default(MaterializerEntry); Version responseVersion; Exception exception = BaseSaveResult.HandleResponse(this.RequestInfo, (HttpStatusCode)responseMsg.StatusCode, responseMsg.GetHeader(XmlConstants.HttpODataVersion), () => { return(responseStream); }, false /*throwOnFailure*/, out responseVersion); var headers = new HeaderCollection(responseMsg); if (responseStream != null && current.DescriptorKind == DescriptorKind.Entity && exception == null) { // Only process the response if the current resource is an entity and it's an insert or update scenario EntityDescriptor entityDescriptor = (EntityDescriptor)current; // We were ignoring the payload for non-insert and non-update scenarios. We need to keep doing that. if (entityDescriptor.State == EntityStates.Added || entityDescriptor.StreamState == EntityStates.Added || entityDescriptor.State == EntityStates.Modified || entityDescriptor.StreamState == EntityStates.Modified) { try { ResponseInfo responseInfo = this.CreateResponseInfo(entityDescriptor); var responseMessageWrapper = new HttpWebResponseMessage( headers, responseMsg.StatusCode, () => responseStream); entry = ODataReaderEntityMaterializer.ParseSingleEntityPayload(responseMessageWrapper, responseInfo, entityDescriptor.Entity.GetType()); entityDescriptor.TransientEntityDescriptor = entry.EntityDescriptor; } catch (Exception ex) { exception = ex; if (!CommonUtil.IsCatchableExceptionType(ex)) { throw; } } } } this.cachedResponses.Add(new CachedResponse( current, headers, (HttpStatusCode)responseMsg.StatusCode, responseVersion, entry, exception)); if (exception != null) { current.SaveError = exception; // DEVNOTE(pqian): // There are two possible scenario here: // 1. We are in the sync code path, and there's an in stream error on the server side, or there are bad xml thrown // 2. We are in the async code path, there's a error thrown on the server side (any error) // Ideally, we need to check whether we want to continue to the next changeset. (Call this.CheckContinueOnError) // However, in V1/V2, we did not do this. Thus we will always continue on error on these scenarios } }