Problem statement
Today in Astoria if you have two entity types that are functionally in a containment relationship, you still see a top-level entity set for the instances of the contained type, and further the URLs for instances of the contained types that go through the parent type contain redundant information.
For example, assuming a schema where Order Lines (type “OrderLine”, entityset “OrderLines”) live within Orders(type “Order”, entityset “Orders”), two issues surface as shown below (points a & b). Additionally, assume that Orders have an “id” member that is the key, and that OrderLines has id and orderId (container id) members that form a composite key.
a) entities are always addressable through top-level entitysets, e.g. /OrderLines(1,2)
b) deep addressing through container has redundant information, e.g. /Orders(1)/OrderLines(1,2) (the container id, “1”, is repeated)
We have received feedback that this does not work for some scenarios in which people want to use Astoria. Continuing the example above where OrderLines are strictly contained within Orders, it does not feel right to have top-level access to it, even a composite key. On the other hand, containment where keys of the contained type repeat within different parents is something that the EDM currently does not model.
We need a solution that forces access to contained instances through its parents, does not require redundant specification of keys, and does not deviate from our data model.
Proposal
We define two new features to handle the general scenario of composition:
The first feature eliminates the need for redundant keys specifically for children that have a composite key where part of the key is the complete parent key. The model metadata needs to indicate that this is a parent-child relationship. For the case of the EDM metadata (entity framework), we’ll annotate the CSDL with attributes [1] and for the case of CLR object models we’ll use an attribute as in the following example:
class MyData
{
public IQueryable<Order> Orders;
[CanonicalAccessPath(Parent = “Orders”,
ParentNavigationProperty = “OrderLines”,
KeyMapping = { id, orderId},
]
public IQueryable<OrderLine> OrderLines;
}
Once an entity-set has been annotated like this, the keys of the parent are no longer required, so what was /Orders(1)/OrderLines(1,2) becomes /Orders(1)/OrderLines(2), where the key that goes in /OrderLines can repeat across different containers because it’s implicitly scoped to the other parts of the key that flow from the parent. URI construction rules to support this are:
· Child relationships can be chained to build multi-level containment arrangements.
· URIs with compound keys require the key to be specified using name/value pairs consisting of the property name of the key followed by its value
Example: /Orders(1)/Es(orderId=1,id=2)
· For non-compound keys (ex. /Customers(1) ) the key/value format is allowed, but is not the canonical form
Example: /Customer(1) is legal and canonical, /Customer(Key=1) is also legal, but not canonical
· The canonical form of containment URIs is the representation which does NOT include the parent key
Example: given /Orders(1)/OrderLines(orderId=1,id=2) & /Orders(1)/OrderLines(2) from above, the canonical URI is: /Orders(1)/OrderLines(2)
· While dropping the parent key results in the canonical URI, the “full form” (ie. /Orders(1)/OrderLines(orderId=1,id=2)) is allowed
From the Astoria runtime perspective, this means 2 things:
-the URL translator will know about this annotations in the sets, and whenever we’re translating a URL and hit an entity set with an access path marker we’ll simply flow the key values from the parent. This means that at the query expression tree level there will be no noticeable difference, which scopes the change to URL translation and metadata handling only.
-the metadata generation will include an annotation to indicate that a given entity set contains child resources.
The second feature is the ability to hide a top-level set and re-direct the canonical URL to an alternate path that goes through a parent. This is annotated with the CanonicalAccessPath attributes (or in CSDL via custom annotations[1]). Obviously you can have only one of these. Example (different from previous in green):
class MyData
{
public IQueryable<Order> Orders;
[CanonicalAccessPath(Parent = “Orders”,
ParentNavigationProperty = “OrderLines”,
KeyMapping = { id, orderId},
TopLevelAccess = false)]
public IQueryable<OrderLine> OrderLines;
}
The effect of applying this attribute is two-fold:
· the top-level entityset is no longer accessible (specified via TopLevelAccess=false), so something like /OrderLines(orderId=1,id=2) would result in a 404
· the presence of the CanonicalAccessPath attribute means the canonical URLs in the serializer are generated using the parent container, so the canonical form of /OrderLines(orderId=1,id=2) becomes /Orders(1)/OrderLines(2)
NOTE: An additional attribute named AccessPath (ie. no “Canonical” prefix) is defined which enables the specification of additional routes to a resource just as is done with the CanonicalAccessPath attribute; however, paths defined with the AccessPath attribute do NOT represent the canonical path to the resource.
Note that since the keys of the parent are always entirely contained within the child keys we never need additional queries to build the full nested URL.
A side-effect of this feature is that clients that are metadata-driven need to understand the special annotation to know that they need to generate URLs for an entity marked this way in a special way
Notes:
[1] Two annotations will be defined in CSDL which map to the information conveyed by the CanonicalAccessPath and AccessPath attributes noted in the document. The attributes will be:
· On an EntitySet element: TopLevelAccess=true|false
· One a NavigationProperty element: AccessPath=Canonical|NonCanonical
We look forward to hearing what you think of this approach…
Happy Holidays,
-Mike Flasko
Program Manager, ADO.NET Data Services (“Project Astoria”)
This post is part of the transparent design exercise in the Astoria Team. To understand how it works and how your feedback will be used please look at this post.
0 comments