Entity Set Resolver
Problem Statement:
In previous versions, the WCF Data Services .NET and Silverlight client libraries always assumed that all collections (aka entity sets) had the same base URI. This assumption was in place because the library used the URI provided in the DataServiceContext constructor to generate collection URIs using the OData addressing conventions. For example:
Base URI: https://localhost:1309/NorthwindDataService.svc/
Sample collection: Customers
Collection URI by convention: https://localhost:1309/NorthwindDataService.svc/Customers
This simple convention based approach works when all collections belong to a common root, but a number of scenarios, such as partitioning, exist where two collections may have different base URIs or the service needs more control over how URI paths are constructed. For example, imagine a case where collections are partitioned across different servers for load balancing purposes. The “Customers” collection might be on a different server from the “Employees” collection.
To solve this problem, we added a new feature we call an Entity Set Resolver (ESR) to the WCF Data Services client library. The idea behind the ESR feature is to provide a mechanism allowing the client library to be entirely driven by the server (URIs of collections in this case) response payloads.
Design:
For the ESR feature, we have introduced a new ResolveEntitySet property to the DataServiceContext class. This new property returns a delegate that takes the collection name as input and returns the corresponding URI for that collection:
public Func<string, Uri> ResolveEntitySet
{
get; set;
}
This new delegate will then be invoked when the client library needs the URI of a collection. If the delegate is not set or returns null, then the library will fall back to the conventions currently used. For example, the API on the DataServiceContext class to insert a new entity looks like:
public void AddObject(string entitySetName, Object entity)
Currently this API inserts the new entity by taking the collection/entitySetName provided and appending it to the base URI passed to the DataServiceContext constructor. As previously stated, this works if all collections are at the same base URI and the server follows the recommended OData addressing scheme. However, as stated above, there are good reasons for this not to be the case.
With the addition of the ESR feature, the AddObject API will no longer first try to create the collection URI by convention. Instead, it will invoke the ESR (if one is provided) and ask it for the URI of the collection. One possible way to utilize the ESR is to have it parse the server’s Service Document -- a document exposed by OData services that lists the URIs for all the collections exposed by the server. Doing this allows the client library to be decoupled from the URI construction strategy used by an OData service.
It is important to note that since the ESR will be invoked during the processing of an AddObject call, the ESR should not block. Building upon the ESR parsing the Service Document example above, a good way is to have the collection URIs stored in a dictionary where the key is the collection name and the value is the URI. If this dictionary is populated during the DataServiceContext initialization time, users can avoid requesting and parsing the Service Document on demand. In practice this means the ESR should return URIs from a prepopulated dictionary and not request and parse the Service Document on demand.
The ESR feature is not scoped only to insert operations (AddObject calls). The resolver will be invoked anytime a URI to a collection is required and the URI hasn’t previously been learnt by parsing responses to prior requests sent to the service. The full list of client library methods which will cause the client library to invoke the ESR are: AddObject, AttachTo, and CreateQuery.
Mentioned above, one of the ways to use the ESR is to parse the Service Document to get the collection URIs. Below is sample code to introduce this process. The code returns a dictionary given the Service Document URI. For the dictionary, the key is the collection name and the value is the collection URI. To see the ESR in use, check out the sources included with the OData client for Windows Live Services blog post.
public Dictionary<string, Uri> ParseSvcDoc(string uri) { var serviceName = XName.Get("service", "https://www.w3.org/2007/app"); var workspaceName = XName.Get("workspace", "https://www.w3.org/2007/app"); var collectionName = XName.Get("collection", "https://www.w3.org/2007/app"); var titleName = XName.Get("title", "https://www.w3.org/2005/Atom"); var document = XDocument.Load(uri); return document.Element(serviceName).Element(workspaceName).Elements(collectionName) .ToDictionary(e => e.Element(titleName).Value, e => new Uri(e.Attribute("href").Value, UriKind.RelativeOrAbsolute)); } |
While initializing the DataServiceContext, the code can also parse the Service Document and set the ResolveEntitySet property. In this case, ResolveEntitySet is set to the GetCollectionUri method:
DataServiceContext ctx = new DataServiceContext(new Uri("https://localhost:1309/NorthwindDataService.svc/")); ctx.ResolveEntitySet = GetCollectionUri; Dictionary<string, Uri> collectionDict = new Dictionary<string, Uri>(); collectionDict = ParseSvcDoc(ctx.BaseUri.OriginalString); |
The GetCollectionUri method is very simple. It just returns the value in the dictionary for the associated collection name key. If this key does not exist, the method will return null and the collection URI will be constructed using OData addressing conventions.
public Uri GetCollectionUri(string collectionName) { Uri retUri; collectionDict.TryGetValue(collectionName, out retUri); return retUri; } |
For those of you working with Silverlight, I’ve added an async version of the ParseSvcDoc. It parses the document in the same manner but doesn’t block when retrieving the Service Document. Check out the attached samples for this code.
Julian Lai
WCF Data Services, Program Manager
Comments
Anonymous
November 30, 2010
To you have maybe aby samples for creating such a partitioned service? :)Anonymous
November 30, 2010
To you have maybe any samples for creating such a partitioned service? :)Anonymous
November 30, 2010
To you have maybe any samples for creating such a partitioned service? :)Anonymous
November 30, 2010
Sorry for the duplicated comment, don't know what happend. You can delete 2 of them :)Anonymous
September 12, 2011
How do you get an DataService object with two entity sets? This write up makes me want it, but it is not enough info to use it.Anonymous
September 12, 2011
Does this happen on the client side or the server side? If it is on the client side then what is the point of it? I already have to put logic in there now to tell it which endpoint to go to, this just abstracts it a bit. If it is a client side deal then how can this "The idea behind the ESR feature is to provide a mechanism allowing the client library to be entirely driven by the server (URIs of collections in this case) response payloads. " be true. It seems to not be be driven by the server at all. Basically, this needs more discussion. Go over a real example and show how to do it. And provide some real example code on how to do it.