Exemplo n.º 1
0
        /// <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;
        }
Exemplo n.º 2
0
        /// <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;
        }
Exemplo n.º 3
0
        /// <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;
            }
        }
Exemplo n.º 4
0
        /// <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);
            }
        }
Exemplo n.º 5
0
 /// <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);
     }
 }
Exemplo n.º 6
0
        /// <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;
        }
Exemplo n.º 7
0
        /// <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);
            }
        }
Exemplo n.º 8
0
        /// <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);
        }
Exemplo n.º 9
0
        /// <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);
            }
        }
Exemplo n.º 10
0
        /// <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));
 }
Exemplo n.º 12
0
 /// <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);
Exemplo n.º 13
0
 /// <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));
 }
Exemplo n.º 14
0
 /// <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)