/// <summary> /// Reads a property value starting on a complex value. /// </summary> /// <param name="complexValue">The complex value to start with.</param> /// <param name="complexPropertySegment">The EPM source path segment which points to the <paramref name="complexValue"/>.</param> /// <param name="epmValueCache">The EPM value cache to use.</param> /// <param name="sourceSegmentIndex">The index in the property value path to start with.</param> /// <param name="resourceType">The resource type of the complex value.</param> /// <param name="metadata">The metadata provider to use.</param> /// <param name="nullOnParentProperty">true if the value of the property is null because one of its parent properties was null, in this case /// the return value of the method is always null. false if the value of the property is the actual property value which may or may not be null.</param> /// <returns>The value of the property (may be null), or null if the property itself was not found due to one of its parent properties being null.</returns> private object ReadComplexPropertyValue( ODataComplexValue complexValue, EpmSourcePathSegment complexPropertySegment, EpmValueCache epmValueCache, int sourceSegmentIndex, ResourceType resourceType, DataServiceMetadataProviderWrapper metadata, out bool nullOnParentProperty) { Debug.Assert(this.propertyValuePath != null, "The propertyValuePath should have been initialized by now."); Debug.Assert(this.propertyValuePath.Length > sourceSegmentIndex, "The propertyValuePath must be at least as long as the source segment index."); Debug.Assert(epmValueCache != null, "epmValueCache != null"); Debug.Assert(sourceSegmentIndex >= 0, "sourceSegmentIndex >= 0"); Debug.Assert(resourceType != null, "resourceType != null"); if (complexValue == null) { nullOnParentProperty = true; return(null); } return(this.ReadPropertyValue( EpmValueCache.GetComplexValueProperties(epmValueCache, complexPropertySegment, complexValue, false), sourceSegmentIndex, resourceType, metadata, epmValueCache, out nullOnParentProperty)); }
/// <summary> /// Writes out the value of a complex property. /// </summary> /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="complexValue">The complex value to write.</param> /// <param name="metadataType">The metadata type for the complex value.</param> /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param> /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param> /// <param name="version">The protocol version used for writing.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmSourcePathSegment">The EPM source path segment which points to the property we're writing. (can be null)</param> internal static void WriteComplexValue( XmlWriter writer, DataServiceMetadataProviderWrapper metadata, ODataComplexValue complexValue, ResourceType metadataType, bool isOpenPropertyType, bool isWritingCollection, ODataVersion version, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(complexValue != null, "complexValue != null"); string typeName = complexValue.TypeName; // resolve the type name to the resource type; if no type name is specified we will use the // type inferred from metadata ResourceType complexValueType = MetadataUtils.ResolveTypeName(metadata, metadataType, ref typeName, ResourceTypeKind.ComplexType, isOpenPropertyType); if (typeName != null) { WritePropertyTypeAttribute(writer, typeName); } WriteProperties( writer, metadata, complexValueType, EpmValueCache.GetComplexValueProperties(epmValueCache, epmSourcePathSegment, complexValue, true), version, isWritingCollection, epmValueCache, epmSourcePathSegment); }
/// <summary> /// Constructor which creates an empty root. /// </summary> /// <param name="epmTargetTree">Target xml tree</param> internal EpmSourceTree(EpmTargetTree epmTargetTree) { DebugUtils.CheckNoExternalCallers(); this.root = new EpmSourcePathSegment(); this.epmTargetTree = epmTargetTree; }
/// <summary> /// Returns the items for the specified multivalue. /// </summary> /// <param name="sourcePathSegment">The source path segment for the property which has this multivalue.</param> /// <param name="multiValue">The multivalue to get the items for.</param> /// <param name="writingContent">true if we're writing entry content or false when writing out-of-content EPM.</param> /// <returns>The items enumeration for the multivalue.</returns> private IEnumerable GetMultiValueItems(EpmSourcePathSegment sourcePathSegment, ODataMultiValue multiValue, bool writingContent) { Debug.Assert(writingContent || sourcePathSegment != null, "sourcePathSegment must be specified when writing out-of-content."); Debug.Assert(multiValue != null, "multiValue != null"); // If we're writing into content we don't want to populate the cache if it's not already populated. // The goal is to behave the same with and without EPM. if (writingContent && this.epmValuesCache == null) { return(multiValue.Items); } object cachedItemsValue; if (this.epmValuesCache != null && this.epmValuesCache.TryGetValue(sourcePathSegment, out cachedItemsValue)) { Debug.Assert(cachedItemsValue is List <object>, "The cached value for multi value must be a List of object"); return((IEnumerable)cachedItemsValue); } IEnumerable items = multiValue.Items; List <object> cachedItems = null; if (items != null) { cachedItems = new List <object>(); foreach (object item in items) { // If the value is a complex value, store it as EpmMultiValueItemCache instance, so that we have a place // to cache the enumeration of properties on that complex value (and possible other nested complex/multi values). if (item is ODataComplexValue) { cachedItems.Add(new EpmMultiValueItemCache(item)); } else { // Otherwise it should be a primitive value and thus we can just cache the value itself as it won't have any children cachedItems.Add(item); } } } if (this.epmValuesCache == null) { this.epmValuesCache = new Dictionary <EpmSourcePathSegment, object>(ReferenceEqualityComparer <EpmSourcePathSegment> .Instance); } this.epmValuesCache.Add(sourcePathSegment, cachedItems); return(cachedItems); }
/// <summary> /// Determines if the property with the specified value should be written into content or not. /// </summary> /// <param name="propertyValue">The property value to write.</param> /// <param name="epmSourcePathSegment">The EPM source path segment for the property being written.</param> /// <param name="version">The version of the protocol being used for the response.</param> /// <returns>true if the property should be written into content, or false otherwise</returns> private static bool ShouldWritePropertyInContent(object propertyValue, EpmSourcePathSegment epmSourcePathSegment, ODataVersion version) { if (epmSourcePathSegment == null) { return(true); } EntityPropertyMappingInfo epmInfo = epmSourcePathSegment.EpmInfo; if (epmInfo == null) { return(true); } EntityPropertyMappingAttribute epmAttribute = epmInfo.Attribute; Debug.Assert(epmAttribute != null, "Attribute should always be initialized for EpmInfo."); if (version <= ODataVersion.V2) { // In V2 and lower we sometimes write properties into content even if asked not to. // If the property value is null, we always write into content if (propertyValue == null) { return(true); } string stringPropertyValue = propertyValue as string; if (stringPropertyValue != null && stringPropertyValue.Length == 0) { // If the property value is an empty string and we should be writing it into an ATOM element which does not allow empty string // we write it into content as well. switch (epmAttribute.TargetSyndicationItem) { case SyndicationItemProperty.AuthorEmail: case SyndicationItemProperty.AuthorUri: case SyndicationItemProperty.ContributorEmail: case SyndicationItemProperty.ContributorUri: return(true); default: break; } } } return(epmAttribute.KeepInContent); }
/// <summary> /// Validates the specified segment and all its subsegments. /// </summary> /// <param name="pathSegment">The path segment to validate.</param> /// <param name="resourceType">The resource type of the property represented by this segment (null for open properties).</param> private static void Validate(EpmSourcePathSegment pathSegment, ResourceType resourceType) { Debug.Assert(pathSegment != null, "pathSegment != null"); foreach (EpmSourcePathSegment subSegment in pathSegment.SubProperties) { bool isMultiValueProperty; ResourceType subSegmentResourceType = GetPropertyType(resourceType, subSegment.PropertyName, out isMultiValueProperty); if (isMultiValueProperty) { ValidateMultiValueSegment(subSegment.EpmInfo, subSegment, subSegmentResourceType); } else { Validate(subSegment, subSegmentResourceType); } } }
/// <summary> /// Returns the properties for the specified complex value. /// </summary> /// <param name="epmValueCache">The EPM value cache to use (can be null).</param> /// <param name="sourcePathSegment">The source path segment for the property which has this complex value.</param> /// <param name="complexValue">The complex value to get the properties for.</param> /// <param name="writingContent">If we're writing content of an entry or not.</param> /// <returns>The properties enumeration for the complex value.</returns> internal static IEnumerable<ODataProperty> GetComplexValueProperties( EpmValueCache epmValueCache, EpmSourcePathSegment sourcePathSegment, ODataComplexValue complexValue, bool writingContent) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(complexValue != null, "complexValue != null"); Debug.Assert(writingContent || epmValueCache != null, "If we're not writing content, then the EPM value cache must exist."); if (epmValueCache == null) { return complexValue.Properties; } else { return epmValueCache.GetComplexValueProperties(sourcePathSegment, complexValue, writingContent); } }
/// <summary> /// Returns the items for the specified multivalue. /// </summary> /// <param name="epmValueCache">The EPM value cache to use (can be null).</param> /// <param name="sourcePathSegment">The source path segment for the property which has this multivalue.</param> /// <param name="multiValue">The multivalue to get the items for.</param> /// <param name="writingContent">If we're writing content of an entry or not.</param> /// <returns>The items enumeration for the multivalue.</returns> internal static IEnumerable GetMultiValueItems( EpmValueCache epmValueCache, EpmSourcePathSegment sourcePathSegment, ODataMultiValue multiValue, bool writingContent) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(multiValue != null, "multiValue != null"); Debug.Assert(writingContent || epmValueCache != null, "If we're not writing content, then the EPM value cache must exist."); if (epmValueCache == null) { return(multiValue.Items); } else { return(epmValueCache.GetMultiValueItems(sourcePathSegment, multiValue, writingContent)); } }
/// <summary> /// Returns the items for the specified multivalue. /// </summary> /// <param name="epmValueCache">The EPM value cache to use (can be null).</param> /// <param name="sourcePathSegment">The source path segment for the property which has this multivalue.</param> /// <param name="multiValue">The multivalue to get the items for.</param> /// <param name="writingContent">If we're writing content of an entry or not.</param> /// <returns>The items enumeration for the multivalue.</returns> internal static IEnumerable GetMultiValueItems( EpmValueCache epmValueCache, EpmSourcePathSegment sourcePathSegment, ODataMultiValue multiValue, bool writingContent) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(multiValue != null, "multiValue != null"); Debug.Assert(writingContent || epmValueCache != null, "If we're not writing content, then the EPM value cache must exist."); if (epmValueCache == null) { return multiValue.Items; } else { return epmValueCache.GetMultiValueItems(sourcePathSegment, multiValue, writingContent); } }
/// <summary> /// Returns the properties for the specified complex value. /// </summary> /// <param name="epmValueCache">The EPM value cache to use (can be null).</param> /// <param name="sourcePathSegment">The source path segment for the property which has this complex value.</param> /// <param name="complexValue">The complex value to get the properties for.</param> /// <param name="writingContent">If we're writing content of an entry or not.</param> /// <returns>The properties enumeration for the complex value.</returns> internal static IEnumerable <ODataProperty> GetComplexValueProperties( EpmValueCache epmValueCache, EpmSourcePathSegment sourcePathSegment, ODataComplexValue complexValue, bool writingContent) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(complexValue != null, "complexValue != null"); Debug.Assert(writingContent || epmValueCache != null, "If we're not writing content, then the EPM value cache must exist."); if (epmValueCache == null) { return(complexValue.Properties); } else { return(epmValueCache.GetComplexValueProperties(sourcePathSegment, complexValue, writingContent)); } }
/// <summary> /// Write the given collection of properties. /// </summary> /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="owningType">The <see cref="ResourceType"/> of the entry (or null if not metadata is available).</param> /// <param name="cachedProperties">Collection of cached properties for the entry.</param> /// <param name="version">The protocol version used for writing.</param> /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmSourcePathSegment">The EPM source path segment which points to the property which sub-properites we're writing. (can be null)</param> internal static void WriteProperties( XmlWriter writer, DataServiceMetadataProviderWrapper metadata, ResourceType owningType, IEnumerable <ODataProperty> cachedProperties, ODataVersion version, bool isWritingCollection, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(writer != null, "writer != null"); if (cachedProperties == null) { return; } foreach (ODataProperty property in cachedProperties) { WriteProperty(writer, metadata, property, owningType, version, false, isWritingCollection, epmValueCache, epmSourcePathSegment); } }
/// <summary> /// Returns the properties for the specified complex value. /// </summary> /// <param name="sourcePathSegment">The source path segment for the property which has this complex value.</param> /// <param name="complexValue">The complex value to get the properties for.</param> /// <param name="writingContent">true if we're writing entry content or false when writing out-of-content EPM.</param> /// <returns>The properties enumeration for the complex value.</returns> private IEnumerable <ODataProperty> GetComplexValueProperties(EpmSourcePathSegment sourcePathSegment, ODataComplexValue complexValue, bool writingContent) { Debug.Assert(writingContent || sourcePathSegment != null, "sourcePathSegment must be specified when writing out-of-content."); Debug.Assert(complexValue != null, "complexValue != null"); // If we're writing into content we don't want to populate the cache if it's not already populated. // The goal is to behave the same with and without EPM. if (writingContent && this.epmValuesCache == null) { return(complexValue.Properties); } object cachedPropertiesValue; if (this.epmValuesCache != null && this.epmValuesCache.TryGetValue(sourcePathSegment, out cachedPropertiesValue)) { Debug.Assert(cachedPropertiesValue is List <ODataProperty>, "The cached value for complex type must be a List of ODataProperty"); return((IEnumerable <ODataProperty>)cachedPropertiesValue); } IEnumerable <ODataProperty> properties = complexValue.Properties; List <ODataProperty> cachedProperties = null; if (properties != null) { cachedProperties = new List <ODataProperty>(properties); } if (this.epmValuesCache == null) { this.epmValuesCache = new Dictionary <EpmSourcePathSegment, object>(ReferenceEqualityComparer <EpmSourcePathSegment> .Instance); } this.epmValuesCache.Add(sourcePathSegment, cachedProperties); return(cachedProperties); }
/// <summary> /// Returns the items for the specified multivalue. /// </summary> /// <param name="sourcePathSegment">The source path segment for the property which has this multivalue.</param> /// <param name="multiValue">The multivalue to get the items for.</param> /// <param name="writingContent">true if we're writing entry content or false when writing out-of-content EPM.</param> /// <returns>The items enumeration for the multivalue.</returns> private IEnumerable GetMultiValueItems(EpmSourcePathSegment sourcePathSegment, ODataMultiValue multiValue, bool writingContent) { Debug.Assert(writingContent || sourcePathSegment != null, "sourcePathSegment must be specified when writing out-of-content."); Debug.Assert(multiValue != null, "multiValue != null"); // If we're writing into content we don't want to populate the cache if it's not already populated. // The goal is to behave the same with and without EPM. if (writingContent && this.epmValuesCache == null) { return multiValue.Items; } object cachedItemsValue; if (this.epmValuesCache != null && this.epmValuesCache.TryGetValue(sourcePathSegment, out cachedItemsValue)) { Debug.Assert(cachedItemsValue is List<object>, "The cached value for multi value must be a List of object"); return (IEnumerable)cachedItemsValue; } IEnumerable items = multiValue.Items; List<object> cachedItems = null; if (items != null) { cachedItems = new List<object>(); foreach (object item in items) { // If the value is a complex value, store it as EpmMultiValueItemCache instance, so that we have a place // to cache the enumeration of properties on that complex value (and possible other nested complex/multi values). if (item is ODataComplexValue) { cachedItems.Add(new EpmMultiValueItemCache(item)); } else { // Otherwise it should be a primitive value and thus we can just cache the value itself as it won't have any children cachedItems.Add(item); } } } if (this.epmValuesCache == null) { this.epmValuesCache = new Dictionary<EpmSourcePathSegment, object>(ReferenceEqualityComparer<EpmSourcePathSegment>.Instance); } this.epmValuesCache.Add(sourcePathSegment, cachedItems); return cachedItems; }
/// <summary> /// Returns the properties for the specified complex value. /// </summary> /// <param name="sourcePathSegment">The source path segment for the property which has this complex value.</param> /// <param name="complexValue">The complex value to get the properties for.</param> /// <param name="writingContent">true if we're writing entry content or false when writing out-of-content EPM.</param> /// <returns>The properties enumeration for the complex value.</returns> private IEnumerable<ODataProperty> GetComplexValueProperties(EpmSourcePathSegment sourcePathSegment, ODataComplexValue complexValue, bool writingContent) { Debug.Assert(writingContent || sourcePathSegment != null, "sourcePathSegment must be specified when writing out-of-content."); Debug.Assert(complexValue != null, "complexValue != null"); // If we're writing into content we don't want to populate the cache if it's not already populated. // The goal is to behave the same with and without EPM. if (writingContent && this.epmValuesCache == null) { return complexValue.Properties; } object cachedPropertiesValue; if (this.epmValuesCache != null && this.epmValuesCache.TryGetValue(sourcePathSegment, out cachedPropertiesValue)) { Debug.Assert(cachedPropertiesValue is List<ODataProperty>, "The cached value for complex type must be a List of ODataProperty"); return (IEnumerable<ODataProperty>)cachedPropertiesValue; } IEnumerable<ODataProperty> properties = complexValue.Properties; List<ODataProperty> cachedProperties = null; if (properties != null) { cachedProperties = new List<ODataProperty>(properties); } if (this.epmValuesCache == null) { this.epmValuesCache = new Dictionary<EpmSourcePathSegment, object>(ReferenceEqualityComparer<EpmSourcePathSegment>.Instance); } this.epmValuesCache.Add(sourcePathSegment, cachedProperties); return cachedProperties; }
internal void Add(EntityPropertyMappingInfo epmInfo) { DebugUtils.CheckNoExternalCallers(); List <EpmSourcePathSegment> pathToCurrentSegment = new List <EpmSourcePathSegment>(); EpmSourcePathSegment currentSourceSegment = this.Root; EpmSourcePathSegment foundSourceSegment = null; ResourceType currentType = epmInfo.ActualPropertyType; EpmSourcePathSegment multiValuePropertySegment = null; Debug.Assert(!string.IsNullOrEmpty(epmInfo.Attribute.SourcePath), "Invalid source path"); string[] propertyPath = epmInfo.Attribute.SourcePath.Split('/'); if (epmInfo.CriteriaValue != null) { ValidateConditionalMapping(epmInfo); } Debug.Assert(propertyPath.Length > 0, "Must have been validated during EntityPropertyMappingAttribute construction"); for (int sourcePropertyIndex = 0; sourcePropertyIndex < propertyPath.Length; sourcePropertyIndex++) { string propertyName = propertyPath[sourcePropertyIndex]; if (propertyName.Length == 0) { throw new ODataException(Strings.EpmSourceTree_InvalidSourcePath(epmInfo.DefiningType.Name, epmInfo.Attribute.SourcePath)); } bool isMultiValueProperty; currentType = GetPropertyType(currentType, propertyName, out isMultiValueProperty); foundSourceSegment = currentSourceSegment.SubProperties.SingleOrDefault(e => e.PropertyName == propertyName); if (foundSourceSegment != null) { currentSourceSegment = foundSourceSegment; } else { EpmSourcePathSegment newSourceSegment = new EpmSourcePathSegment(propertyName); currentSourceSegment.SubProperties.Add(newSourceSegment); currentSourceSegment = newSourceSegment; } pathToCurrentSegment.Add(currentSourceSegment); if (isMultiValueProperty) { Debug.Assert( currentSourceSegment.EpmInfo == null || currentSourceSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty, "MultiValue property must have EpmInfo marked as MultiValue or none at all."); Debug.Assert( currentSourceSegment.EpmInfo != null || foundSourceSegment == null, "The only way to get a propety without info attached yet on a MultiValue property is when we just created it."); if (multiValuePropertySegment != null) { // Nested MultiValue - not allowed to be mapped throw new ODataException(Strings.EpmSourceTree_NestedMultiValue( multiValuePropertySegment.EpmInfo.Attribute.SourcePath, multiValuePropertySegment.EpmInfo.DefiningType.Name, epmInfo.Attribute.SourcePath)); } multiValuePropertySegment = currentSourceSegment; // MultiValue properties can only be mapped to a top-level element, so we can blindly use the first part // of the target path as the target path for the MultiValue property. Debug.Assert(!string.IsNullOrEmpty(epmInfo.Attribute.TargetPath), "Target path should have been checked by the EpmAttribute constructor."); string multiValuePropertyTargetPath = epmInfo.Attribute.TargetPath.Split('/')[0]; if (currentSourceSegment.EpmInfo == null || !currentSourceSegment.EpmInfo.DefiningTypesAreEqual(epmInfo)) { if (currentSourceSegment.EpmInfo != null) { Debug.Assert(!currentSourceSegment.EpmInfo.DefiningTypesAreEqual(epmInfo), "Just verifying that the ifs are correct."); Debug.Assert(foundSourceSegment != null, "Can't have existing node with EpmInfo on it here and not found it before."); // If the MultiValue property we're trying to add is from a different type than the one we already have // just overwrite the epm info. This means that the derived type defines a different mapping for the property than the base type. // We also need to walk all the children of the base type mapping here and remove them from the target tree // since we're overriding the entire MultiValue property mapping (not just one item property) // Note that for MultiValue properties, removing the MultiValue property node itself will remove all the MultiValue item properties as well // as they have to be children of the MultiValue property node in the target tree. this.epmTargetTree.Remove(foundSourceSegment.EpmInfo); // We also need to remove all children of the MultiValue property node from the source tree // as the derived type is overriding it completely. If the derived type doesn't override all of the properties // we should fail as if it did't map all of them. currentSourceSegment.SubProperties.Clear(); } // This is the first time we've seen this MultiValue property mapped // (on this type, we might have seen it on the base type, but that has been removed) // The source path is the path we have so far for the property string multiValuePropertySourcePath = string.Join("/", propertyPath, 0, sourcePropertyIndex + 1); if (!epmInfo.IsSyndicationMapping) { // Custom EPM for MultiValue is not supported yet // Note: This has already been implemented, but then removed from the code. To see what it takes to implement this // please see the change which adds this comment into the sources. throw new ODataException(Strings.EpmSourceTree_MultiValueNotAllowedInCustomMapping( multiValuePropertySourcePath, epmInfo.DefiningType.Name)); } // Create a new EPM attribute to represent the MultiValue property mapping // note that this attribute is basically implicitly declared whenever the user declares EPM attribute // for a property from some MultiValue property. (the declaration happens right here) EntityPropertyMappingAttribute multiValueEpmAttribute = new EntityPropertyMappingAttribute( multiValuePropertySourcePath, multiValuePropertyTargetPath, epmInfo.Attribute.TargetNamespacePrefix, epmInfo.Attribute.TargetNamespaceUri, epmInfo.Attribute.KeepInContent); // Create a special EpmInfo from the above special attribute which represents just the MultiValue property itself EntityPropertyMappingInfo multiValueEpmInfo = new EntityPropertyMappingInfo( multiValueEpmAttribute, epmInfo.DefiningType, epmInfo.ActualPropertyType); multiValueEpmInfo.MultiValueStatus = EntityPropertyMappingMultiValueStatus.MultiValueProperty; multiValueEpmInfo.MultiValueItemType = currentType; // We need to mark the info as syndication/custom mapping explicitely since the attribute we create (From which it's infered) is always custom mapping Debug.Assert(epmInfo.IsSyndicationMapping, "Only syndication mapping is allowed for MultiValue properties."); multiValueEpmInfo.SetMultiValuePropertySyndicationMapping(); multiValueEpmInfo.SetPropertyValuePath(pathToCurrentSegment.ToArray()); multiValueEpmInfo.Criteria = epmInfo.Criteria; multiValueEpmInfo.CriteriaValue = epmInfo.CriteriaValue; // Now associate the current source tree segment with the new info object (or override the one from base) currentSourceSegment.EpmInfo = multiValueEpmInfo; // And add the new info to the target tree this.epmTargetTree.Add(multiValueEpmInfo); // And continue with the walk of the source path. // This means that the EPM attribute specified as the input to this method is still to be added // It might be added to the source tree if the path is longer (property on an item in the MultiValue property of complex types) // or it might not be added to the source tree if this segment is the last (MultiValue property of primitive types). // In any case it will be added to the target tree (so even if the MultiValue property itself is mapped to the top-level element only // the items in the MultiValue property can be mapped to child element/attribute of that top-level element). } else { Debug.Assert(currentSourceSegment.EpmInfo.DefiningTypesAreEqual(epmInfo), "The condition in the surrounding if is broken."); // We have already found a MultiValue property mapped from this source node. // If it's on the same defining type we need to make sure that it's the same MultiValue property being mapped // First verify that the mapping for the other property has the same top-level element for the MultiValue property // since we only allow properties from one MultiValue property to be mapped to the same top-level element if (multiValuePropertyTargetPath != currentSourceSegment.EpmInfo.Attribute.TargetPath || epmInfo.Attribute.TargetNamespacePrefix != currentSourceSegment.EpmInfo.Attribute.TargetNamespacePrefix || epmInfo.Attribute.TargetNamespaceUri != currentSourceSegment.EpmInfo.Attribute.TargetNamespaceUri || epmInfo.Criteria != currentSourceSegment.EpmInfo.Criteria || String.Compare(epmInfo.Attribute.CriteriaValue, currentSourceSegment.EpmInfo.CriteriaValue, StringComparison.OrdinalIgnoreCase) != 0) { throw new ODataException(Strings.EpmSourceTree_PropertiesFromSameMultiValueMappedToDifferentTopLevelElements(currentSourceSegment.EpmInfo.Attribute.SourcePath, currentSourceSegment.EpmInfo.DefiningType.Name)); } // Second verify that the mappings for both properties have the same KeepInContent value if (epmInfo.Attribute.KeepInContent != currentSourceSegment.EpmInfo.Attribute.KeepInContent) { throw new ODataException(Strings.EpmSourceTree_PropertiesFromSameMultiValueMappedWithDifferentKeepInContent(currentSourceSegment.EpmInfo.Attribute.SourcePath, currentSourceSegment.EpmInfo.DefiningType.Name)); } } } } // The last segment is the one being mapped from by the user specified attribute. // It must be a primitive type - we don't allow mappings of anything else than primitive properties directly. // Note that we can only verify this for non-open properties, for open properties we must assume it's a primitive type // and we will make this check later during serialization again when we actually have the value of the property. if (currentType != null) { if (currentType.ResourceTypeKind != ResourceTypeKind.Primitive) { throw new ODataException(Strings.EpmSourceTree_EndsWithNonPrimitiveType(currentSourceSegment.PropertyName)); } SyndicationItemProperty targetSyndicationItem = epmInfo.Attribute.TargetSyndicationItem; if (targetSyndicationItem == SyndicationItemProperty.LinkRel || targetSyndicationItem == SyndicationItemProperty.CategoryScheme) { if (PrimitiveStringResourceType != currentType) { throw new InvalidOperationException(Strings.EpmSourceTree_NonStringPropertyMappedToConditionAttribute( currentSourceSegment.PropertyName, epmInfo.DefiningType.FullName, targetSyndicationItem.ToString())); } } } if (multiValuePropertySegment == currentSourceSegment) { // If the MultiValue property is the last segment it means that the MultiValue property itself is being mapped (and it must be a MultiValue of primitive types). // If we found the MultiValue property already in the tree, here we actually want the item of the MultiValue property (as the MultiValue one was processed above already) // If we have the item value already in the tree use it as the foundProperty so that we correctly check the duplicate mappings below if (foundSourceSegment != null) { Debug.Assert(foundSourceSegment == currentSourceSegment, "If we found an existing segment it must be the current one."); foundSourceSegment = currentSourceSegment.SubProperties.SingleOrDefault(e => e.IsMultiValueItemValue); } if (foundSourceSegment == null) { // This is a bit of a special case. In the source tree we will create a special node to represent the item value (we need that to be able to tell // if it was not mapped twice). // In the target tree, we will also create a special node which will hold the information specific // to serialization of the item value (for example the exact syndication mapping target and so on). // The creation of the special node is done in the target tree Add method. EpmSourcePathSegment newSegment = EpmSourcePathSegment.CreateMultiValueItemValueSegment(); currentSourceSegment.SubProperties.Add(newSegment); currentSourceSegment = newSegment; } else { currentSourceSegment = foundSourceSegment; } } // Note that once we're here the EpmInfo we have is never the MultiValue property itself, it's always either a non-MultiValue property // or MultiValue item property. Debug.Assert(foundSourceSegment == null || foundSourceSegment.EpmInfo != null, "Can't have a leaf node in the tree without EpmInfo."); // Two EpmAttributes with same PropertyName in the same ResourceType, this could be a result of inheritance if (foundSourceSegment != null) { Debug.Assert(Object.ReferenceEquals(foundSourceSegment, currentSourceSegment), "currentSourceSegment variable should have been updated already to foundSourceSegment"); Debug.Assert( foundSourceSegment.EpmInfo.MultiValueStatus != EntityPropertyMappingMultiValueStatus.MultiValueProperty, "We should never get here with a MultiValue property itself, we should have a node represent its item or property on the item instead."); // Check for duplicates on the same entity type Debug.Assert(foundSourceSegment.SubProperties.Count == 0, "If non-leaf, it means we allowed complex type to be a leaf node"); if (foundSourceSegment.EpmInfo.DefiningTypesAreEqual(epmInfo)) { throw new ODataException(Strings.EpmSourceTree_DuplicateEpmAttrsWithSameSourceName(epmInfo.Attribute.SourcePath, epmInfo.DefiningType.Name)); } // In case of inheritance, we need to remove the node from target tree which was mapped to base type property this.epmTargetTree.Remove(foundSourceSegment.EpmInfo); } epmInfo.SetPropertyValuePath(pathToCurrentSegment.ToArray()); currentSourceSegment.EpmInfo = epmInfo; if (multiValuePropertySegment != null) { Debug.Assert(multiValuePropertySegment.EpmInfo != null, "All MultiValue property segments must have EpmInfo assigned."); // We are mapping a MultiValue property - so mark the info as a MultiValue item property (since the MultiValue property itself was added above) epmInfo.MultiValueStatus = EntityPropertyMappingMultiValueStatus.MultiValueItemProperty; // Set the item type on each of the item properties, so that the segmented path know from which type to start epmInfo.MultiValueItemType = multiValuePropertySegment.EpmInfo.MultiValueItemType; // And trim its property value path to start from the MultiValue item. This path is basically a list of properties to traverse // when access the value of the property on the specified resource. For non-MultiValue and MultiValue properties themselves // this path starts with the entity instance. For MultiValue item properties this path starts with the MultiValue item instance. // Note that if it's a MultiValue of primitive types, the path is going to be empty meaning that the value is the item instance itself. epmInfo.TrimMultiValueItemPropertyPath(multiValuePropertySegment.EpmInfo); #if DEBUG // Check that if the MultiValue item is of primitive type, we can only ever add a single child source segment which points directly to the MultiValue property itself // If we would allow this here, we would fail later, but with a much weirder error message Debug.Assert( multiValuePropertySegment.EpmInfo.MultiValueItemType.ResourceTypeKind != ResourceTypeKind.Primitive || epmInfo.PropertyValuePath.Length == 0, "We shoud have failed to map a subproperty of a primitive MultiValue item."); #endif } this.epmTargetTree.Add(epmInfo); }
/// <summary> /// Writes a single property in ATOM format. /// </summary> /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="property">The property to write out.</param> /// <param name="owningType">The type owning the property (or null if no metadata is available).</param> /// <param name="version">The protocol version used for writing.</param> /// <param name="isTopLevel">True if writing a top-level property payload; otherwise false.</param> /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmParentSourcePathSegment">The EPM source path segment which points to the property which sub-property we're writing. (can be null)</param> internal static void WriteProperty( XmlWriter writer, DataServiceMetadataProviderWrapper metadata, ODataProperty property, ResourceType owningType, ODataVersion version, bool isTopLevel, bool isWritingCollection, EpmValueCache epmValueCache, EpmSourcePathSegment epmParentSourcePathSegment) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(writer != null, "writer != null"); ValidationUtils.ValidateProperty(property); ResourceProperty resourceProperty = ValidationUtils.ValidatePropertyDefined(property.Name, owningType); EpmSourcePathSegment epmSourcePathSegment = null; if (epmParentSourcePathSegment != null) { epmSourcePathSegment = epmParentSourcePathSegment.SubProperties.Where(subProperty => subProperty.PropertyName == property.Name).FirstOrDefault(); } object value = property.Value; // TODO: If we implement validation or type conversions the value needs to be converted here // since the next method call needs to know if the value is a string or not in some cases. // If EPM tells us to skip this property in content, then we're done here. if (!ShouldWritePropertyInContent(value, epmSourcePathSegment, version)) { return; } // <d:propertyname> writer.WriteStartElement( isWritingCollection ? string.Empty : AtomConstants.ODataNamespacePrefix, property.Name, AtomConstants.ODataNamespace); if (isTopLevel) { WriteDefaultNamespaceAttributes(writer, DefaultNamespaceFlags.OData | DefaultNamespaceFlags.ODataMetadata); } // Null property value. if (value == null) { // verify that MultiValue properties are not null if (resourceProperty != null && resourceProperty.Kind == ResourcePropertyKind.MultiValue) { throw new ODataException(Strings.ODataWriter_MultiValuePropertiesMustNotHaveNullValue(resourceProperty.Name)); } ODataAtomWriterUtils.WriteNullAttribute(writer); } else { ODataComplexValue complexValue = value as ODataComplexValue; ResourceType resourcePropertyType = resourceProperty == null ? null : resourceProperty.ResourceType; bool isOpenPropertyType = owningType != null && owningType.IsOpenType && resourceProperty == null; // Complex properties are written recursively. if (complexValue != null) { WriteComplexValue(writer, metadata, complexValue, resourcePropertyType, isOpenPropertyType, isWritingCollection, version, epmValueCache, epmSourcePathSegment); } else { ODataMultiValue multiValue = value as ODataMultiValue; if (multiValue != null) { ODataVersionChecker.CheckMultiValueProperties(version, property.Name); WriteMultiValue(writer, metadata, multiValue, resourcePropertyType, isOpenPropertyType, isWritingCollection, version, epmValueCache, epmSourcePathSegment); } else { WritePrimitiveValue(writer, value, resourcePropertyType); } } } // </d:propertyname> writer.WriteEndElement(); }
/// <summary> /// Write the content of the given entry. /// </summary> /// <param name="entry">The entry for which to write properties.</param> /// <param name="entryType">The <see cref="ResourceType"/> of the entry (or null if not metadata is available).</param> /// <param name="propertiesValueCache">The cache of properties.</param> /// <param name="rootSourcePathSegment">The root of the EPM source tree, if there's an EPM applied.</param> private void WriteEntryContent(ODataEntry entry, ResourceType entryType, EntryPropertiesValueCache propertiesValueCache, EpmSourcePathSegment rootSourcePathSegment) { Debug.Assert(entry != null, "entry != null"); Debug.Assert(propertiesValueCache != null, "propertiesValueCache != null"); ODataMediaResource mediaResource = entry.MediaResource; if (mediaResource == null) { // <content type="application/xml"> this.writer.WriteStartElement( AtomConstants.AtomNamespacePrefix, AtomConstants.AtomContentElementName, AtomConstants.AtomNamespace); this.writer.WriteAttributeString( AtomConstants.AtomTypeAttributeName, MimeConstants.MimeApplicationXml); // <m:properties> // we always write the <m:properties> element even if there are no properties this.writer.WriteStartElement( AtomConstants.ODataMetadataNamespacePrefix, AtomConstants.AtomPropertiesElementName, AtomConstants.ODataMetadataNamespace); ODataAtomWriterUtils.WriteProperties( this.writer, this.MetadataProvider, entryType, propertiesValueCache.EntryProperties, this.Version, false, propertiesValueCache, rootSourcePathSegment); // </m:properties> this.writer.WriteEndElement(); // </content> this.writer.WriteEndElement(); } else { Uri mediaEditLink = mediaResource.EditLink; if (mediaEditLink != null) { // <link rel="edit-media" href="href" /> this.writer.WriteStartElement( AtomConstants.AtomNamespacePrefix, AtomConstants.AtomLinkElementName, AtomConstants.AtomNamespace); this.writer.WriteAttributeString( AtomConstants.AtomLinkRelationAttributeName, AtomConstants.AtomEditMediaRelationAttributeValue); this.writer.WriteAttributeString( AtomConstants.AtomHRefAttributeName, AtomUtils.ToUrlAttributeValue(mediaEditLink, this.BaseUri)); string mediaETag = mediaResource.ETag; if (mediaETag != null) { this.writer.WriteAttributeString( AtomConstants.ODataMetadataNamespacePrefix, AtomConstants.ODataETagAttributeName, AtomConstants.ODataMetadataNamespace, mediaETag); } // </link> this.writer.WriteEndElement(); } Debug.Assert(mediaEditLink != null || mediaResource.ETag == null, "The default stream edit link and etag should have been validated by now."); // <content type="type" src="src"> this.writer.WriteStartElement( AtomConstants.AtomNamespacePrefix, AtomConstants.AtomContentElementName, AtomConstants.AtomNamespace); Debug.Assert(!string.IsNullOrEmpty(mediaResource.ContentType), "The default stream content type should have been validated by now."); this.writer.WriteAttributeString( AtomConstants.AtomTypeAttributeName, mediaResource.ContentType); Debug.Assert(mediaResource.ReadLink != null, "The default stream read link should have been validated by now."); this.writer.WriteAttributeString( AtomConstants.MediaLinkEntryContentSourceAttributeName, AtomUtils.ToUrlAttributeValue(mediaResource.ReadLink, this.BaseUri)); // </content> this.writer.WriteEndElement(); // <m:properties> // we always write the <m:properties> element even if there are no properties this.writer.WriteStartElement( AtomConstants.ODataMetadataNamespacePrefix, AtomConstants.AtomPropertiesElementName, AtomConstants.ODataMetadataNamespace); ODataAtomWriterUtils.WriteProperties( this.writer, this.MetadataProvider, entryType, propertiesValueCache.EntryProperties, this.Version, false, propertiesValueCache, rootSourcePathSegment); // </m:properties> this.writer.WriteEndElement(); } }
/// <summary> /// Reads a property value starting with the specified index to the property value path. /// </summary> /// <param name="cachedProperties">The enumeration of properties to search for the first property in the property value path.</param> /// <param name="sourceSegmentIndex">The index in the property value path to start with.</param> /// <param name="resourceType">The resource type of the entry or complex value the <paramref name="cachedProperties"/> enumeration belongs to.</param> /// <param name="metadata">The metadata provider to use.</param> /// <param name="epmValueCache">The EPM value cache to use.</param> /// <param name="nullOnParentProperty">true if the value of the property is null because one of its parent properties was null, in this case /// the return value of the method is always null. false if the value of the property is the actual property value which may or may not be null.</param> /// <returns>The value of the property (may be null), or null if the property itself was not found due to one of its parent properties being null.</returns> private object ReadPropertyValue( IEnumerable <ODataProperty> cachedProperties, int sourceSegmentIndex, ResourceType resourceType, DataServiceMetadataProviderWrapper metadata, EpmValueCache epmValueCache, out bool nullOnParentProperty) { Debug.Assert(this.propertyValuePath != null, "The propertyValuePath should have been initialized by now."); Debug.Assert(this.propertyValuePath.Length > sourceSegmentIndex, "The propertyValuePath must be at least as long as the source segment index."); Debug.Assert(resourceType != null, "resourceType != null"); Debug.Assert(epmValueCache != null, "epmValueCache != null"); EpmSourcePathSegment sourceSegment = this.propertyValuePath[sourceSegmentIndex]; string propertyName = sourceSegment.PropertyName; bool lastSegment = this.propertyValuePath.Length == sourceSegmentIndex + 1; ResourceProperty resourceProperty = ValidationUtils.ValidatePropertyDefined(propertyName, resourceType); if (resourceProperty != null) { // If this is the last part of the path, then it has to be a primitive or multiValue type otherwise should be a complex type if (lastSegment) { if (!resourceProperty.IsOfKind(ResourcePropertyKind.Primitive) && !resourceProperty.IsOfKind(ResourcePropertyKind.MultiValue)) { throw new ODataException(Strings.EpmSourceTree_EndsWithNonPrimitiveType(propertyName)); } } else { if (!resourceProperty.IsOfKind(ResourcePropertyKind.ComplexType)) { throw new ODataException(Strings.EpmSourceTree_TraversalOfNonComplexType(propertyName)); } } } else { Debug.Assert( resourceType.IsOpenType, "Only open types can have undeclared properties, otherwise we should have failed in the ValidatePropertyDefined method."); } ODataProperty property = null; if (cachedProperties != null) { property = cachedProperties.FirstOrDefault(p => p.Name == propertyName); } if (property == null) { throw new ODataException(Strings.EpmSourceTree_MissingPropertyOnInstance(propertyName, resourceType.FullName)); } object propertyValue = property.Value; ODataComplexValue propertyComplexValue = propertyValue as ODataComplexValue; if (lastSegment) { if (propertyValue != null && resourceProperty != null) { ValidationUtils.ValidateIsExpectedPrimitiveType(propertyValue, resourceProperty.ResourceType); } // If this property is the last one it has to be either a primitive or multivalue // TODO: Check for named streams here as well if (propertyComplexValue != null) { throw new ODataException(Strings.EpmSourceTree_EndsWithNonPrimitiveType(propertyName)); } nullOnParentProperty = false; return(propertyValue); } else { // Otherwise it's in the middle and thus it must be a complex value if (propertyComplexValue == null) { throw new ODataException(Strings.EpmSourceTree_TraversalOfNonComplexType(propertyName)); } string typeName = propertyComplexValue.TypeName; ResourceType complexValueType = MetadataUtils.ResolveTypeName( metadata, resourceProperty == null ? null : resourceProperty.ResourceType, ref typeName, ResourceTypeKind.ComplexType, resourceProperty == null); return(this.ReadComplexPropertyValue( propertyComplexValue, sourceSegment, epmValueCache, sourceSegmentIndex + 1, complexValueType, metadata, out nullOnParentProperty)); } }
/// <summary> /// Write the items in a MultiValue in ATOM format. /// </summary> /// <param name="writer">The <see cref="XmlWriter"/> to write to.</param> /// <param name="metadata">The metadata provider to use or null if no metadata is available.</param> /// <param name="multiValue">The MultiValue to write.</param> /// <param name="resourcePropertyType">The resource type of the multi value (or null if not metadata is available).</param> /// <param name="isOpenPropertyType">True if the type name belongs to an open property.</param> /// <param name="isWritingCollection">True if we are writing a collection instead of an entry.</param> /// <param name="version">The protocol version used for writing.</param> /// <param name="epmValueCache">Cache of values used in EPM so that we avoid multiple enumerations of properties/items. (can be null)</param> /// <param name="epmSourcePathSegment">The EPM source path segment which points to the multivalue property we're writing. (can be null)</param> private static void WriteMultiValue( XmlWriter writer, DataServiceMetadataProviderWrapper metadata, ODataMultiValue multiValue, ResourceType resourcePropertyType, bool isOpenPropertyType, bool isWritingCollection, ODataVersion version, EpmValueCache epmValueCache, EpmSourcePathSegment epmSourcePathSegment) { Debug.Assert(multiValue != null, "multiValue != null"); string typeName = multiValue.TypeName; // resolve the type name to the resource type; if no type name is specified we will use the // type inferred from metadata MultiValueResourceType multiValueType = (MultiValueResourceType)MetadataUtils.ResolveTypeName(metadata, resourcePropertyType, ref typeName, ResourceTypeKind.MultiValue, isOpenPropertyType); if (typeName != null) { WritePropertyTypeAttribute(writer, typeName); } ResourceType expectedItemType = multiValueType == null ? null : multiValueType.ItemType; IEnumerable items = EpmValueCache.GetMultiValueItems(epmValueCache, epmSourcePathSegment, multiValue, true); if (items != null) { foreach (object itemValue in items) { object item; EpmMultiValueItemCache epmItemCache = itemValue as EpmMultiValueItemCache; if (epmItemCache != null) { item = epmItemCache.ItemValue; } else { item = itemValue; } ValidationUtils.ValidateMultiValueItem(item); writer.WriteStartElement(AtomConstants.ODataNamespacePrefix, AtomConstants.ODataMultiValueItemElementName, AtomConstants.ODataNamespace); ODataComplexValue complexValue = item as ODataComplexValue; if (complexValue != null) { WriteComplexValue(writer, metadata, complexValue, expectedItemType, false, isWritingCollection, version, epmItemCache, epmSourcePathSegment); } else { ODataMultiValue multiValueItem = item as ODataMultiValue; if (multiValueItem != null) { throw new ODataException(Strings.ODataWriter_NestedMultiValuesAreNotSupported); } else { AtomValueUtils.WritePrimitiveValue(writer, item, expectedItemType); } } writer.WriteEndElement(); } } }
/// <summary> /// Validates the specified segment which is a subsegment of a MultiValue property or the MultiValue property segment itself. /// </summary> /// <param name="multiValuePropertyInfo">Info about the MultiValue property being processed. Used for exception messages only.</param> /// <param name="multiValueSegment">The segment belonging to a MultiValue property to validate.</param> /// <param name="resourceType">The resource type of the property represented by this segment (item type for the MultiValue property itself).</param> private static void ValidateMultiValueSegment( EntityPropertyMappingInfo multiValuePropertyInfo, EpmSourcePathSegment multiValueSegment, ResourceType resourceType) { Debug.Assert( multiValuePropertyInfo != null && multiValuePropertyInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty, "multiValuePropertyName must be non-null and must be a MultiValue info."); Debug.Assert(multiValueSegment != null, "multiValueSegment != null"); Debug.Assert(resourceType != null, "resourceType != null"); Debug.Assert( multiValueSegment.EpmInfo == null || multiValueSegment.EpmInfo == multiValuePropertyInfo || multiValueSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueItemProperty, "The specified segment does not belong to a MultiValue property subtree."); if (resourceType.ResourceTypeKind == ResourceTypeKind.ComplexType) { Debug.Assert( multiValueSegment.EpmInfo == null || multiValueSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty, "EPM source segment representing a complex property of a MultiValue property must not have an EpmInfo for the property itself."); // Verify that all properties of the complex type are mapped (have respective source segments) foreach (ResourceProperty property in resourceType.Properties) { string propertyName = property.Name; string resourceTypeName = resourceType.Name; EpmSourcePathSegment subSegment = multiValueSegment.SubProperties.SingleOrDefault(e => e.PropertyName == propertyName); if (subSegment == null) { throw new ODataException(Strings.EpmSourceTree_NotAllMultiValueItemPropertiesMapped( multiValuePropertyInfo.Attribute.SourcePath, multiValuePropertyInfo.DefiningType.Name, propertyName, resourceTypeName)); } else { ResourceType propertyType = property.ResourceType; // Recursive call to verify the sub segment // Note that we don't need to check for enless loops and recursion depth because we are effectively walking the EPM source tree // which itself can't have loops in it, and can't be infinite either. So if the metadata for a MultiValue property has loops in it // we would eventually fail to find a matching segment in the source tree and throw. ValidateMultiValueSegment(multiValuePropertyInfo, subSegment, propertyType); } } } else { Debug.Assert(multiValueSegment.EpmInfo != null, "Primitive value must have EpmInfo."); if (multiValueSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueProperty) { // MultiValue of primitive types Debug.Assert(multiValueSegment.SubProperties.Count == 1, "Exactly one subproperty should be on a node representing a MultiValue property of primitive types."); Debug.Assert( multiValueSegment.SubProperties[0].IsMultiValueItemValue && multiValueSegment.SubProperties[0].EpmInfo != null && multiValueSegment.SubProperties[0].EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueItemProperty, "The only subproperty of a collectin of primitive types should be the special item's value node."); EpmSourcePathSegment leafSegment = multiValueSegment.SubProperties[0]; if (leafSegment.EpmInfo.IsAtomLinkMapping) { if (leafSegment.EpmInfo.CriteriaValue == null) { throw new ODataException(Strings.EpmSourceTree_MultiValueOfPrimitiveMappedToLinkWithoutCriteria( multiValuePropertyInfo.Attribute.SourcePath, multiValuePropertyInfo.DefiningType.Name, leafSegment.EpmInfo.Attribute.TargetPath)); } else if (leafSegment.EpmInfo.Attribute.TargetSyndicationItem != SyndicationItemProperty.LinkHref) { throw new ODataException(Strings.EpmTargetTree_ConditionalMappingLinkHrefIsRequired( multiValuePropertyInfo.Attribute.SourcePath, multiValuePropertyInfo.DefiningType.Name, leafSegment.EpmInfo.Attribute.TargetPath)); } } else if (leafSegment.EpmInfo.IsAtomCategoryMapping) { if (leafSegment.EpmInfo.CriteriaValue == null && leafSegment.EpmInfo.Attribute.TargetSyndicationItem != SyndicationItemProperty.CategoryTerm) { throw new ODataException(Strings.EpmTargetTree_ConditionalMappingCategoryTermIsRequired( multiValuePropertyInfo.Attribute.SourcePath, multiValuePropertyInfo.DefiningType.Name, leafSegment.EpmInfo.Attribute.TargetPath)); } } } else { // MultiValue of complex types, we're on a leaf primitive property. Debug.Assert( multiValueSegment.EpmInfo.MultiValueStatus == EntityPropertyMappingMultiValueStatus.MultiValueItemProperty, "Only multiValue property or multiValue item property nodes should get here."); Debug.Assert(multiValueSegment.SubProperties.Count == 0, "Primtive item property value should have no sub properties."); Debug.Assert(!multiValueSegment.IsMultiValueItemValue, "Special MultiValue item value must not be a child of a complex property."); EntityPropertyMappingInfo multiValueSegmentEpmInfo = multiValueSegment.EpmInfo; if (multiValueSegmentEpmInfo.IsAtomLinkMapping != multiValueSegmentEpmInfo.IsAtomLinkMapping || multiValueSegmentEpmInfo.IsAtomCategoryMapping != multiValueSegmentEpmInfo.IsAtomCategoryMapping || String.Compare(multiValueSegmentEpmInfo.CriteriaValue, multiValueSegmentEpmInfo.CriteriaValue, StringComparison.OrdinalIgnoreCase) != 0) { throw new ODataException(Strings.EpmSourceTree_MultiValueOfComplexTypesDifferentConditionalMapping( multiValuePropertyInfo.Attribute.SourcePath, multiValuePropertyInfo.DefiningType.Name, multiValuePropertyInfo.Attribute.TargetPath)); } } } }