I was experimenting recently with the .Net implementation of OData and ran across one of my pet peeves. “Magic Strings”. Apparently, the .Net community’s definition of magic strings is close but seems slightly different from Wikipedia. Therefore the magic strings I’m talking about here are what you’ll find on such posts as “Functional .Net – Lose the Magic Strings.”
I don’t want to get into the magic string debate here, just that I want to snapshot this little helper (for when I need to remember to write it again and don’t want to “figure it out”). This is also not intended to be a complete overview of OData, but I will provide some getter starter links and tips (if you haven’t touched it).
Enough background show me the code: (scroll to the bottom if you don’t care about the post)
Let’s pretend we want to request a “Title” from the NetFlix OData api.
You can do this by going to the web browser and typing the following URL
Sweet. XML, yippie. Um, no thanks. Let’s try that again. Go download LinqPad (read up on using LinqPad for querying an OData store)
Once you’ve connected LinqPad to the NetFlix OData service (http://odata.netflix.com/catalog). Now we’re ready to play around. Our URL “query” above translates in to a C# LINQ statement that looks like the below in LinqPad.
(from title in Titles
select title).Take(1).Dump()
The .Dump() is a LinqPad extension method that displays the object in the results window.
If you execute this in LinqPad you will see some data about the first Title form the Netflix OData service. In the results window scroll all the way to the right. Notice all the properties that are supposed to be a Collection<T> but have no data? To retrieve these through OData you have to extend your LINQ query with the Expand(“{propertyName}”) method.
Let’s say we want to include AudioFormats collection when we ask for the first Title.
(from title in Titles.Expand("AudioFormats")
select title).Take(1).Dump()
Notice how we have to explicitly tell the OData service to include this property when we retrieve it form the service. Not only do we explicitly tell the property name, but it’s a magic string (gag, hack, baaa) none the less. If you click on “SQL” in LinqPad result window it will show the URL used for OData queries. Our URL shows the expanded property.
http://odata.netflix.com/catalog/Titles()?$top=1&$expand=AudioFormats
Now let’s pretend (just for the sake of pretending) that your front end application’s entire data access strategy was going to sit on top of OData. Not saying this is a good thing (or a bad thing). Just sayin…
If you have a fairly complex data model and each screen in your application requests slightly different data in a slightly different way, but in the end it all essentially comes down to a set of entities and their relationships. What would you do if you had to “.Expand” all those magic stringed property names. Now, I know we’re all great at search and replace (of the magic strings). However every little step along the way where I can avoid a refactor that will break every other screen in the app, well, I think I just might take that.
Now, if you change your LinqPad query from a “C# Expression” to a “C# Program”. Copy the helper class at the bottom of this post in to the bottom of the LinqPad code window. You can now write your linq statement as follows
(from title in Titles.Expand(x=> x.AudioFormats)
select title).Take(1).Dump();
Notice the switch from magic strings to an intellisense helping, refactoring safe lambda? This trick is not new. You’ll see it in many .Net open source projects such as mocking frameworks, asp.net MVC projects etc…
Just wanted to write this little goodie down for the next time I need it. Hope this helps someone else as well.
public static class ServiceQueryExtension
{
public static DataServiceQuery<T> Expand<T, TProperty>(
this DataServiceQuery<T> entities,
Expression<Func<T, TProperty>> propertyExpressions)
{
string propertyName = propertyExpressions.GetMemberName();
return entities.Expand(propertyName);
}
public static string GetMemberName<T, TProperty>(this Expression<Func<T, TProperty>> propertyExpression)
{
return ((MemberExpression)(propertyExpression.Body)).Member.Name;
}
}