ADO.NET Data Services Friendly Feeds , Mapping EDM Types - I
As I mentioned in my last blog post , here are some samples of how to map your entity properties to Atom/custom markup in the atom:entry element.
You can apply Friendly Feed mappings on the EDM entity types by adding attributes to the CSDL of the Entity Data Model .
We will focus on the kinds of mappings and how to achieve them .
Pre-requisites
1. To edit the CSDL , you will need to open the EDM model in Xml view .To do this , right-click the EDM model (.EDMX file ) in Visual Studio
and select “Open with”->”Xml Editor” .
2. Add a reference to this namespace in the <edmx:ConceptualModels> node of the CSDL section in the EDMX file :
ex: xmlns:m2=" https://schemas.microsoft.com/ado/2007/08/dataservices/metadata "
1) Mapping to ATOM elements in the atom:entry payload :
The EntityPropertyMapping (EPM) attribute has two constructors , one which binds the property to an Atom element in the feed ,
and another which binds the property to a custom element . We shall discuss the former in this section.
For ATOM Mappings , the EPM markup has the following attributes .
- m2:FC_SourcePath: The m2:FC_SourcePath is used when
a) The Property you are mapping exists in a base type and not on the current type .
b) The property you are mapping exists in a complex type and not on the current type . - m2:FC_TargetPath : The atom:entry element to which this property has to be mapped to
- m2:FC_ContentKind: The content-type of the mapped atom:entry element
- m2:FC_KeepInContent : set this to false if you want the entity’s property value to turn up only in the mapped atom:entry element and not in the <contents> section.
Lets proceed , using the same BlogPost type that we discussed last time .
This is the EDM Markup for the type :
<EntityType Name="BlogPost">
<Key>
<PropertyRef Name="BlogPostID" />
</Key>
<Property Name="Lat" Type="Edm.Double" Nullable="false"/>
<Property Name="Long" Type="Edm.Double" Nullable="false"/>
<Property Name="Published" Type="Edm.DateTime" Nullable="false" />
<Property Name="BlogPostID" Type="Edm.Int32" Nullable="false" />
<Property Name="Title" Type="Edm.String" Nullable="true"/>
<Property Name="Body" Type="Edm.String" Nullable="true" />
<Property Name="Author" Type="Edm.String" Nullable="true"/>
<Property Name="PostURI" Type="Edm.String" Nullable="true" />
<Property Name="ContentSummary" Type="Edm.String" Nullable="true" />
<Property Name="IconUri" Type="Edm.String" Nullable="true"/>
</EntityType>
1. Map the “Title” property of the BlogPost Entity type to the entry:title element
When decorating EDM types , the markup specifying the mapping goes on the property you are mapping ,
ex:
<Property Name="Title" Type="Edm.String" Nullable="true"
m2:FC_TargetPath="SyndicationTitle"
m2:FC_EpmContentKind="Plaintext"
m2:FC_EpmKeepInContent="true" />
2. Map the “Author” property to entry:author element
This is what the markup would look like :
<Property Name="Author" Type="Edm.String" Nullable="true"
m2:FC_TargetPath="SyndicationAuthorName"
m2:FC_ContentKind="Plaintext"
m2:FC_KeepInContent="true" />
As described in my previous blog post , you can map an Entity’s properties to the following atom:entry elements in the payload :
atom:entry Element | m2:FC_TargetPath |
entry:author/email | SyndicationAuthorEmail |
entry:author/name | SyndicationAuthorName |
entry:author/uri | SyndicationAuthorUri |
entry:published | SyndicationPublished |
entry:rights | SyndicationRights |
entry:summary | SyndicationRights |
entry:title | SyndicationTitle |
Content-Kind | m2:FC_ContentKind |
Plaintext | Plaintext |
Html | Html |
Xhtml | XHtml |
Default if not specified. | PlainText |
2) Mapping to non-ATOM/custom elements in the atom:entry payload :
For non-ATOM/custom Mappings , the EPM markup requires the following attributes.
- m2:FC_SourcePath : The property of the Entity Type whose value should be mapped
- m2:FC_TargetPath : The xml path markup which describes the path to the custom markup this property should be mapped to .
- m2:FC_NsPrefix : The xml prefix for the custom element/attribute that this property is mapped to.
- m2:FC_NsUri : The xml namespace to which the custom element/attribute that this property is mapped should be under.
- m2:FC_KeepInContent : set this to false if you want the entity’s property value to turn up only in the mapped atom:entry element and not in the <contents> section.
The Xml Path syntax for custom mappings.
this syntax is very logical and looks like the following .
Lets say that you wanted to map a property to a custom element in markup that looks like this :
<mycustomRoot xmlns=”https://www.mycustomFormat.org”>
<customElement>property value goes here</customElement>
</mycustomRoot>
for this example ,
m2:FC_TargetPath would be “mycustomRoot/customElement”
m2:FC_NsPrefix would be an empty string as this markup has no custom prefix.
m2:FC_NsUri would be “https://www.mycustomFormat.org”
m2:FC_KeepInContent is subjective to whether you want to keep the property value in the <content> section or not.
and now , lets say that you wanted to map a property to a custom attribute of an element in markup that looks like this :
<me:mycustomRoot xmlns:me="https://www.georss.org.georss">
<me:customElement customAttribute="property value goes here"></me:customElement>
</me:mycustomRoot>
for this example ,
m2:FC_TargetPath would be <“mycustomRoot/customElement/@customAttribute>”
m2:FC_NsPrefix would be “me”.
m2:FC_NsUri would be “https://www.mycustomFormat.org”
m2:FC_KeepContent is subjective to whether you want to keep the property value in the <content> section or not.
A note , the complexity of your custom markup has a direct effect on the performance costs for Serialization/De-Serialization of the entity type .
with this example , lets map the lat & long properties to geoRss markup ,which looks like this :
<geo:lat xmlns:geo="https://www.georss.org/georss">47.684</geo:lat>
<geo:long xmlns:geo="https://www.georss.org/georss">-122.122</geo:long>
Final type definition looks like this :
<EntityType Name="BlogPost">
<Key>
<PropertyRef Name="BlogPostID" />
</Key>
<!-- map the Lat property to the <geo:lat> element -->
<Property Name="Lat" Type="Edm.Double" Nullable="false"
m2:FC_TargetPath="lat"
m2:FC_NsUri="https://www.georss.org/georss"
m2:FC_NsPrefix="geo"
m2:FC_KeepContent="true" />
<!-- map the Long property to the <geo:long> element -->
<Property Name="Long" Type="Edm.Double" Nullable="false"
m2:FC_TargetPath="long"
m2:FC_NsUri="https://www.georss.org/georss"
m2:FC_NsPrefix="geo"
m2:FC_KeepContent="true" />
<!-- Map the “Title” property of the BlogPost Entity type to the entry:title element -->
<Property Name="Title" Type="Edm.String" Nullable="true"
m2:FC_TargetPath="EpmSyndicationTitle"
m2:FC_ContentKind="EpmPlaintext"
m2:FC_KeepContent="true" />
<Property Name="Body" Type="Edm.String" Nullable="true" />
<!-- Map the “Author” property to entry:author element -->
<Property Name="Author" Type="Edm.String" Nullable="true"
m2:FC_TargetPath="EpmSyndicationAuthorName"
m2:FC_ContentKind="EpmPlaintext"
m2:FC_KeepContent="true" />
<Property Name="BlogPostID" Type="Edm.Int32" Nullable="false" />
<Property Name="Published" Type="Edm.DateTime" Nullable="false" />
<Property Name="PostURI" Type="Edm.String" Nullable="true" />
<Property Name="ContentSummary" Type="Edm.String" Nullable="true" />
<Property Name="IconUri" Type="Edm.String" Nullable="true"/>
</EntityType>
Special cases
I . Complex type properties
[I’ll leave the type definitions as CLR types so that its easier to visualize the relations.]
Consider the following model ,
public class Address
{
public long DoorNumber { get; set; }
public string Street { get; set; }
public int ZipCode { get; set; }
}
[DataServiceKey("CustomerID")]
public class Customer
{
public int CustomerID { get; set; }
public Address myAddress { get; set; }
}
and lets say that you wanted to map the property Street of the complex type Address when accessed through the
entity type “Customer” to the atom:title element ,
this can be achieved via setting the EpmSourcePath property to “Street” .
In this case , the Epm markup would look like this :
<EntityType Name="Customer">
<!-- Map the Street property of the Address complex type to the entry:title element -->
<Property Name="myAddress" Type="MyModelNamespace.Address"
me:EpmSourcePath="Street"
m2:FC_Atom="true"
m2:FC_TargetPath="EpmSyndicationTitle"
m2:FC_ContentKind="EpmPlaintext"
m2:FC_KeepContent="true"/>
<!-- other properties removed for brevity-->
</EntityType>
II Mapping properties declared on base type
Consider this data model :
[DataServiceKey("CustomerID")]
public class Customer
{
public int CustomerID { get; set; }
public Address myAddress { get; set; }
public string BaseTypeField { get; set; }
}
public class DerivedCustomer : Customer
{
public string DerivedTypeField { get; set; }
}
and you want to map a property declared on the Base type “Customer” on the derived type “DerivedCustomer”
The EPM Markup would look like this :
<!-- Map the "BaseTypeField" property of the base type "Customer" to the entry:title element -->
<EntityType Name="DerivedCustomer" BaseType="MyModelNamespace.Customer"
m2:FC_SourcePath="BaseTypeField"
m2:FC_TargetPath="EpmSyndicationAuthorName"
m2:FC_ContentKind="EpmPlaintext"
m2:FC_KeepContent="true">
<!-- other properties removed for brevity-->
</EntityType>
Considerations for location of EPM markup in EDM Schema
Use the following guidelines to decide where in the EDM Schema you should add the attribute for either ATOM or custom Mappings,
Where is property defined ? | Where do I apply the EPM attribute? | SourcePath |
Simple property on the Entity Type ex: “Title” property of “BlogPost” type above | <Property> node in the <Entitytype> markup | Not required |
Simple property on entity’s base type | <EntityType> node | Base type property name |
Complex Property on the Entity Type | Complex Types cannot be mapped directly | N/A |
Simple property defined on complex type which is a property on an Entity Type | <Property> node in the <EntityType> | Complex type’s simple property name |
Hope you enjoyed this post about applying Friendly Feeds mappings to EDMdata models.
The second part of this post will discuss more special cases in EDM Models and troubleshooting failing mappings
and also a sample project for EDM Friendly Feeds Mappings.
Comments
Anonymous
March 28, 2009
PingBack from http://www.anith.com/?p=23990Anonymous
March 28, 2009
Thank you for submitting this cool story - Trackback from DotNetShoutoutAnonymous
March 29, 2009
Phani, a member of the data services team, has continued his great series on Web Friendly Feeds withAnonymous
July 02, 2009
Is there no way to simply have ADO.NET Data Services simply return a POX feed? As far as I'm aware, you can currently only return JSON and Atom. Is there a way to get ADO.NET Data Services to be fully plain-old-xml, so that the GET requests by default receive POX and when doing the HTTP INSERT/POST/DELETE, we send POX to the service?