internal EntityPropertyMappingInfo(EntityPropertyMappingAttribute attribute, IEdmEntityType definingType, IEdmEntityType actualTypeDeclaringProperty) { this.attribute = attribute; this.definingType = definingType; this.actualPropertyType = actualTypeDeclaringProperty; this.isSyndicationMapping = this.attribute.TargetSyndicationItem != SyndicationItemProperty.CustomProperty; }
public EntityPropertyMappingInfo(EntityPropertyMappingAttribute attribute, Type definingType, ClientTypeAnnotation actualPropertyType) { this.attribute = attribute; this.definingType = definingType; this.actualPropertyType = actualPropertyType; this.propertyValuePath = attribute.SourcePath.Split(new char[] { '/' }); this.isSyndicationMapping = this.attribute.TargetSyndicationItem != SyndicationItemProperty.CustomProperty; }
public EntityPropertyMappingInfo(EntityPropertyMappingAttribute attribute, ResourceType definingType, ResourceType actualPropertyType, bool isEFProvider) { this.isEFProvider = isEFProvider; this.attribute = attribute; this.definingType = definingType; this.actualPropertyType = actualPropertyType; this.propertyValuePath = attribute.SourcePath.Split(new char[] { '/' }); this.isSyndicationMapping = this.attribute.TargetSyndicationItem != SyndicationItemProperty.CustomProperty; }
/// <summary> /// Creates instance of EntityPropertyMappingInfo class. /// </summary> /// <param name="attribute">The <see cref="EntityPropertyMappingAttribute"/> corresponding to this object</param> /// <param name="definingType">Type the <see cref="EntityPropertyMappingAttribute"/> was defined on.</param> /// <param name="actualPropertyType">ClientType whose property is to be read.</param> public EntityPropertyMappingInfo(EntityPropertyMappingAttribute attribute, Type definingType, ClientType actualPropertyType) { #endif Debug.Assert(attribute != null, "attribute != null"); Debug.Assert(definingType != null, "definingType != null"); Debug.Assert(actualPropertyType != null, "actualPropertyType != null"); this.attribute = attribute; this.definingType = definingType; this.actualPropertyType = actualPropertyType; Debug.Assert(!string.IsNullOrEmpty(attribute.SourcePath), "Invalid source path"); this.segmentedSourcePath = attribute.SourcePath.Split('/'); }
public EntityPropertyMappingInfo(EntityPropertyMappingAttribute attribute, Type definingType, ClientType actualPropertyType) { #endif Debug.Assert(attribute != null, "attribute != null"); Debug.Assert(definingType != null, "definingType != null"); Debug.Assert(actualPropertyType != null, "actualPropertyType != null"); this.attribute = attribute; this.definingType = definingType; this.actualPropertyType = actualPropertyType; Debug.Assert(!string.IsNullOrEmpty(attribute.SourcePath), "Invalid source path"); this.segmentedSourcePath = attribute.SourcePath.Split('/'); }
/// <summary> /// Does given property in the attribute exist in the specified resource type. /// </summary> /// <param name="resourceType">The resource type to inspect.</param> /// <param name="epmAttribute">Attribute which has PropertyName.</param> /// <returns>true if property exists in the specified type, false otherwise.</returns> private static bool PropertyExistsOnType(ResourceType resourceType, EntityPropertyMappingAttribute epmAttribute) { Debug.Assert(resourceType != null, "resourceType != null"); Debug.Assert(epmAttribute != null, "epmAttribute != null"); int indexOfSeparator = epmAttribute.SourcePath.IndexOf('/'); String propertyToLookFor = indexOfSeparator == -1 ? epmAttribute.SourcePath : epmAttribute.SourcePath.Substring(0, indexOfSeparator); return resourceType.PropertiesDeclaredOnThisType.Any(p => p.Name == propertyToLookFor); }
/// <summary> /// Creates instance of EntityPropertyMappingInfo class. /// </summary> /// <param name="attribute">The <see cref="EntityPropertyMappingAttribute"/> corresponding to this object</param> /// <param name="definingType">Type the <see cref="EntityPropertyMappingAttribute"/> was defined on.</param> /// <param name="actualPropertyType">Type whose property is to be read. This can be different from defining type when inheritance is involved.</param> /// <param name="isEFProvider">Whether the current data source is an EF provider. Needed for error reporting.</param> public EntityPropertyMappingInfo(EntityPropertyMappingAttribute attribute, ResourceType definingType, ResourceType actualPropertyType, bool isEFProvider) { this.isEFProvider = isEFProvider;
private static bool PropertyExistsOnType(IEdmStructuredType structuredType, EntityPropertyMappingAttribute epmAttribute) { int index = epmAttribute.SourcePath.IndexOf('/'); string propertyToLookFor = (index == -1) ? epmAttribute.SourcePath : epmAttribute.SourcePath.Substring(0, index); return structuredType.DeclaredProperties.Any<IEdmProperty>(p => (p.Name == propertyToLookFor)); }
/// <summary> /// Builds the EntityPropertyMappingInfo corresponding to an EntityPropertyMappingAttribute, also builds the delegate to /// be invoked in order to retrieve the property provided in the <paramref name="epmAttr"/> /// </summary> /// <param name="epmAttr">Source EntityPropertyMappingAttribute</param> /// <param name="definingType">Type that has the attribute applied to it</param> /// <param name="isEFProvider">Is EF provider being initialized, used for error message formatting</param> internal void BuildEpmInfo(EntityPropertyMappingAttribute epmAttr, ResourceType definingType, bool isEFProvider) { // We don't need to check for null/empty status of the source path because it is already checked // in the constructor for EntityPropertyMappingAttribute // ParameterExpression rsrcParam = Expression.Parameter(typeof(object), "rsrc"); // ParameterExpression providerParam = Expression.Parameter(typeof(DataServiceProviderWrapper), "provider"); ResourceProperty resourceProperty = null; // Expression propValReaderExpr = this.BuildPropertyReader( // rsrcParam, // providerParam, // this, // epmAttr.SourcePath.Split('/'), // 0, // ref resourceProperty); // Delegate dlgPropValReader = Expression.Lambda(propValReaderExpr, rsrcParam, providerParam).Compile(); // this.EpmSourceTree.Add(EntityPropertyMappingInfo epmAttr); // new EntityPropertyMappingInfo // { // Attribute = epmAttr, //PropValReader = dlgPropValReader, // DefiningType = definingType, // IsEFProvider = isEFProvider // }); }
/// <summary> /// Adds an <see cref="EntityPropertyMappingAttribute"/> for the resource type. /// </summary> /// <param name="attribute">Given <see cref="EntityPropertyMappingAttribute"/></param> public void AddEntityPropertyMappingAttribute(EntityPropertyMappingAttribute attribute) { // EntityPropertyMapping attribute can not be added to readonly resource types. this.ThrowIfSealed(); if (this.ResourceTypeKind != ResourceTypeKind.EntityType) { throw new InvalidOperationException("Not an entity type"); } if (this.epmInfo == null) { this.epmInfo = new EpmInfoPerResourceType(); } this.OwnEpmInfo.Add(attribute); }
public void AddEntityPropertyMappingAttribute (EntityPropertyMappingAttribute attribute) { throw new NotImplementedException (); }
/// <summary> /// Builds the EntityPropertyMappingInfo corresponding to an EntityPropertyMappingAttribute, also builds the delegate to /// be invoked in order to retrieve the property provided in the <paramref name="epmAttr"/> /// </summary> /// <param name="epmAttr">Source EntityPropertyMappingAttribute</param> /// <param name="definingType">Type that has the attribute applied to it</param> /// <param name="isEFProvider">Is EF provider being initialized, used for error message formatting</param> internal void BuildEpmInfo(EntityPropertyMappingAttribute epmAttr, ResourceType definingType, bool isEFProvider) { this.EpmSourceTree.Add(new EntityPropertyMappingInfo(epmAttr, definingType, this, isEFProvider)); }
/// <summary>Creates an EntityPropertyMappingAttribute and adds it to the <paramref name="typeDecl"/></summary> /// <param name="epmAttr">Attribute to add</param> /// <param name="typeDecl">Type declaration for which the attribute is generated</param> private void AddEpmAttributeToTypeDeclaration(EntityPropertyMappingAttribute epmAttr, CodeTypeDeclaration typeDecl) { if (epmAttr.TargetSyndicationItem != SyndicationItemProperty.CustomProperty) { var syndicationItem = new CodeFieldReferenceExpression( new CodeTypeReferenceExpression(typeof(SyndicationItemProperty)), epmAttr.TargetSyndicationItem.ToString()); var contentKind = new CodeFieldReferenceExpression( new CodeTypeReferenceExpression(typeof(SyndicationTextContentKind)), epmAttr.TargetTextContentKind.ToString()); CodeAttributeDeclaration attribute = new CodeAttributeDeclaration( TypeReference.FromString( Utils.WebFrameworkCommonNamespace + "." + "EntityPropertyMappingAttribute", true)); AddAttributeArguments(attribute, new object[] { epmAttr.SourcePath, syndicationItem, contentKind, epmAttr.KeepInContent }); typeDecl.CustomAttributes.Add(attribute); } else { CodeAttributeDeclaration attribute = new CodeAttributeDeclaration( TypeReference.FromString( Utils.WebFrameworkCommonNamespace + "." + "EntityPropertyMappingAttribute", true)); AddAttributeArguments(attribute, new object[] { epmAttr.SourcePath, epmAttr.TargetPath, epmAttr.TargetNamespacePrefix, epmAttr.TargetNamespaceUri, epmAttr.KeepInContent }); typeDecl.CustomAttributes.Add(attribute); } }
/// <summary> /// Does given property in the attribute exist in this type or one of it's base types /// </summary> /// <param name="epmAttr">Attribute which has PropertyName</param> /// <returns>true if property exists in current type, false otherwise</returns> private bool PropertyExistsInCurrentType(EntityPropertyMappingAttribute epmAttr) { int indexOfSeparator = epmAttr.SourcePath.IndexOf('/'); String propertyToLookFor = indexOfSeparator == -1 ? epmAttr.SourcePath : epmAttr.SourcePath.Substring(0, indexOfSeparator); return this.PropertiesDeclaredOnThisType.Any(p => p.Name == propertyToLookFor); }
/// <summary> /// Given a resource type and its resource proeperty builds the EntityPropertyMappingInfo for the EntityPropertyMappingAttribute on it /// </summary> /// <param name="propertyInformation">EPM information for current property</param> /// <param name="entityProperty">Property for which to get the information</param> /// <param name="typeDecl">Type declaration to add the attributes to</param> private void EmitEpmAttributeForEntityProperty( EpmPropertyInformation propertyInformation, EdmInfo entityProperty, CodeTypeDeclaration typeDecl) { if (propertyInformation.IsAtom) { if (entityProperty.IsComplex) { throw new InvalidOperationException(Strings.ObjectContext_SyndicationMappingForComplexPropertiesNotAllowed); } else { EntityPropertyMappingAttribute epmAttr = new EntityPropertyMappingAttribute( propertyInformation.SourcePath, propertyInformation.SyndicationItem, propertyInformation.ContentKind, propertyInformation.KeepInContent); this.AddEpmAttributeToTypeDeclaration(epmAttr, typeDecl); } } else { if (entityProperty.IsComplex) { foreach (EntityPropertyMappingAttribute epmAttr in GetEpmAttrsFromComplexProperty( entityProperty.Member, propertyInformation.SourcePath, propertyInformation.TargetPath, propertyInformation.NsPrefix, propertyInformation.NsUri, propertyInformation.KeepInContent)) { this.AddEpmAttributeToTypeDeclaration(epmAttr, typeDecl); } } else { EntityPropertyMappingAttribute epmAttr = new EntityPropertyMappingAttribute( propertyInformation.SourcePath, propertyInformation.TargetPath, propertyInformation.NsPrefix, propertyInformation.NsUri, propertyInformation.KeepInContent); this.AddEpmAttributeToTypeDeclaration(epmAttr, typeDecl); } } }
/// <summary> /// Creates instance of EntityPropertyMappingInfo class. /// </summary> /// <param name="attribute">The <see cref="EntityPropertyMappingAttribute"/> corresponding to this object</param> /// <param name="definingType">Type the <see cref="EntityPropertyMappingAttribute"/> was defined on.</param> /// <param name="actualTypeDeclaringProperty">Type whose property is to be read. This can be different from defining type when inheritance is involved.</param> internal EntityPropertyMappingInfo(EntityPropertyMappingAttribute attribute, ResourceType definingType, ResourceType actualTypeDeclaringProperty) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(attribute != null, "attribute != null"); Debug.Assert(definingType != null, "definingType != null"); Debug.Assert(actualTypeDeclaringProperty != null, "actualTypeDeclaringProperty != null"); this.attribute = attribute; this.definingType = definingType; this.actualPropertyType = actualTypeDeclaringProperty; this.multiValueStatus = EntityPropertyMappingMultiValueStatus.None; this.CriteriaValue = attribute.CriteriaValue; // Infer the mapping type from the attribute this.isSyndicationMapping = this.attribute.TargetSyndicationItem != SyndicationItemProperty.CustomProperty; switch (this.attribute.TargetSyndicationItem) { case SyndicationItemProperty.AuthorEmail: case SyndicationItemProperty.AuthorName: case SyndicationItemProperty.AuthorUri: this.SyndicationParent = EpmSyndicationParent.Author; break; case SyndicationItemProperty.CategoryLabel: case SyndicationItemProperty.CategoryScheme: case SyndicationItemProperty.CategoryTerm: this.SyndicationParent = EpmSyndicationParent.Category; break; case SyndicationItemProperty.ContributorEmail: case SyndicationItemProperty.ContributorName: case SyndicationItemProperty.ContributorUri: this.SyndicationParent = EpmSyndicationParent.Contributor; break; case SyndicationItemProperty.LinkHref: case SyndicationItemProperty.LinkHrefLang: case SyndicationItemProperty.LinkLength: case SyndicationItemProperty.LinkRel: case SyndicationItemProperty.LinkTitle: case SyndicationItemProperty.LinkType: this.SyndicationParent = EpmSyndicationParent.Link; break; default: this.SyndicationParent = EpmSyndicationParent.Entry; break; } if (attribute.CriteriaValue != null) { if (this.SyndicationParent == EpmSyndicationParent.Category) { this.Criteria = EpmSyndicationCriteria.CategoryScheme; } else if (this.SyndicationParent == EpmSyndicationParent.Link) { this.Criteria = EpmSyndicationCriteria.LinkRel; } else { this.Criteria = EpmSyndicationCriteria.None; } } }
private static bool EpmPropertyExistsInDeclaredProperties(EntityPropertyMappingAttribute epmAttr, ReadOnlyCollection<ResourceProperty> declaredProperties) { int index = epmAttr.SourcePath.IndexOf('/'); string propertyToLookFor = (index == -1) ? epmAttr.SourcePath : epmAttr.SourcePath.Substring(0, index); return declaredProperties.Any<ResourceProperty>(p => (p.Name == propertyToLookFor)); }
internal void AddEntityPropertyMappingAttributeInternal(EntityPropertyMappingAttribute attribute, bool isEFProvider) { WebUtil.CheckArgumentNull<EntityPropertyMappingAttribute>(attribute, "attribute"); this.ThrowIfSealed(); if (this.ResourceTypeKind != System.Data.Services.Providers.ResourceTypeKind.EntityType) { throw new InvalidOperationException(System.Data.Services.Strings.EpmOnlyAllowedOnEntityTypes(this.Name)); } if (this.epmInfo == null) { this.epmInfo = new EpmInfoPerResourceType(isEFProvider); } this.OwnEpmInfo.Add(attribute); }
public void AddEntityPropertyMappingAttribute(EntityPropertyMappingAttribute attribute) { this.AddEntityPropertyMappingAttributeInternal(attribute, false); }
/// <summary> /// Adds an <see cref="EntityPropertyMappingAttribute"/> for the resource type. /// </summary> /// <param name="attribute">Given <see cref="EntityPropertyMappingAttribute"/></param> public void AddEntityPropertyMappingAttribute(EntityPropertyMappingAttribute attribute) { WebUtil.CheckArgumentNull(attribute, "attribute"); // EntityPropertyMapping attribute can not be added to readonly resource types. this.ThrowIfSealed(); if (this.ResourceTypeKind != ResourceTypeKind.EntityType) { throw new InvalidOperationException(Strings.EpmOnlyAllowedOnEntityTypes(this.Name)); } if (this.epmInfo == null) { this.epmInfo = new EpmInfoPerResourceType(); } this.OwnEpmInfo.Add(attribute); }
/// <summary> /// Validates the annotation values parsed for an EPM mapping. /// </summary> /// <param name="annotationValues">The <see cref="EpmAnnotationValues"/> to validate.</param> /// <param name="typeName">The name of the type for which the annotations are validated or that declares the <paramref name="property"/>. Only used in error messages.</param> /// <param name="property">The property for which the annotations are validated; null if the annotations are for a type.</param> /// <returns>An <see cref="EntityPropertyMappingAttribute"/> instance that represents the mapping created from the <paramref name="annotationValues"/>.</returns> private static EntityPropertyMappingAttribute ValidateAnnotationValues(EpmAnnotationValues annotationValues, string typeName, IEdmProperty property) { Debug.Assert(annotationValues != null, "annotationValues != null"); Debug.Assert(annotationValues.AttributeSuffix != null, "annotationValues.AttributeSuffix != null"); Debug.Assert(!string.IsNullOrEmpty(typeName), "!string.IsNullOrEmpty(typeName)"); //// Conditions for EPM annotation values to represent a valid mapping: //// 1. must have target path //// 2. can have keep-in-content (default is 'true') //// 3a. if custom mapping: target path must map to custom property, content kind must be null //// 3b. if syndication mapping: content kind (optional; default: plain text), no ns uri, no ns prefix if (annotationValues.TargetPath == null) { string attributeName = EpmConstants.ODataEpmTargetPath + annotationValues.AttributeSuffix; string errorMessage = property == null ? o.Strings.EpmExtensionMethods_MissingAttributeOnType(attributeName, typeName) : o.Strings.EpmExtensionMethods_MissingAttributeOnProperty(attributeName, property.Name, typeName); throw new ODataException(errorMessage); } EntityPropertyMappingAttribute mapping; bool keepInContent = true; if (annotationValues.KeepInContent != null) { if (!bool.TryParse(annotationValues.KeepInContent, out keepInContent)) { string attributeName = EpmConstants.ODataEpmKeepInContent + annotationValues.AttributeSuffix; throw new InvalidOperationException(property == null ? o.Strings.EpmExtensionMethods_InvalidKeepInContentOnType(attributeName, typeName) : o.Strings.EpmExtensionMethods_InvalidKeepInContentOnProperty(attributeName, property.Name, typeName)); } } // figure out whether this is a custom mapping or not SyndicationItemProperty targetSyndicationItem = MapTargetPathToSyndicationProperty(annotationValues.TargetPath); if (targetSyndicationItem == SyndicationItemProperty.CustomProperty) { if (annotationValues.ContentKind != null) { string attributeName = EpmConstants.ODataEpmContentKind + annotationValues.AttributeSuffix; string errorMessage = property == null ? o.Strings.EpmExtensionMethods_AttributeNotAllowedForCustomMappingOnType(attributeName, typeName) : o.Strings.EpmExtensionMethods_AttributeNotAllowedForCustomMappingOnProperty(attributeName, property.Name, typeName); throw new ODataException(errorMessage); } mapping = new EntityPropertyMappingAttribute( annotationValues.SourcePath, annotationValues.TargetPath, annotationValues.NamespacePrefix, annotationValues.NamespaceUri, keepInContent); } else { if (annotationValues.NamespaceUri != null) { string attributeName = EpmConstants.ODataEpmNsUri + annotationValues.AttributeSuffix; string errorMessage = property == null ? o.Strings.EpmExtensionMethods_AttributeNotAllowedForAtomPubMappingOnType(attributeName, typeName) : o.Strings.EpmExtensionMethods_AttributeNotAllowedForAtomPubMappingOnProperty(attributeName, property.Name, typeName); throw new ODataException(errorMessage); } if (annotationValues.NamespacePrefix != null) { string attributeName = EpmConstants.ODataEpmNsPrefix + annotationValues.AttributeSuffix; string errorMessage = property == null ? o.Strings.EpmExtensionMethods_AttributeNotAllowedForAtomPubMappingOnType(attributeName, typeName) : o.Strings.EpmExtensionMethods_AttributeNotAllowedForAtomPubMappingOnProperty(attributeName, property.Name, typeName); throw new ODataException(errorMessage); } SyndicationTextContentKind contentKind = SyndicationTextContentKind.Plaintext; if (annotationValues.ContentKind != null) { contentKind = MapContentKindToSyndicationTextContentKind( annotationValues.ContentKind, annotationValues.AttributeSuffix, typeName, property == null ? null : property.Name); } mapping = new EntityPropertyMappingAttribute( annotationValues.SourcePath, targetSyndicationItem, contentKind, keepInContent); } Debug.Assert(mapping != null, "mapping != null"); return mapping; }
public EntityPropertyMappingInfo(EntityPropertyMappingAttribute attribute, ResourceType definingType, ResourceType actualPropertyType, bool isEFProvider) { this.isEFProvider = isEFProvider;
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); }
private static void BuildEpmInfo(EntityPropertyMappingAttribute epmAttr, Type definingType, ClientTypeAnnotation clientTypeAnnotation, System.Data.Services.Client.Serializers.EpmSourceTree sourceTree) { sourceTree.Add(new System.Data.Services.Client.Serializers.EntityPropertyMappingInfo(epmAttr, definingType, clientTypeAnnotation)); }
/// <summary> /// Adds an <see cref="EntityPropertyMappingAttribute"/> for the resource type. /// </summary> /// <param name="attribute">Given <see cref="EntityPropertyMappingAttribute"/>.</param> public void AddEntityPropertyMappingAttribute(EntityPropertyMappingAttribute attribute) { ExceptionUtils.CheckArgumentNotNull(attribute, "attribute"); // EntityPropertyMapping attribute can not be added to readonly resource types. this.ThrowIfSealed(); if (this.ResourceTypeKind != ResourceTypeKind.EntityType) { throw new InvalidOperationException(Strings.ResourceType_EpmOnlyAllowedOnEntityTypes(this.Name)); } // Initialize the EPM annotation for this type EpmResourceTypeAnnotation epm = this.Epm(); if (epm == null) { epm = new EpmResourceTypeAnnotation(); this.SetAnnotation(epm); } // And add the attribute to it epm.OwnEpmAttributes.Add(attribute); }