OPEN DATA PROTOCOL (ODATA)DEEP DIVE
A common, open, RESTful Web protocol for querying and updating data that provides a uniform way to unlock data and free it from silos that exist in applications today.
ODATA
= REST• Resource-oriented
o Entities modeled as URI-addressable Resourceso Relationships modeled as links (URIs)o CRUD = POST, GET, PUT/PATCH, DELETE
• Hypermedia-driveno Navigate from Service Document to Sets to Members to related items to…o Links in Payload for editing, navigation, operations, etc.
+ Data Model• URLs, operations, namespaces, derived from declarative model
+ Common Conventions• Common query string options• Representation within Formats
+ Common Formats• ATOM, JSON
USE OF HTTP
• HTTP Methods• GET, POST, PUT/PATCH, DELETE
• HTTP Headers• Content Negotiation
o Accept, Accept-Charset, Accept-Encoding, Accept-Languageo Content-Type, Content-Length, Content-Encoding, Content-
Language
• Concurrencyo Etags, If-Match, If-None-Match
• Etc…
• HTTP Result Codes• 2xx Success Codes• 3xx Redirection• 4xx Client Error Messages
ODATA FEATURE AREAS
• Versioning• Service Metadata• Data Requests• Data Modification• Relationships• Content Types• Extensibility
VERSIONING
PROTOCOL VERSIONING
• DataServiceVersion header• Describes the protocol version according to which
the request/response should be interpreted
• MaxDataServiceVersion Request Header• The maximum protocol version that the client can
accept
• MinDataServiceVersion Request Header• The minimum protocol version the client can
accept
-Clients SHOULD specify a MaxDataService-Services return a response formatted according to the latest protocol version the service supports that is less than MaxDataServiceVersion but at least MinDataServiceVersion
SCHEMA VERSIONING
• When a data model or other breaking change is made to the service, the service URL is generally versioned. For example:• odata.netflix.com/v1/Catalog • odata.netflix.com/v2/Catalog
SERVICE METADATA
SERVICE DOCUMENT
• Root of service exposes a “Service Document”• Describes collections of EntitiesGET /odata.netflix.com/v2/Catalog HTTP/1.1
<service xmlns="http://www.w3.org/2007/app" xmlns:app="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom" xml:base="http://odata.netflix.com/v2/Catalog/"> <workspace> <atom:title>Default</atom:title>-<collection href="Genres"> <atom:title>Genres</atom:title> </collection>-<collection href="Titles"> <atom:title>Titles</atom:title> </collection>-<collection href="TitleAudioFormats"> <atom:title>TitleAudioFormats</atom:title> </collection>-<collection href="TitleAwards"> <atom:title>TitleAwards</atom:title> </collection>-<collection href="People"> <atom:title>People</atom:title> </collection>-<collection href="TitleScreenFormats"> <atom:title>TitleScreenFormats</atom:title> </collection>-<collection href="Languages"> <atom:title>Languages</atom:title> </collection> </workspace></service>
ENTITY DATA MODEL
• Exposed on /$metadata• Describes Entity Model of Data Service• Entity Types• Complex Types• Association Types• EntityContainers
GET /odata.netflix.com/v2/Catalog/$metadata HTTP/1.1<edmx:Edmx xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" Version="1.0"> <edmx:DataServices m:DataServiceVersion="1.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"> <Schema xmlns="http://schemas.microsoft.com/ado/2008/09/edm" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" Namespace="Netflix.Catalog.v2"> +<EntityType Name="Genre"> +<EntityType Name="Title" m:HasStream="true"> +<ComplexType Name="BoxArt"> +<Association Name="Genre_Titles"> +<EntityContainer Name="NetflixCatalog" m:IsDefaultEntityContainer="true"> </Schema> </edmx:DataServices></edmx:Edmx>
ENTITY TYPE
• Named structures with keys• Support Inheritance• Properties• String, Integer, Boolean, DateTime, Spatial
datatypes • Collections, Complex Types• Relationship Properties
• May be marked as "Open"• May return "dynamic" properties not described in $metadata
<EntityType Name="Genre"> <Key> <PropertyRef Name="Name"/> </Key> <Property Name="Name" Type="Edm.String"/> <NavigationProperty Name="Titles" Relationship="Self.Genre_Titles" ToRole="Genre_Titles_Target" FromRole="Genre_Titles_Source"/></EntityType>
COMPLEX TYPE
• Named Structures w/o keys• Support Inheritance• Don't support relationships• Because they don't have an independent ID
<ComplexType Name="BoxArt"> <Property Name="SmallUrl" Type="Edm.String"/> <Property Name="MediumUrl" Type="Edm.String"/> <Property Name="LargeUrl" Type="Edm.String"/></ComplexType>
ASSOCIATIONS
• Define relationships (navigation paths) between Entities• Bi-directional• Specify Cardinality of each end• Can also impose integrity constraints
<Association Name="Genre_Titles"> <End Type="Self.Genre" Multiplicity="*" Role="Genre_Titles_Source"/> <End Type="Self.Title" Multiplicity="*" Role="Genre_Titles_Target"/></Association>
ENTITY CONTAINER
• Contains• EntitySets – collections of Entity Instances• AssociationSets – relationships between entities• FunctionImports – Actions/Functions exposed by
service
<EntityContainer Name="NetflixCatalog" m:IsDefaultEntityContainer="true"> <EntitySet Name="Genres" EntityType="Self.Genre"/> <EntitySet Name="Titles" EntityType="Self.Title"/> <AssociationSet Name="Genre_Titles" Association="Self.Genre_Titles"> <End Role="Genre_Titles_Source" EntitySet="Genres"/> <End Role="Genre_Titles_Target" EntitySet="Titles"/> </AssociationSet></EntityContainer>
DATA REQUESTS
QUERYING DATA
• Querying a Set• GET /v2/Catalog/Genres HTTP/1.1
• Requesting an Individual Entity by ID• GET /v2/Catalog/Genres('Adventures') HTTP/1.1
• Requesting an Individual Property• GET /v2/Catalog/Genres('Adventures')/Name HTTP/1.1
• Requesting an Individual Property Raw Value• GET /v2/Catalog/Genres('Adventures')/
Name/$value HTTP/1.1
$FILTER
• Basic predicates, built-in functions• GET /v2/Catalog/Titles?$filter=Name eq
'The Sting' HTTP/1.1
• Filter on Properties of a derived type• Only returns entities of that type that match the predicate• GET
/v2/Catalog/Awards?$filter=Netflix.AcademyAward/Category HTTP/1.1
• Query based on collection Membership using Any/All• GET /v2/Catalog/Titles?$filter=Genres/Any(g:g/Name eq
'Adventure') HTTP/1.1
OPERATORS
• Logical Operators• eq, ne, gt, ge, lt, le, and, or, not
• Mathematic Operators• add, sub, mult, div, mod
• Grouping Operator• ()
• NULL literal• null
BUILT-IN FUNCTIONS
• String Functions• substringof, endswith, startswith, length, indexof, substring,
tolower, toupper, trim, concat
• DateTime Functions• year(), month(), day(), hour(), minute(), second()
• Math Functions• round(), floor(), ceiling()
• Type Functions• isof()
• Casting• Insert type in path
• Geo Functions• geo.length, geo.intersects, geo.distance
$ORDERBY
• Comma separated list of properties, expressions• GET /v2/Catalog/Titles?$orderby=
AverageRating,ReleaseYear HTTP/1.1
• Can specify asc (default) or desc• GET /v2/Catalog/Titles?$orderby=AverageRating desc
HTTP/1.1
$TOP/$SKIP
• Enables Client-side paging through large result sets• GET /v2/Catalog/Titles?$top=10&$orderby=
AverageRating desc HTTP/1.1• GET /v2/Catalog/Titles?$top=10&$skip=10 &$orderby=
AverageRating desc HTTP/1.1
• If no $orderby, server guarantees stable ordering
$EXPAND
• Specify comma separated list of navigation property paths to include in response• GET /v2/Catalog/Titles?$expand=Cast,Awards HTTP/1.1
• Result are returned inline according to the appropriate format
$SELECT
• Narrow the set of fields returned• GET /v2/Catalog/Titles?$select=Name,Synopsis,Rating
HTTP/1.1
• Related properties must be specified in $expand• GET /v2/Catalog/Titles?$select=Name,Cast
/Name&$expand=Cast HTTP/1.1
• Can include all properties using *• Does not include navigation properties• GET /v2/Catalog/Titles?$select=* HTTP/1.1
$COUNT/$INLINECOUNT
• Get just the count• Can include additional query operators• GET /v2/Catalog/Titles/$count HTTP/1.1
• Include the count with the results• $inlinecount ignores $top/$skip but includes
$filter• GET /v2/Catalog/Titles?$inlinecount=allpages&$top=10
HTTP/1.1
$FORMAT
• Specify a particular format• "atom"
o application/atom+xml, o application/atomsvc+xml for service document
• "json"o application/json
• "xml"o application/xml
• Overrides content type specified in headers
SERVER DRIVEN PAGING
• Server controls the maximum number of records to return at a time• Each "page" of results includes a "next link"
for retrieving the next page of results• Pages can vary in size; some may be blank• i.e., return records as computed or across a
federation
DATA MODIFICATION
INSERT
• POST entity to the EntitySet collection• Returns inserted entity• Use PREFER header to request no content
returned
• Media Resources have special rules according to AtomPub:• POST media resource to the collection• Returns Media Link Entry• Update the Media Link Entry
UPDATE
• PUT to the edit-link to replace• Unspecified values are set to null or default
• PATCH to the edit-link to affect only specified values• Unspecified values are left unchanged
• Use PREFER header to request content returned• Use entity etag in if-match for concurrency
control• Obtained from header or payload
DELETE
• DELETE to the edit-link to delete• Use entity etag in if-match for concurrency
control• Obtained from header or payload
BATCH REQUESTS
• "Batch" multiple statements in a single multi-part mime request• Results for each statement returned in a
multi-part mime response• Multiple Data Modification statements may
be grouped in a single "ChangeSet"• ChangeSets are atomic• Reference the results of other statements in the
changeseto i.e., add a child to an added parent
• May have multiple ChangeSets per Batch
RELATIONSHIPS
REQUESTING RELATIONSHIP LINKS• Use $links to request the relationship links
for a particular navigation property• Results are returned as an array of URIso GET /v2/Catalog/Titles/Genres('Adventures')/$
links/Titles HTTP/1.1
CREATING LINKS TO NEW ENTITIES• Create a new Entity with a new related entity
by POSTing the entity with the related entity as inline content ("Deep Inserts")• Create a new entity related to an existing
entity by POSTing to the relationship collection of the existing entity
• POST /v2/Catalog/Titles/Genres('Adventures')/$links/Titles HTTP/1.1 <entry>…</entry>
CREATING LINKS TO EXISTING ENTITIES• Create a relationship between two existing
entities by POSTing the URL of the one entity to the appropriate $links collection of the other entityo POST /v2/Catalog/Titles/Genres('Adventures')/$links/Titles
HTTP/1.1 <uri>http://odata.netflix.com/v2/Catalog/Titles('13kbf')</uri>
DELETING LINKS
• Remove a relationship by sending a DELETE request to the $links collection specifying the url to removeo DELETE /v2/Catalog/Titles/Genres('Adventures')/$links/Titles
HTTP/1.1 <uri>http://odata.netflix.com/v2/Catalog/Titles('13kbf')</uri>
CONTENT TYPES (FORMATS)
ATOM FORMAT
• Multiple entities represented as a <feed>• Count optionally returned in <metadata:count> element• Next link returned as <link> element
• Each entity represented as an <entry>• <id> is unique id• <category> specifies type• Self, edit <link>s• <link> element for Relationships
o Inline content as child <metadata:inline> element containing feed or element
• <link> element for editing media
• Properties nested in <metadata:properties> element• Individual properties namespace qualified with data namespace• For Media Resources, <content> has a src property to the media stream
o <metadata:properties> is a sibling of <content>
• For non-Media Resources, <metadata:properties> is a child of <content>
• Functions/Actions returned as <metadata:function> and <metadata:action> elements
<?xml version="1.0" encoding="utf-8" ?><feed xml:base="http://odata.netflix.com/v2/Catalog/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom"> <title type="text">Genres</title> <id>http://odata.netflix.com/v2/Catalog/Genres/</id> <updated>2012-07-26T18:37:18Z</updated> <link rel="self" title="Genres" href="Genres" /> <m:count>326</m:count> <entry> <id>http://odata.netflix.com/v2/Catalog/Genres('Adventures')</id> <title type="text">Adventures</title> <updated>2012-07-26T19:08:35Z</updated> <author> <name/> </author> <link rel="edit" title="Genre" href="Genres('Adventures')" /> <link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Titles" type="application/atom+xml;type=feed" title="Titles" href="Genres('Adventures')/Titles" /> <category term="Netflix.Catalog.v2.Genre" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" /> <content type="application/xml"> <m:properties> <d:Name>Adventures</d:Name> </m:properties> </content> </entry> <link rel="next" href="Genres/?$skiptoken='Adventures'" /></feed>
ATOM PAYLOAD - EXAMPLE
JSON VERBOSE FORMAT
• Results wrapped in a "d" wrapper• Multiple entities returned as JSON Array property named
"Result"o Optionally has __Count property for number of elements in query
result (across all pages)o Optionally has "__next" property for next link
• Each Entity has o A "__metadata" property containing:
– URI– Type– Media Link, edit-media link, content type, etag for media resources
o A "__deferred" property for each un-expanded relationship containing the URL for the related entity/collection
o Properties a Name/Value pairs
• Each Complex Type has a "__metadata" property containing the Type
JSON PAYLOAD - EXAMPLE{ "d" : { "results": [ { "__metadata": { "uri":"http://odata.netflix.com/Genres('Adventures')", "type": "Netflix.Catalog.v2.Genre" }, "Name": "Adventures", "Titles": { "__deferred": { "uri":"http://odata.netflix.com/Genres('Adventures')/Titles"} } } ], "__count": "326", "__next": " Genres/?$skiptoken='Adventures'" }}
NEW JSON ODATA FORMAT
• Effort to simplify JSON• More readable JSON format• Reduce size• Preserve OData-ness
o Still Hypermedia-driven
• Uses Namespacing Mechanism
• Removes almost all metadata from payloads• Calculates values from conventions or templates in
metadata• 80-90% more efficient than Atom
• Future contribution to TC
NEW JSON PAYLOAD - EXAMPLE{ "odata.metadata":"http://odata.netflix.com/$metadata#Netflix/Genres", "count":"830", "value": [ { "Name": "Adventures", } ], "nextLink":" http://odata.netflix.com/Genres('Adventures')/Titles "}
EXTENSIBILITY
CUSTOM ACTIONS
• Side-effecting operations that may or may not return a value• May be exposed on instances (Hypermedia-
driven)• Use entity etag in if-match for concurrency
control• Obtained from header or payload
CUSTOM FUNCTIONS
• Extend query language• May be exposed on instances (Hypermedia-
driven)• Use entity etag in if-match for concurrency
control• Obtained from header or payload
VOCABULARIES
An OData Vocabulary defines a shareable set of Annotations that may be applied to metadata or instance data
• Extended Property Informationo Units of measuremento Read-only, read-write
• Ontologieso This entity represents the common concept of a person
• Validationo This integer property is between 10 and 20
• Display hintso Caption field, grouping, navigation
• Service Capabilitieso Query limitations, etc.
• Extensiono Analytics
ANNOTATIONS CONSIST OF:
• A Target: The construct against which the annotation is appliedo May be any Model Construct (type, property, etc.)
• A Term: The global name of the annotationo For example, org.odata.display.Title
• A Value: The value for the annotationo May be a static value, path, or expression
A Vocabulary is a set of terms in a common namespace
ANNOTATIONS MAY…
• Be defined in a common format• Machine readable (i.e., for validation)
• Be embedded in a model• To define extended metadata
• Be applied through an external annotation application file• Externally annotate existing services
• Be applied to Instance Data• Information that may vary per instance
TYPES OF ANNOTATIONS
• ValueTerm• A single annotation term applied to a type,
property, association, etc.
• Complex TypeTerm• Generally used to describe an is-a relationship• Relationships may be modeled as properties of
individual or collections of other complex type terms
• Entity TypeTerm• Most prescriptive; define keys, relationships
UNANNOTATED ENTITY TYPE
<Schema xmlns="http://schemas.microsoft.com/ado/2008/09/edm" Namespace="Sales">
<EntityContainer Name="Sales"> <EntitySet Name="Customer" EntityType="Sales.Customer"/></EntityContainer><EntityType Name="Customer"> <Key> <PropertyRef Name="CustomerID"/> </Key> <Property Name="CustomerID" Type="Edm.Integer" /> <Property Name="FirstName" Type="Edm.String" /> <Property Name="LastName" Type="Edm.String" /> <Property Name="Email" Type="Edm.String" /></EntityType>
</Schema>
VOCABULARY DEFINITIONS
• “Person” vocabulary TypeTerm:
• Display Vocabulary Value Terms
<Schema xmlns="http://schemas.microsoft.com/ado/2008/09/edm" Namespace="myorg.schemas.person"><ComplexType Name="Person"> <Property Name="SSN" Type="Edm.String" /> <Property Name="GivenName" Type="Edm.String" /> <Property Name="FamilyName" Type="Edm.String" /> <Property Name="Gender" Type="Edm.String" /> <Property Name="Email" Type="Edm.String" /></ComplexType>
</Schema>
<Schema xmlns="http://schemas.microsoft.com/ado/2008/09/edm" Namespace="org.odata.display"><ValueTerm Name="Caption" Type="Edm.String"/><ValueTerm Name="Description" Type="Edm.String"/><ValueTerm Name="ImageUrl" Type="Edm.String"/>
</Schema>
EXTERNAL ANNOTATION FILE
<edmx:Reference Url="http://www.odata.org/vocabularies/display/v1" /><edmx:Reference Url="http://www.myorg.com/schemas/person" /><edmx:Reference Url="http://www.myservice.com/sales.svc/$metadata" /><Schema xmlns="http://schemas.microsoft.com/ado/2008/09/edm" Namespace="Person"><Using Namespace="myorg.schemas.person" Alias="PersonVocabulary" /><Using Namespace="org.odata.display" Alias="Display" /><Using Namespace="Sales" Alias="Sales" /><Annotations Target="Sales.Customer"> <ValueAnnotation Term="Display.Caption" Path="CustomerID"/> <TypeAnnotation Term="PersonVocabulary.Person"> <PropertyValue Name="GivenName" Path="FirstName" /> <PropertyValue Name="FamilyName" Path="LastName" /> <PropertyValue Name="Email" Path="Email" /> </TypeAnnotation>
</Annotations></Schema>
ANNOTATED METADATA<edmx:AnnotationsReference Url="http://odata.org/vocabularies/display/v1"> <Include TermNamespace ="myorg.schemas.person"/> </edmx:AnnotationsReference>
<edmx:AnnotationsReference Url="http://odata.org/vocabularies/display/v1"> <Include TermNamespace ="org.odata.display"/>
</edmx:AnnotationsReference><Schema xmlns="http://schemas.microsoft.com/ado/2008/09/edm" Namespace="Sales">
<Using Namespace="myorg.schemas.person" Alias="PersonVocabulary" /><Using Namespace="org.odata.display" Alias="Display" /><EntityContainer Name="Sales"> <EntitySet Name="Customer" EntityType="Sales.Customer"/></EntityContainer><EntityType Name="Customer"> <Key> <PropertyRef Name="CustomerID"/> </Key> <Property Name="CustomerID" Type="Edm.Integer"> <ValueAnnotation Term="Display.Title" /> </Property> <Property Name="FirstName" Type="Edm.String" /> <Property Name="LastName" Type="Edm.String" /> <Property Name="Email" Type="Edm.String" /> <TypeAnnotation Term="PersonVocabulary.Person"> <PropertyValue Name="GivenName" Path="FirstName" /> <PropertyValue Name="FamilyName" Path="LastName" /> <PropertyValue Name="Email" Path="Email" /> </TypeAnnotation>
</EntityType></Schema>
INSTANCE ANNOTATIONS
{ "odata.json.metadata": "http://myservice.com/sales.svc/$metadata#Sales.Customer", "results" : { “CustomerID" : 123, "FirstName" : "John", "LastName" : "Public", "Email" : "[email protected]" , "@CustomerID" : { odata.org.display.readonly : true } }}
ANNOTATION/METADATA REFERENCING• AnnotationReference• Specifies a separate document that annotates this
model• Constrain to individual namespaces, qualifiers
through "Include"
• Reference• Brings a separate model "into scope" for use
within this model
SUMMARY
• OData =REST o Entities, Entity Sets as URI addressable Resourceso Hypermedia Driveno CRUD = POST, GET, PUT/PATCH, DELETE
+ Data Modelo Entity Relational Model
+ Common Conventionso Query Syntax, Server Driven Paging, …
+ Common Formatso Atom, JSON
+ Extensibilityo Custom Functions, Actions, Shared Annotations
BACKUP
{ "d": { "results": [ { "__metadata": { "uri": "http://services.odata.org/OData/OData.svc/Products(0)", "type": "ODataDemo.Product" }, "ID": 0, "Name": "Bread", "Description": "Whole grain bread", "ReleaseDate": "\/Date(694224000000)\/", "DiscontinuedDate": null, "Rating": 4, "Price": "2.5", "Category": { "__deferred": { "uri": "http://services.odata.org/OData/OData.svc/Products(0)/Category" } }, "Supplier": { "__deferred": { "uri": "http://services.odata.org/OData/OData.svc/Products(0)/Supplier" } } }, { "__metadata": { "uri": "http://services.odata.org/OData/OData.svc/Products(1)", "type": "ODataDemo.Product" }, "ID": 1, "Name": "Milk", "Description": "Low fat milk", "ReleaseDate": "\/Date(812505600000)\/", "DiscontinuedDate": null, "Rating": 3, "Price": "3.5", "Category": { "__deferred": { "uri": "http://services.odata.org/OData/OData.svc/Products(1)/Category" } }, "Supplier": { "__deferred": { "uri": "http://services.odata.org/OData/OData.svc/Products(1)/Supplier" } } }], "__count": 9 }}
{ "odata.json.metadata": "http://services.odata.org/OData/OData.svc/$metadata#ODataDemo.DemoService.Products", "results": [ { "ID": 0, "Name": "Bread", "Description": "Whole grain bread", "ReleaseDate": "\/Date(694224000000)\/", "DiscontinuedDate": null, "Rating": 4, "Price": "2.5" }, { "ID": 1, "Name": "Milk", "Description": "Low fat milk", "ReleaseDate": "\/Date(812505600000)\/", "DiscontinuedDate": null, "Rating": 3, "Price": "3.5" }], "__count": 9}
OData.svc/Products?$top=2&$inlinecount=allpages&$format=json
QUERY
• $filter• Basic predicates, built-in functions
• $sort• Properties, expressions
• $select• Narrow the set of fields returned
• $top/$skip• Client-side paging
• $expand • Include related entities
• $count/$inlinecount• Include count of entities
• Server Driven Paging
ENTITY DATA MODEL• Entity Types
• Named structures with keys• Support Inheritance• May be Open
• Properties• String, Integer, Boolean, DateTime,
Spatial datatypes • Collections, Complex Types• Relationship Properties
• Complex Types• Named Structures w/o keys• Can’t have relationships
to ComplexTypes
• Associations• Expose navigation paths
• EntityContainer• Contains EntitySets,
AssociationSets, FunctionImports
<EntityType Name="Genre"> <Key> <PropertyRef Name="Name"/> </Key> <Property Name="Name" Type="Edm.String"/> <NavigationProperty Name="Titles" Relationship="Self.Genre_Titles" ToRole="Genre_Titles_Target" FromRole="Genre_Titles_Source"/></EntityType>
<ComplexType Name="BoxArt"> <Property Name="SmallUrl" Type="Edm.String"/> <Property Name="MediumUrl" Type="Edm.String"/> <Property Name="LargeUrl" Type="Edm.String"/></ComplexType><Association Name="Genre_Titles"> <End Type="Self.Genre" Multiplicity="*" Role="Genre_Titles_Source"/> <End Type="Self.Title" Multiplicity="*" Role="Genre_Titles_Target"/></Association><EntityContainer Name="NetflixCatalog"> <EntitySet Name="Genres" EntityType="Self.Genre"/> <EntitySet Name="Titles" EntityType="Self.Title"/> <AssociationSet Name="Genre_Titles" Association="Self.Genre_Titles"> <End Role="Genre_Titles_Source" EntitySet="Genres"/> <End Role="Genre_Titles_Target" EntitySet="Titles"/> </AssociationSet></EntityContainer>