/// <summary>
        /// Tracks the AddRelatedObject method.
        /// </summary>
        /// <param name="data">The data service context data on which to apply state transition.</param>
        /// <param name="source">The source.</param>
        /// <param name="sourcePropertyName">Name of the source property.</param>
        /// <param name="target">The target.</param>
        public static void TrackAddRelatedObject(this DataServiceContextData data, object source, string sourcePropertyName, object target)
        {
            CheckEntityIsNotTracked(data, target);

            EntityDescriptorData sourceDescriptorData = GetTrackedEntityDescriptorData(data, source, "Cannot add link:", "source");
            CheckStateIsNot(EntityStates.Deleted, sourceDescriptorData, "Cannot add related object:", "source");

            EntityDescriptorData targetDescriptorData = data.CreateEntityDescriptorData(EntityStates.Added, data.GetNextChangeOrder(), target);

            data.CreateLinkDescriptorData(EntityStates.Added, uint.MaxValue, sourceDescriptorData, sourcePropertyName, targetDescriptorData);

            targetDescriptorData
                .SetParentForInsert(source)
                .SetParentPropertyForInsert(sourcePropertyName);
        }
        /// <summary>
        /// Tracks the AttachLink method.
        /// </summary>
        /// <param name="data">The data service context data on which to apply state transition.</param>
        /// <param name="source">The source.</param>
        /// <param name="sourcePropertyName">Name of the source property.</param>
        /// <param name="target">The target.</param>
        public static void TrackAttachLink(this DataServiceContextData data, object source, string sourcePropertyName, object target)
        {
            ExceptionUtilities.CheckArgumentNotNull(data, "data");

            LinkDescriptorData linkDescriptorData;
            if (data.TryGetLinkDescriptorData(source, sourcePropertyName, target, out linkDescriptorData))
            {
                throw new TaupoInvalidOperationException(
                    string.Format(CultureInfo.InvariantCulture, "The link already exists: {0}.", linkDescriptorData.ToString()));
            }

            EntityDescriptorData sourceDescriptorData = GetTrackedEntityDescriptorData(data, source, "Cannot attach link:", "source");
            CheckStateIsNot(EntityStates.Deleted, sourceDescriptorData, "Cannot attach link:", "source");
            CheckStateIsNot(EntityStates.Added, sourceDescriptorData, "Cannot attach link:", "source");

            EntityDescriptorData targetDescriptorData = null;
            if (target != null)
            {
                targetDescriptorData = GetTrackedEntityDescriptorData(data, target, "Cannot attach link:", "target");
                CheckStateIsNot(EntityStates.Deleted, targetDescriptorData, "Cannot attach link:", "target");
                CheckStateIsNot(EntityStates.Added, targetDescriptorData, "Cannot attach link:", "target");
            }

            data.CreateLinkDescriptorData(EntityStates.Unchanged, data.GetNextChangeOrder(), sourceDescriptorData, sourcePropertyName, targetDescriptorData);
        }
        /// <summary>
        /// Tracks the SetLink method.
        /// </summary>
        /// <param name="data">The data service context data on which to apply state transition.</param>
        /// <param name="source">The source.</param>
        /// <param name="sourcePropertyName">Name of the source property.</param>
        /// <param name="target">The target.</param>
        public static void TrackSetLink(this DataServiceContextData data, object source, string sourcePropertyName, object target)
        {
            ExceptionUtilities.CheckStringArgumentIsNotNullOrEmpty(sourcePropertyName, "sourcePropertyName");

            EntityDescriptorData sourceDescriptorData = GetTrackedEntityDescriptorData(data, source, "Cannot set link:", "source");
            CheckStateIsNot(EntityStates.Deleted, sourceDescriptorData, "Cannot set link:", "source");

            EntityDescriptorData targetDescriptorData = null;
            if (target != null)
            {
                targetDescriptorData = GetTrackedEntityDescriptorData(data, target, "Cannot set link:", "target");
                CheckStateIsNot(EntityStates.Deleted, sourceDescriptorData, "Cannot set link:", "target");
            }

            var relatedToSource = data.LinkDescriptorsData.Where(e => e.SourceDescriptor.Entity == source && e.SourcePropertyName == sourcePropertyName).ToList();

            if (relatedToSource.Count > 1)
            {
                throw new TaupoInvalidOperationException("Cannot set link: source contains multiple links for the property: " + sourcePropertyName);
            }

            LinkDescriptorData existingLinkDescriptorData = relatedToSource.FirstOrDefault();

            if (existingLinkDescriptorData == null)
            {
                data.CreateLinkDescriptorData(EntityStates.Modified, data.GetNextChangeOrder(), sourceDescriptorData, sourcePropertyName, targetDescriptorData);
            }
            else
            {
                if (existingLinkDescriptorData.State != EntityStates.Modified || existingLinkDescriptorData.TargetDescriptor != targetDescriptorData)
                {
                    data.ChangeStateAndChangeOrder(existingLinkDescriptorData, EntityStates.Modified, data.GetNextChangeOrder());
                }

                existingLinkDescriptorData.TargetDescriptor = targetDescriptorData;
            }
        }
        /// <summary>
        /// Tracks the DeleteLink method.
        /// </summary>
        /// <param name="data">The data service context data on which to apply state transition.</param>
        /// <param name="source">The source.</param>
        /// <param name="sourcePropertyName">Name of the source property.</param>
        /// <param name="target">The target.</param>
        public static void TrackDeleteLink(this DataServiceContextData data, object source, string sourcePropertyName, object target)
        {
            EntityDescriptorData sourceDescriptorData = GetTrackedEntityDescriptorData(data, source, "Cannot delete link:", "source");

            EntityDescriptorData targetDescriptorData = GetTrackedEntityDescriptorData(data, target, "Cannot delete link:", "target");

            LinkDescriptorData descriptorData;
            if (data.TryGetLinkDescriptorData(sourceDescriptorData, sourcePropertyName, targetDescriptorData, out descriptorData)
                && descriptorData.State == EntityStates.Added)
            {
                data.RemoveDescriptorData(descriptorData);
            }
            else
            {
                CheckStateIsNot(EntityStates.Added, sourceDescriptorData, "Cannot delete link:", "source");
                CheckStateIsNot(EntityStates.Added, targetDescriptorData, "Cannot delete link:", "target");

                if (descriptorData == null)
                {
                    data.CreateLinkDescriptorData(EntityStates.Deleted, data.GetNextChangeOrder(), sourceDescriptorData, sourcePropertyName, targetDescriptorData);
                }
                else if (descriptorData.State != EntityStates.Deleted)
                {
                    data.ChangeStateAndChangeOrder(descriptorData, EntityStates.Deleted, data.GetNextChangeOrder());
                }
            }
        }
        /// <summary>
        /// Extension method to either find or create a link descriptor with the given values
        /// </summary>
        /// <param name="contextData">The context data</param>
        /// <param name="sourceDescriptor">The source descriptor</param>
        /// <param name="propertyName">The property name</param>
        /// <param name="targetDescriptor">The target descriptor</param>
        /// <returns>A link descriptor with the given values</returns>
        public static LinkDescriptorData MaterializeLinkDescriptor(this DataServiceContextData contextData, EntityDescriptorData sourceDescriptor, string propertyName, EntityDescriptorData targetDescriptor)
        {
            var existingLink = contextData.LinkDescriptorsData.SingleOrDefault(l => l.SourceDescriptor == sourceDescriptor && l.SourcePropertyName == propertyName && l.TargetDescriptor == targetDescriptor);
            if (existingLink == null)
            {
                existingLink = contextData.CreateLinkDescriptorData(EntityStates.Unchanged, 0, sourceDescriptor, propertyName, targetDescriptor);
            }

            return existingLink;
        }