/// <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: successfully (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>use location from header to generate initial edit and identity</summary> /// <param name="entity">entity in added state</param> /// <param name="identity">identity as specified in the response header - location header or OData-EntityId header.</param> /// <param name="editLink">editlink as specified in the response header - location header.</param> internal void AttachLocation(object entity, Uri identity, Uri editLink) { Debug.Assert(entity != null, "null != entity"); Debug.Assert(editLink != null, "editLink != null"); this.EnsureIdentityToResource(); // resource.State == EntityState.Added or Unchanged for second pass of media link EntityDescriptor resource = this.entityDescriptors[entity]; // make sure we got the right one - server could override identity and we may be tracking another one already. this.ValidateDuplicateIdentity(identity, resource); 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: successfully 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>Detach existing link</summary> /// <param name="existingLink">link to detach</param> /// <param name="targetDelete">true if target is being deleted, false otherwise</param> internal override 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.GetEntityDescriptor(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.TryRemoveLinkDescriptor(existingLink)) { // this link may have been previously detached by a detaching entity existingLink.State = EntityStates.Detached; } }
/// <summary> /// Invoke this method to notify the log that a link was removed /// from a collection. /// </summary> /// <param name="source"> /// Instance with the collection from which <paramref name="target"/> /// was removed. /// </param> /// <param name="propertyName">Property name for collection.</param> /// <param name="target">Object which was removed.</param> internal void RemovedLink(MaterializerEntry source, string propertyName, object target) { Debug.Assert(source.Entry != null || source.ForLoadProperty, "source != null || source.ForLoadProperty"); Debug.Assert(propertyName != null, "propertyName != null"); if (IsEntity(source) && IsEntity(target, this.clientEdmModel)) { Debug.Assert(this.Tracking, "this.Tracking -- otherwise there's an 'if' missing (it happens to be that the assert holds for all current callers"); LinkDescriptor item = new LinkDescriptor(source.ResolvedObject, propertyName, target, EntityStates.Detached); this.links.Add(item); } }
/// <summary> /// Add the given link to the link descriptor collection /// </summary> /// <param name="linkDescriptor">link descriptor to add</param> /// <exception cref="InvalidOperationException">throws argument exception if the link already exists</exception> internal void AddLink(LinkDescriptor linkDescriptor) { Debug.Assert(linkDescriptor != null, "linkDescriptor != null"); try { this.EnsureLinkBindings(); this.bindings.Add(linkDescriptor, linkDescriptor); } catch (ArgumentException) { throw Error.InvalidOperation(Strings.Context_RelationAlreadyContained); } }
/// <summary> /// attach the link with the given source, sourceProperty and target. /// </summary> /// <param name="source">source entity of the link.</param> /// <param name="sourceProperty">name of the property on the source entity.</param> /// <param name="target">target entity of the link.</param> /// <param name="linkMerge">merge option to be used to merge the link if there is an existing link.</param> internal override void AttachLink(object source, string sourceProperty, object target, MergeOption linkMerge) { LinkDescriptor relation = new LinkDescriptor(source, sourceProperty, target, this.model); LinkDescriptor existing = this.TryGetLinkDescriptor(source, sourceProperty, target); if (existing != null) { switch (linkMerge) { case MergeOption.AppendOnly: break; case MergeOption.OverwriteChanges: relation = existing; break; case MergeOption.PreserveChanges: if ((existing.State == EntityStates.Added) || (existing.State == EntityStates.Unchanged) || (existing.State == EntityStates.Modified && existing.Target != null)) { relation = existing; } break; case MergeOption.NoTracking: // public API point should throw if link exists throw Error.InvalidOperation(Strings.Context_RelationAlreadyContained); } } else { if (this.model.GetClientTypeAnnotation(this.model.GetOrCreateEdmType(source.GetType())).GetProperty(sourceProperty, UndeclaredPropertyBehavior.ThrowException).IsEntityCollection || ((existing = this.DetachReferenceLink(source, sourceProperty, target, linkMerge)) == null)) { this.AddLink(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> /// Invoke this method to notify the log that a new link was /// added to a collection. /// </summary> /// <param name="source"> /// Instance with the collection to which <paramref name="target"/> /// was added. /// </param> /// <param name="propertyName">Property name for collection.</param> /// <param name="target">Object which was added.</param> internal void AddedLink(MaterializerEntry source, string propertyName, object target) { Debug.Assert(source.Entry != null || source.ForLoadProperty, "source != null || source.ForLoadProperty"); Debug.Assert(propertyName != null, "propertyName != null"); if (!this.Tracking) { return; } if (IsEntity(source) && IsEntity(target, this.clientEdmModel)) { LinkDescriptor item = new LinkDescriptor(source.ResolvedObject, propertyName, target, EntityStates.Added); this.links.Add(item); } }
/// <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> internal LinkDescriptor DetachReferenceLink(object source, string sourceProperty, object target, MergeOption linkMerge) { Debug.Assert(sourceProperty.IndexOf('/') == -1, "sourceProperty.IndexOf('/') == -1"); LinkDescriptor existing = this.GetLinks(source, sourceProperty).FirstOrDefault(); if (existing != null) { 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.Links.Any(o => (o.Source == source) && (o.SourceProperty == sourceProperty)), "only expecting one"); } return(null); }
/// <summary> /// Writes an entity reference link. /// </summary> /// <param name="binding">The link descriptor.</param> /// <param name="requestMessage">The request message used for writing the payload.</param> internal void WriteEntityReferenceLink(LinkDescriptor binding, ODataRequestMessageWrapper requestMessage) #endif { using (ODataMessageWriter messageWriter = Serializer.CreateMessageWriter(requestMessage, this.requestInfo, false /*isParameterPayload*/)) { EntityDescriptor targetResource = this.requestInfo.EntityTracker.GetEntityDescriptor(binding.Target); Uri targetReferenceLink = targetResource.GetLatestIdentity(); if (targetReferenceLink == null) { #if DEBUG Debug.Assert(isBatch, "we should be cross-referencing entities only in batch scenarios"); #endif targetReferenceLink = UriUtil.CreateUri("$" + targetResource.ChangeOrder.ToString(CultureInfo.InvariantCulture), UriKind.Relative); } ODataEntityReferenceLink referenceLink = new ODataEntityReferenceLink(); referenceLink.Url = targetReferenceLink; messageWriter.WriteEntityReferenceLink(referenceLink); } }
/// <summary>Applies all accumulated changes to the associated data context.</summary> /// <remarks>The log should be cleared after this method successfully executed.</remarks> internal void ApplyToContext() { if (!this.Tracking) { return; } foreach (KeyValuePair <Uri, ODataResource> entity in this.identityStack) { // Try to attach the entity descriptor got from materializer, if one already exists, get the existing reference instead. MaterializerEntry entry = MaterializerEntry.GetEntry(entity.Value); bool mergeEntityDescriptorInfo = entry.CreatedByMaterializer || entry.ResolvedObject == this.insertRefreshObject || entry.ShouldUpdateFromPayload; // Whenever we merge the data, only at those times will be merge the links also EntityDescriptor descriptor = this.entityTracker.InternalAttachEntityDescriptor(entry.EntityDescriptor, false /*failIfDuplicated*/); AtomMaterializerLog.MergeEntityDescriptorInfo(descriptor, entry.EntityDescriptor, mergeEntityDescriptorInfo, this.mergeOption); if (mergeEntityDescriptorInfo) { // In AtomMaterializer.TryResolveFromContext, we set AtomEntry.ShouldUpdateFromPayload to true // when even MergeOption is PreserveChanges and entityState is Deleted. But in that case, we cannot // set the entity state to Unchanged, hence need to workaround that one scenario if (this.mergeOption != MergeOption.PreserveChanges || descriptor.State != EntityStates.Deleted) { // we should always reset descriptor's state to Unchanged (old v1 behavior) descriptor.State = EntityStates.Unchanged; descriptor.PropertiesToSerialize.Clear(); } } } foreach (LinkDescriptor link in this.links) { if (EntityStates.Added == link.State) { // Added implies collection this.entityTracker.AttachLink(link.Source, link.SourceProperty, link.Target, this.mergeOption); } else if (EntityStates.Modified == link.State) { // Modified implies reference object target = link.Target; if (MergeOption.PreserveChanges == this.mergeOption) { // GetLinks looks up the existing link using just the SourceProperty, the declaring server type name is not significant here. LinkDescriptor end = this.entityTracker.GetLinks(link.Source, link.SourceProperty).SingleOrDefault(); if (end != null && end.Target == null) { // leave the SetLink(link.Source, link.SourceProperty, null) continue; } if ((target != null) && (this.entityTracker.GetEntityDescriptor(target).State == EntityStates.Deleted) || (EntityStates.Deleted == this.entityTracker.GetEntityDescriptor(link.Source).State)) { target = null; } } this.entityTracker.AttachLink(link.Source, link.SourceProperty, target, this.mergeOption); } else { // detach link Debug.Assert(EntityStates.Detached == link.State, "not detached link"); this.entityTracker.DetachExistingLink(link, false); } } }
/// <summary>is the entity the same as the source or target entity</summary> /// <param name="related">related end</param> /// <returns>true if same as source or target entity</returns> internal bool IsRelatedEntity(LinkDescriptor related) { return((this.entity == related.Source) || (this.entity == related.Target)); }
/// <summary>Detach existing link</summary> /// <param name="existingLink">link to detach</param> /// <param name="targetDelete">true if target is being deleted, false otherwise</param> internal abstract void DetachExistingLink(LinkDescriptor existingLink, bool targetDelete);
/// <summary> /// Remove the link from the list of tracked link descriptors. /// </summary> /// <param name="linkDescriptor">link to be removed.</param> /// <returns>true if the link was tracked and now removed, otherwise returns false.</returns> internal bool TryRemoveLinkDescriptor(LinkDescriptor linkDescriptor) { this.EnsureLinkBindings(); return(this.bindings.Remove(linkDescriptor)); }
/// <summary> /// Writes an entity reference link. /// </summary> /// <param name="binding">The link descriptor.</param> /// <param name="requestMessage">The request message used for writing the payload.</param> /// <param name="isBatch">True if batch, false otherwise.</param> internal void WriteEntityReferenceLink(LinkDescriptor binding, ODataRequestMessageWrapper requestMessage, bool isBatch)