/// <summary> /// Creates a StreamDescriptor class with the given name and other information /// </summary> /// <param name="name">name of the stream.</param> /// <param name="entityDescriptor">instance of entity descriptor that contains this stream.</param> internal StreamDescriptor(string name, EntityDescriptor entityDescriptor) : base(EntityStates.Unchanged) { Debug.Assert(!String.IsNullOrEmpty(name), "!String.IsNullOrEmpty(name)"); Debug.Assert(entityDescriptor != null, "entityDescriptor != null"); this.streamLink = new DataServiceStreamLink(name); this.entityDescriptor = entityDescriptor; }
/// <summary> /// Creates a StreamDescriptor class for the default stream (MR) associated with an entity. /// </summary> /// <param name="entityDescriptor">instance of entity descriptor that contains this stream.</param> internal StreamDescriptor(EntityDescriptor entityDescriptor) : base(EntityStates.Unchanged) { Debug.Assert(entityDescriptor != null, "entityDescriptor != null"); this.streamLink = new DataServiceStreamLink(null); this.entityDescriptor = entityDescriptor; }
/// <summary> /// Instantiates a new Serializer class and calls WriteEntry method on it. /// </summary> /// <param name="dataServiceContext"></param> /// <returns></returns> private static Person SetupSerializerAndCallWriteEntry(DataServiceContext dataServiceContext) { Person person = new Person(); Address address = new Address(); Car car1 = new Car(); person.Cars.Add(car1); person.HomeAddress = address; dataServiceContext.AttachTo("Cars", car1); dataServiceContext.AttachTo("Addresses", address); var requestInfo = new RequestInfo(dataServiceContext); var serializer = new Serializer(requestInfo); var headers = new HeaderCollection(); var clientModel = new ClientEdmModel(ODataProtocolVersion.V4); var entityDescriptor = new EntityDescriptor(clientModel); entityDescriptor.State = EntityStates.Added; entityDescriptor.Entity = person; var requestMessageArgs = new BuildingRequestEventArgs("POST", new Uri("http://www.foo.com/Northwind"), headers, entityDescriptor, HttpStack.Auto); var linkDescriptors = new LinkDescriptor[] { new LinkDescriptor(person, "Cars", car1, clientModel), new LinkDescriptor(person, "HomeAddress", address, clientModel) }; var odataRequestMessageWrapper = ODataRequestMessageWrapper.CreateRequestMessageWrapper(requestMessageArgs, requestInfo); serializer.WriteEntry(entityDescriptor, linkDescriptors, odataRequestMessageWrapper); return person; }
/// <summary> /// Creates a new instance of MaterializerEntry using the given entity descriptor for LoadProperty. /// </summary> /// <param name="entityDescriptor">Entity descriptor.</param> /// <param name="format">OData Format.</param> /// <param name="isTracking">Whether this entity is being tracked.</param> /// <remarks>Use this constructor only for LoadProperty scenario.</remarks> private MaterializerEntry(EntityDescriptor entityDescriptor, ODataFormat format, bool isTracking) { this.entityDescriptor = entityDescriptor; this.Format = format; #pragma warning disable 618 this.isAtomOrTracking = isTracking || this.Format == ODataFormat.Atom; #pragma warning restore 618 this.SetFlagValue(EntryFlags.ShouldUpdateFromPayload | EntryFlags.EntityHasBeenResolved | EntryFlags.ForLoadProperty, true); }
public void Init() { this.clientEdmModel = new ClientEdmModel(ODataProtocolVersion.V4); this.entityDescriptor = new EntityDescriptor(this.clientEdmModel) { Entity = new Customer() }; var serverType = new EdmEntityType("FQ.NS", "MyServerType"); serverType.AddUnidirectionalNavigation(new EdmNavigationPropertyInfo { Name = "Navigation", Target = serverType, TargetMultiplicity = EdmMultiplicity.ZeroOrOne }); this.serverTypeName = ((IEdmSchemaElement)serverType).FullName(); var serverContainer = new EdmEntityContainer("FQ.NS", "MyContainer"); this.serverEntitySet = serverContainer.AddEntitySet("MyEntitySet", serverType); var serverModel = new EdmModel(); serverModel.AddElement(serverType); serverModel.AddElement(serverContainer); this.ctx = new DataServiceContext(new Uri("http://temp.org/"), ODataProtocolVersion.V4, this.clientEdmModel); this.ctx.Format.UseJson(serverModel); this.testSubject = new RequestInfo(ctx); }
/// <summary> /// Creates a new instance of MaterializerEntry. /// </summary> /// <param name="entry">The entry.</param> /// <param name="format">The format the entry was read in.</param> /// <param name="isTracking">True if the contents of the entry will be tracked in the context, otherwise False.</param> /// <param name="model">The client model.</param> private MaterializerEntry(ODataEntry entry, ODataFormat format, bool isTracking, ClientEdmModel model) { Debug.Assert(entry != null, "entry != null"); this.entry = entry; this.Format = format; this.entityDescriptor = new EntityDescriptor(model); #pragma warning disable 618 this.isAtomOrTracking = isTracking || this.Format == ODataFormat.Atom; #pragma warning restore 618 string serverTypeName = this.Entry.TypeName; SerializationTypeNameAnnotation serializationTypeNameAnnotation = entry.GetAnnotation<SerializationTypeNameAnnotation>(); if (serializationTypeNameAnnotation != null) { // If the annotation has a value use it. Otherwise, in JSON-Light, the types can be inferred from the // context URI even if they are not present on the wire, so just use the type name from the entry. if (serializationTypeNameAnnotation.TypeName != null || this.Format != ODataFormat.Json) { serverTypeName = serializationTypeNameAnnotation.TypeName; } } this.entityDescriptor.ServerTypeName = serverTypeName; }
/// <summary> /// Attach entity into the context in the Unchanged state. /// </summary> /// <param name="entityDescriptorFromMaterializer">entity descriptor from the response</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 override EntityDescriptor InternalAttachEntityDescriptor(EntityDescriptor entityDescriptorFromMaterializer, bool failIfDuplicated) { Debug.Assert((null != entityDescriptorFromMaterializer.Identity), "must have identity"); Debug.Assert(null != entityDescriptorFromMaterializer.Entity && ClientTypeUtil.TypeIsEntity(entityDescriptorFromMaterializer.Entity.GetType(), this.model), "must be entity type to attach"); this.EnsureIdentityToResource(); EntityDescriptor trackedEntityDescriptor; this.entityDescriptors.TryGetValue(entityDescriptorFromMaterializer.Entity, out trackedEntityDescriptor); EntityDescriptor existing; this.identityToDescriptor.TryGetValue(entityDescriptorFromMaterializer.Identity, out existing); // identity existing & pointing to something else if (failIfDuplicated && (null != trackedEntityDescriptor)) { throw Error.InvalidOperation(Strings.Context_EntityAlreadyContained); } else if (trackedEntityDescriptor != existing) { throw Error.InvalidOperation(Strings.Context_DifferentEntityAlreadyContained); } else if (null == trackedEntityDescriptor) { trackedEntityDescriptor = entityDescriptorFromMaterializer; // if resource doesn't exist... this.IncrementChange(entityDescriptorFromMaterializer); this.entityDescriptors.Add(entityDescriptorFromMaterializer.Entity, entityDescriptorFromMaterializer); this.identityToDescriptor.Add(entityDescriptorFromMaterializer.Identity, entityDescriptorFromMaterializer); } // DEVNOTE(pqian): // we used to mark the descriptor as Unchanged // but it's now up to the caller to do that return trackedEntityDescriptor; }
internal 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> /// Adds the given entity descriptors to the list of the tracked entity descriptors. /// </summary> /// <param name="descriptor">entity descriptor instance to be added.</param> internal void AddEntityDescriptor(EntityDescriptor descriptor) { try { this.entityDescriptors.Add(descriptor.Entity, descriptor); } catch (ArgumentException) { throw Error.InvalidOperation(Strings.Context_EntityAlreadyContained); } }
/// <summary> /// Write the entry element. /// </summary> /// <param name="entityDescriptor">The entity.</param> /// <param name="relatedLinks">Collection of links related to the entity.</param> /// <param name="requestMessage">The OData request message.</param> internal void WriteEntry(EntityDescriptor entityDescriptor, IEnumerable<LinkDescriptor> relatedLinks, ODataRequestMessageWrapper requestMessage) { ClientEdmModel model = this.requestInfo.Model; ClientTypeAnnotation entityType = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityDescriptor.Entity.GetType())); using (ODataMessageWriter messageWriter = Serializer.CreateMessageWriter(requestMessage, this.requestInfo, false /*isParameterPayload*/)) { ODataWriterWrapper entryWriter = ODataWriterWrapper.CreateForEntry(messageWriter, this.requestInfo.Configurations.RequestPipeline); // Get the server type name using the type resolver or from the entity descriptor string serverTypeName = this.requestInfo.GetServerTypeName(entityDescriptor); var entry = CreateODataEntry(entityDescriptor, serverTypeName, entityType, this.requestInfo.Format); if (serverTypeName == null) { serverTypeName = this.requestInfo.InferServerTypeNameFromServerModel(entityDescriptor); } IEnumerable<ClientPropertyAnnotation> properties; if ((!Util.IsFlagSet(this.options, SaveChangesOptions.ReplaceOnUpdate) && entityDescriptor.State == EntityStates.Modified && entityDescriptor.PropertiesToSerialize.Any()) || (Util.IsFlagSet(this.options, SaveChangesOptions.PostOnlySetProperties) && entityDescriptor.State == EntityStates.Added)) { properties = entityType.PropertiesToSerialize().Where(prop => entityDescriptor.PropertiesToSerialize.Contains(prop.PropertyName)); } else { properties = entityType.PropertiesToSerialize(); } entry.Properties = this.propertyConverter.PopulateProperties(entityDescriptor.Entity, serverTypeName, properties); entryWriter.WriteStart(entry, entityDescriptor.Entity); if (EntityStates.Added == entityDescriptor.State) { this.WriteNavigationLink(entityDescriptor, relatedLinks, entryWriter); } entryWriter.WriteEnd(entry, entityDescriptor.Entity); } }
public void RequestInfoShouldCreateTunneledPatchRequestMessagePostMethodAndPatchInHttpXMethodHeader() { bool previousPostTunnelingValue = ctx.UsePostTunneling; ctx.UsePostTunneling = true; HeaderCollection headersCollection = new HeaderCollection(); var descriptor = new EntityDescriptor(this.clientEdmModel) { ServerTypeName = this.serverTypeName, Entity = new Customer() }; var buildingRequestArgs = new BuildingRequestEventArgs("PATCH", new Uri("http://localhost/fakeService.svc/"), headersCollection, descriptor, HttpStack.Auto); var requestMessage = (HttpWebRequestMessage)testSubject.CreateRequestMessage(buildingRequestArgs); requestMessage.GetHeader(XmlConstants.HttpXMethod).Should().Be("PATCH"); requestMessage.Method.Should().Be("PATCH"); requestMessage.HttpWebRequest.Method.Should().Be("POST"); // undoing change so this is applicable only for this test. ctx.UsePostTunneling = previousPostTunnelingValue; }
/// <summary> /// Returns the instance of LoadPropertyResponseInfo class, which provides information for LoadProperty response handling. /// </summary> /// <param name="mergeOption">Merge option to use for conflict handling.</param> /// <param name="entityDescriptor">Entity whose property is being loaded.</param> /// <param name="property">Property which is being loaded.</param> /// <returns>Instance of the LoadPropertyResponseInfo class.</returns> internal ResponseInfo GetDeserializationInfoForLoadProperty(MergeOption? mergeOption, EntityDescriptor entityDescriptor, ClientPropertyAnnotation property) { return new LoadPropertyResponseInfo( this, mergeOption.HasValue ? mergeOption.Value : this.Context.MergeOption, entityDescriptor, property); }
/// <summary> /// Generate a request for the given entity. /// </summary> /// <param name="entityDescriptor">Instance of EntityDescriptor.</param> /// <param name="requestMessage">Instance of IODataRequestMessage to be used to generate the payload.</param> /// <returns>True if the payload was generated, otherwise false.</returns> private bool CreateRequestData(EntityDescriptor entityDescriptor, ODataRequestMessageWrapper requestMessage) { Debug.Assert(null != entityDescriptor, "null entityDescriptor"); bool generateRequestPayload = false; switch (entityDescriptor.State) { case EntityStates.Deleted: break; case EntityStates.Modified: case EntityStates.Added: generateRequestPayload = true; break; default: Error.ThrowInternalError(InternalError.UnvalidatedEntityState); break; } if (generateRequestPayload) { Debug.Assert(this.SerializerInstance != null, "this.SerializerInstance != null"); this.SerializerInstance.WriteEntry(entityDescriptor, this.RelatedLinks(entityDescriptor), requestMessage); } return generateRequestPayload; }
private static Uri AppendTargetEntityKeyIfNeeded(Uri linkUri, LinkDescriptor binding, EntityDescriptor targetResource) { // To delete from a collection, we need to append the key. // For example: if the navigation property name is "Purchases" and the resource type is Order with key '1', then this method will generate 'baseuri/Purchases(1)' if (!binding.IsSourcePropertyCollection || EntityStates.Deleted != binding.State) { return linkUri; } Debug.Assert(targetResource != null, "targetResource != null"); StringBuilder builder = new StringBuilder(); builder.Append(UriUtil.UriToString(linkUri)); builder.Append(UriHelper.QUESTIONMARK + XmlConstants.HttpQueryStringId + UriHelper.EQUALSSIGN + targetResource.Identity); return UriUtil.CreateUri(builder.ToString(), UriKind.RelativeOrAbsolute); }
/// <summary> /// clears all the changes - like closes the save stream, clears the transient entity descriptor. /// This method is called when the client is done with sending all the pending requests. /// </summary> internal override void ClearChanges() { this.transientEntityDescriptor = null; this.CloseSaveStream(); }
/// <summary>Handle changeset response for the given entity descriptor.</summary> /// <param name="entityDescriptor">entity descriptor whose response is getting handled.</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 entityDescriptor, string etag) { try { if (EntityStates.Added != entityDescriptor.State && EntityStates.Added != entityDescriptor.StreamState) { Error.ThrowBatchUnexpectedContent(InternalError.EntityNotAddedState); } if (this.ProcessResponsePayload) { this.MaterializeResponse(entityDescriptor, this.CreateResponseInfo(entityDescriptor), etag); } else { entityDescriptor.ETag = etag; entityDescriptor.State = EntityStates.Unchanged; entityDescriptor.PropertiesToSerialize.Clear(); } if (entityDescriptor.StreamState != EntityStates.Added) { // For MR - entityDescriptor.State is merged, we don't need to do link folding since MR will never fold links. foreach (LinkDescriptor end in this.RelatedLinks(entityDescriptor)) { 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 (Util.IncludeLinkState(end.SaveResultWasProcessed) || end.SaveResultWasProcessed == EntityStates.Added) { HandleResponsePost(end); } } } } finally { if (entityDescriptor.StreamState == EntityStates.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(entityDescriptor.State == EntityStates.Unchanged, "The materializer should always set the entity state to Unchanged."); entityDescriptor.State = EntityStates.Modified; // Need to clear the stream state so the next iteration we will always process the Put MLE operation. entityDescriptor.StreamState = EntityStates.Unchanged; } } }
/// <summary> /// Infers the server type name for the entity tracked in the given descriptor based on the server model. /// </summary> /// <param name="descriptor">The descriptor containing the entity to get the type name for.</param> /// <returns>The type name or null if it could not be inferred.</returns> internal string InferServerTypeNameFromServerModel(EntityDescriptor descriptor) { Debug.Assert(descriptor != null, "descriptor != null"); Debug.Assert(descriptor.ServerTypeName == null, "Should not be called if the server type name is already known."); if (descriptor.EntitySetName != null) { string serverTypeName; if (this.TypeResolver.TryResolveEntitySetBaseTypeName(descriptor.EntitySetName, out serverTypeName)) { return serverTypeName; } } else if (descriptor.IsDeepInsert) { string parentServerTypeName = this.GetServerTypeName(descriptor.ParentForInsert); if (parentServerTypeName == null) { parentServerTypeName = this.InferServerTypeNameFromServerModel(descriptor.ParentForInsert); } string serverTypeName; if (this.TypeResolver.TryResolveNavigationTargetTypeName(parentServerTypeName, descriptor.ParentPropertyForInsert, out serverTypeName)) { return serverTypeName; } } return null; }
/// <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> /// <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); }
/// <summary> /// Get the source property Uri for the link URL /// </summary> /// <param name="binding">Link descriptor object of the binding</param> /// <param name="sourceEntityDescriptor">entity descriptor for source</param> /// <returns>source property Uri string</returns> private string GetSourcePropertyUri(LinkDescriptor binding, EntityDescriptor sourceEntityDescriptor) { Debug.Assert(binding != null, "binding != null"); Debug.Assert(sourceEntityDescriptor != null, "sourceEntityDescriptor != null"); if (string.IsNullOrEmpty(binding.SourceProperty)) { return null; } string sourcePropertyUri = binding.SourceProperty; // Add type segment in the link URL for the derived entity type on which a navigation property is defined. // e.g. cxt.Attachto("<entitySetname>",<EntityToBeSource>) // cxt.AddLink(<EntityToBeSource>, "<NavigationPropertyName>" <EntityToBeTarget>) // Get entity type name from model (here service model instead of client model should be used) string entityTypeFullName = this.RequestInfo.TypeResolver.ResolveServiceEntityTypeFullName(binding.Source.GetType()); if (string.IsNullOrEmpty(entityTypeFullName)) { return sourcePropertyUri; } // Get the type of entityset from service model. string sourceEntitySetTypeName = null; if (!string.IsNullOrEmpty(sourceEntityDescriptor.EntitySetName) && this.RequestInfo.TypeResolver.TryResolveEntitySetBaseTypeName(sourceEntityDescriptor.EntitySetName, out sourceEntitySetTypeName)) { // Check whether the entity type and the entity set type are matched. if not matched, set the dervied entity type name as a key segment in the URL. if (!string.IsNullOrEmpty(sourceEntitySetTypeName) && !string.Equals(entityTypeFullName, sourceEntitySetTypeName, StringComparison.OrdinalIgnoreCase)) { sourcePropertyUri = entityTypeFullName + UriHelper.FORWARDSLASH + sourcePropertyUri; } } return sourcePropertyUri; }
public void PostTunnelingDeleteRequestShouldNotHaveContentTypeHeader() { bool previousPostTunnelingValue = ctx.UsePostTunneling; ctx.UsePostTunneling = true; HeaderCollection headersCollection = new HeaderCollection(); var descriptor = new EntityDescriptor(this.clientEdmModel) { ServerTypeName = this.serverTypeName, Entity = new Customer() }; var buildingRequestArgs = new BuildingRequestEventArgs("DELETE", new Uri("http://localhost/fakeService.svc/"), headersCollection, descriptor, HttpStack.Auto); ctx.Configurations.RequestPipeline.OnMessageCreating = (args) => { buildingRequestArgs.Headers.Keys.Should().NotContain(XmlConstants.HttpContentType); return new HttpWebRequestMessage(args); }; testSubject.CreateRequestMessage(buildingRequestArgs); // undoing change so this is applicable only for this test. ctx.UsePostTunneling = previousPostTunnelingValue; ctx.Configurations.RequestPipeline.OnMessageCreating = null; }
/// <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> /// Creates an ODataEntry for the given EntityDescriptor and fills in its ODataLib metadata. /// </summary> /// <param name="entityDescriptor">The entity descriptor.</param> /// <param name="serverTypeName">Name of the server type.</param> /// <param name="entityType">The client-side entity type.</param> /// <param name="clientFormat">The current client format.</param> /// <returns>An odata entry with its metadata filled in.</returns> internal static ODataEntry CreateODataEntry(EntityDescriptor entityDescriptor, string serverTypeName, ClientTypeAnnotation entityType, DataServiceClientFormat clientFormat) { ODataEntry entry = new ODataEntry(); // If the client type name is different from the server type name, then add SerializationTypeNameAnnotation // which tells ODataLib to write the type name in the annotation in the payload. if (entityType.ElementTypeName != serverTypeName) { entry.SetAnnotation(new SerializationTypeNameAnnotation { TypeName = serverTypeName }); } // We always need to write the client type name, since this is the type name used by ODataLib // to resolve the entity type using EdmModel.FindSchemaElement. entry.TypeName = entityType.ElementTypeName; // Continue to send the entry's ID in update payloads in Atom for compatibility with V1-V3, // but for JSON-Light we do not want the extra information on the wire. if (clientFormat.UsingAtom && EntityStates.Modified == entityDescriptor.State) { // <id>http://host/service/entityset(key)</id> entry.Id = entityDescriptor.GetLatestIdentity(); } if (entityDescriptor.IsMediaLinkEntry || entityType.IsMediaLinkEntry) { // Since we are already enabled EnableWcfDataServicesClientBehavior in the writer settings, // setting the MediaResource value will tell ODataLib to write MLE payload, irrespective of // what the metadata says. entry.MediaResource = new ODataStreamReferenceValue(); } return entry; }
/// <summary> /// Create the response info instance to be passed to the materializer. /// </summary> /// <param name="entityDescriptor">entity descriptor whose response is getting handled.</param> /// <returns>instance of the response info class.</returns> protected ResponseInfo CreateResponseInfo(EntityDescriptor entityDescriptor) { 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 (entityDescriptor.StreamState == EntityStates.Added) { mergeOption = MergeOption.PreserveChanges; Debug.Assert(entityDescriptor.State == EntityStates.Modified, "The MLE state must be Modified."); } return this.RequestInfo.GetDeserializationInfo(mergeOption); }
/// <summary> /// Writes a navigation link. /// </summary> /// <param name="entityDescriptor">The entity</param> /// <param name="relatedLinks">The links related to the entity</param> /// <param name="odataWriter">The ODataWriter used to write the navigation link.</param> internal void WriteNavigationLink(EntityDescriptor entityDescriptor, IEnumerable<LinkDescriptor> relatedLinks, ODataWriterWrapper odataWriter) { // TODO: create instance of odatawriter. // TODO: send clientType once, so that we dont need entity descriptor Debug.Assert(EntityStates.Added == entityDescriptor.State, "entity not added state"); Dictionary<string, List<LinkDescriptor>> groupRelatedLinks = new Dictionary<string, List<LinkDescriptor>>(EqualityComparer<string>.Default); foreach (LinkDescriptor end in relatedLinks) { List<LinkDescriptor> linkDescriptorsList = null; if (!groupRelatedLinks.TryGetValue(end.SourceProperty, out linkDescriptorsList)) { linkDescriptorsList = new List<LinkDescriptor>(); groupRelatedLinks.Add(end.SourceProperty, linkDescriptorsList); } linkDescriptorsList.Add(end); } ClientTypeAnnotation clientType = null; foreach (var grlinks in groupRelatedLinks) { if (null == clientType) { ClientEdmModel model = this.requestInfo.Model; clientType = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityDescriptor.Entity.GetType())); } bool isCollection = clientType.GetProperty(grlinks.Key, false).IsEntityCollection; bool started = false; foreach (LinkDescriptor end in grlinks.Value) { Debug.Assert(!end.ContentGeneratedForSave, "already saved link"); end.ContentGeneratedForSave = true; Debug.Assert(null != end.Target, "null is DELETE"); ODataNavigationLink navigationLink = new ODataNavigationLink(); navigationLink.Url = this.requestInfo.EntityTracker.GetEntityDescriptor(end.Target).GetLatestEditLink(); Debug.Assert(Uri.IsWellFormedUriString(UriUtil.UriToString(navigationLink.Url), UriKind.Absolute), "Uri.IsWellFormedUriString(targetEditLink, UriKind.Absolute)"); navigationLink.IsCollection = isCollection; navigationLink.Name = grlinks.Key; if (!started) { odataWriter.WriteNavigationLinksStart(navigationLink); started = true; } odataWriter.WriteNavigationLinkStart(navigationLink, end.Source, end.Target); odataWriter.WriteEntityReferenceLink(new ODataEntityReferenceLink() { Url = navigationLink.Url }, end.Source, end.Target); odataWriter.WriteNavigationLinkEnd(navigationLink, end.Source, end.Target); } odataWriter.WriteNavigationLinksEnd(); } }
/// <summary> /// enumerate the related Modified/Unchanged links for an added item /// </summary> /// <param name="entityDescriptor">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> protected IEnumerable<LinkDescriptor> RelatedLinks(EntityDescriptor entityDescriptor) { foreach (LinkDescriptor end in this.RequestInfo.EntityTracker.Links) { if (end.Source == entityDescriptor.Entity) { if (null != end.Target) { // null TargetResource is equivalent to Deleted EntityDescriptor target = this.RequestInfo.EntityTracker.GetEntityDescriptor(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(BatchWithSingleChangeset)) (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 (Util.IncludeLinkState(target.SaveResultWasProcessed) || ((0 == target.SaveResultWasProcessed) && Util.IncludeLinkState(target.State)) || ((null != target.Identity) && (target.ChangeOrder < entityDescriptor.ChangeOrder) && ((0 == target.SaveResultWasProcessed && EntityStates.Added == target.State) || (EntityStates.Added == target.SaveResultWasProcessed)))) { Debug.Assert(entityDescriptor.ChangeOrder < end.ChangeOrder, "saving is out of order"); yield return end; } } } } }
internal bool DetachResource(EntityDescriptor resource) { this.EnsureLinkBindings(); // 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> /// Create ODataRequestMessage for the given entity. /// </summary> /// <param name="entityDescriptor">resource</param> /// <returns>An instance of ODataRequestMessage for the given entity.</returns> protected ODataRequestMessageWrapper CreateRequest(EntityDescriptor entityDescriptor) { Debug.Assert(null != entityDescriptor, "null entityDescriptor"); Debug.Assert(entityDescriptor.State == EntityStates.Added || entityDescriptor.State == EntityStates.Deleted || entityDescriptor.State == EntityStates.Modified, "the entity must be in one of the 3 possible states"); EntityStates state = entityDescriptor.State; Uri requestUri = entityDescriptor.GetResourceUri(this.RequestInfo.BaseUriResolver, false /*queryLink*/); Debug.Assert(null != requestUri, "request uri is null"); Debug.Assert(requestUri.IsAbsoluteUri, "request uri is not absolute uri"); ClientEdmModel model = this.RequestInfo.Model; ClientTypeAnnotation clientType = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(entityDescriptor.Entity.GetType())); Version requestVersion = DetermineRequestVersion(clientType); string httpMethod = this.GetHttpMethod(state, ref requestVersion); HeaderCollection headers = new HeaderCollection(); // Set the content type if (EntityStates.Deleted != entityDescriptor.State) { this.RequestInfo.Context.Format.SetRequestContentTypeForEntry(headers); } // Set IfMatch (etag) header for update and delete requests if ((EntityStates.Deleted == state) || (EntityStates.Modified == state)) { string etag = entityDescriptor.GetLatestETag(); if (etag != null) { headers.SetHeader(XmlConstants.HttpRequestIfMatch, etag); } } // Set the prefer header if required ApplyPreferences(headers, httpMethod, this.RequestInfo.AddAndUpdateResponsePreference, ref requestVersion); // Set the request DSV and request MDSV headers headers.SetRequestVersion(requestVersion, this.RequestInfo.MaxProtocolVersionAsVersion); this.RequestInfo.Format.SetRequestAcceptHeader(headers); return this.CreateRequestMessage(httpMethod, requestUri, headers, this.RequestInfo.HttpStack, entityDescriptor, this.IsBatchRequest ? entityDescriptor.ChangeOrder.ToString(CultureInfo.InvariantCulture) : null); }
/// <summary>response materialization has an identity to attach to the inserted object</summary> /// <param name="entityDescriptorFromMaterializer">entity descriptor containing all the information about the entity from the response.</param> /// <param name="metadataMergeOption">mergeOption based on which EntityDescriptor will be merged.</param> internal override void AttachIdentity(EntityDescriptor entityDescriptorFromMaterializer, MergeOption metadataMergeOption) { // insert->unchanged Debug.Assert(entityDescriptorFromMaterializer != null, "entityDescriptorFromMaterializer != null"); this.EnsureIdentityToResource(); // resource.State == EntityState.Added or Unchanged for second pass of media link EntityDescriptor trackedEntityDescriptor = this.entityDescriptors[entityDescriptorFromMaterializer.Entity]; // make sure we got the right one - server could override identity and we may be tracking another one already. this.ValidateDuplicateIdentity(entityDescriptorFromMaterializer.Identity, trackedEntityDescriptor); this.DetachResourceIdentity(trackedEntityDescriptor); // 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 (trackedEntityDescriptor.IsDeepInsert) { LinkDescriptor end = this.bindings[trackedEntityDescriptor.GetRelatedEnd()]; end.State = EntityStates.Unchanged; } trackedEntityDescriptor.Identity = entityDescriptorFromMaterializer.Identity; // always attach the identity AtomMaterializerLog.MergeEntityDescriptorInfo(trackedEntityDescriptor, entityDescriptorFromMaterializer, true /*mergeInfo*/, metadataMergeOption); trackedEntityDescriptor.State = EntityStates.Unchanged; trackedEntityDescriptor.PropertiesToSerialize.Clear(); // 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[entityDescriptorFromMaterializer.Identity] = trackedEntityDescriptor; }
/// <summary> /// Get the server type name - either from the entity descriptor or using the type resolver. /// </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"); string serverTypeName = null; if (this.HasResolveName) { Type entityType = descriptor.Entity.GetType(); if (this.IsUserSuppliedResolver) { // User-supplied resolver, must call first serverTypeName = this.ResolveNameFromType(entityType) ?? descriptor.GetLatestServerTypeName(); } else { // V2+ codegen resolver, called last serverTypeName = descriptor.GetLatestServerTypeName() ?? this.ResolveNameFromType(entityType); } } else { serverTypeName = descriptor.GetLatestServerTypeName(); } return serverTypeName; }
/// <summary> /// Ensure an identity is unique and does not point to another resource /// </summary> /// <param name="identity">The identity</param> /// <param name="descriptor">The entity descriptor</param> private void ValidateDuplicateIdentity(Uri identity, EntityDescriptor descriptor) { EntityDescriptor trackedIdentity; if (this.identityToDescriptor.TryGetValue(identity, out trackedIdentity) && descriptor != trackedIdentity && trackedIdentity.State != EntityStates.Deleted && trackedIdentity.State != EntityStates.Detached) { // we checked the state because we do not remove the deleted/detached entity descriptor from the dictionary until we have finished processing all changes // So for instance if you delete one entity and add back one with the same ID, they will be a temporary conflict in the dictionary. throw Error.InvalidOperation(Strings.Context_DifferentEntityAlreadyContained); } }
/// <summary> /// Generate the batch request for all changes to save. /// </summary> /// <returns>Returns the instance of ODataRequestMessage containing all the headers and payload for the batch request.</returns> private ODataRequestMessageWrapper GenerateBatchRequest() { if (this.ChangedEntries.Count == 0 && this.Queries == null) { this.SetCompleted(); return(null); } ODataRequestMessageWrapper batchRequestMessage = this.CreateBatchRequest(); // we need to fire request after the headers have been written, but before we write the payload batchRequestMessage.FireSendingRequest2(null); using (ODataMessageWriter messageWriter = Serializer.CreateMessageWriter(batchRequestMessage, this.RequestInfo, false /*isParameterPayload*/)) { this.batchWriter = messageWriter.CreateODataBatchWriter(); this.batchWriter.WriteStartBatch(); if (this.Queries != null) { foreach (DataServiceRequest query in this.Queries) { QueryComponents queryComponents = query.QueryComponents(this.RequestInfo.Model); Uri requestUri = this.RequestInfo.BaseUriResolver.GetOrCreateAbsoluteUri(queryComponents.Uri); Debug.Assert(requestUri != null, "request uri is null"); Debug.Assert(requestUri.IsAbsoluteUri, "request uri is not absolute uri"); HeaderCollection headers = new HeaderCollection(); headers.SetRequestVersion(queryComponents.Version, this.RequestInfo.MaxProtocolVersionAsVersion); this.RequestInfo.Format.SetRequestAcceptHeaderForQuery(headers, queryComponents); ODataRequestMessageWrapper batchOperationRequestMessage = this.CreateRequestMessage(XmlConstants.HttpMethodGet, requestUri, headers, this.RequestInfo.HttpStack, null /*descriptor*/, null /*contentId*/); batchOperationRequestMessage.FireSendingEventHandlers(null /*descriptor*/); } } else if (0 < this.ChangedEntries.Count) { if (Util.IsBatchWithSingleChangeset(this.Options)) { this.batchWriter.WriteStartChangeset(); } var model = this.RequestInfo.Model; for (int i = 0; i < this.ChangedEntries.Count; ++i) { if (Util.IsBatchWithIndependentOperations(this.Options)) { this.batchWriter.WriteStartChangeset(); } Descriptor descriptor = this.ChangedEntries[i]; if (descriptor.ContentGeneratedForSave) { continue; } EntityDescriptor entityDescriptor = descriptor as EntityDescriptor; if (descriptor.DescriptorKind == DescriptorKind.Entity) { if (entityDescriptor.State == EntityStates.Added) { // We don't support adding MLE/MR in batch mode ClientTypeAnnotation type = model.GetClientTypeAnnotation(model.GetOrCreateEdmType(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); } } } else if (descriptor.DescriptorKind == DescriptorKind.NamedStream) { // Similar to MR, we do not support adding named streams in batch mode. throw Error.NotSupported(Strings.Context_BatchNotSupportedForNamedStreams); } ODataRequestMessageWrapper operationRequestMessage; if (descriptor.DescriptorKind == DescriptorKind.Entity) { operationRequestMessage = this.CreateRequest(entityDescriptor); } else { operationRequestMessage = this.CreateRequest((LinkDescriptor)descriptor); } // we need to fire request after the headers have been written, but before we write the payload operationRequestMessage.FireSendingRequest2(descriptor); this.CreateChangeData(i, operationRequestMessage); if (Util.IsBatchWithIndependentOperations(this.Options)) { this.batchWriter.WriteEndChangeset(); } } if (Util.IsBatchWithSingleChangeset(this.Options)) { this.batchWriter.WriteEndChangeset(); } } this.batchWriter.WriteEndBatch(); this.batchWriter.Flush(); } Debug.Assert(this.ChangedEntries.All(o => o.ContentGeneratedForSave), "didn't generated content for all entities/links"); return(batchRequestMessage); }