Archive for the 'ADO.NET Data Services' Category



30
Dec

ADO.NET Data Services Part 7: Data Modification - Client

As most mornings start out, Phillip listens to a slew of customer voicemails he received overnight. The messages range anywhere from over-the-top praise, to legitimately startling threats. This morning though, Phillip got a call that wasn’t like any other call he has gotten before. In all his years of humbly playing the role of a cell phone reseller, Phillip has yet to have had his manhood called out by a client, until today…

“Hey Phil! I’m loving this service you setup dude, but I found a couple typos in your data. I would have fixed it for you, but your stupid doesn’t support modification. I don’t mean to sound like a negative Nancy, but if you were a real man, you’d go get a real service.”

Wiping the “sweat” from his eyes, Phillip starts breathing a little heavy, and insists that his data service is just as good as any service out there. “Who does this guy think he is?”, exclaims Phillip, “my service doesn’t need to be updatable to be cool!” Or does it?

It suddenly becomes clear that Phillip spent a little too much time getting his query on, and completely forgot to look into whether ADO.NET Data Services offered any functionality in terms of data modification. Well, today is the day to delve into that realm, and see just how brawny your services can be.

As you may recall in article #4, it was mentioned that a successful service client needed 4 things (at least). The first 3 have already been covered, and were beautifully accomplished using the WebDataContext class. The fourth item, change tracking, has yet to be checked, and is currently our feature of need. Reason would lend us to believe that because the WebDataContext came to the rescue with object deserialization, lazy loading, and identity tracking, it would also have to provide change tracking, otherwise it just wouldn’t be the knight in shining armor we once thought it was.

Well, to be honest, when we covered the WebDataContext class back in part 4, we barely even broke the surface. In fact, there are about twice as many methods we skipped, compared to the ones we’ve covered thus far. And yes, you’ve guessed it, those missing methods pertain to data modification.

Lil Jon’s claim to fame is “gettin crunk“, which is cool I guess, but as a computer nerd, I’d rather get CRUD. The R has been done already, so let’s see how we can align methods of the WebDataContext class to the C, U, and D.

C is for Create

<thick_sarcasm>From what I’ve been told, it is necessary to be able to create data in the real-world. I personally think that sounds crazy, but apparently it’s true.</thick_sarcasm> You recall in previous posts, we created an entire client-side model, that we used to interact with the data service. Wouldn’t it make sense to leverage that same model to create new instances of data? Sho ’nuff! If the client wants to create a new Carrier, they should be able to use the same Carrier class that was already generated.

Carrier newCarrier = new Carrier();
newCarrier.Name = "Lost In Trangent Mobile";

Perfect, now what do we do with this thing?

context.AddObject("Carriers", newCarrier);

That will add the new carrier object, to our WebDataContext (unit of work). Note that the AddObject method takes the entity set name, not the entity name, which is why I’m specifying “Carriers”, not “Carrier”. You may also note that I didn’t set the Id property of the carrier. That is because the Id is generated for me in the database.

Does the carrier get immediately added to the data service’s data store with a call to AddObject? Well if it did, it wouldn’t be much of a unit of work now would it? It would be more of an individual bunch of work items :) So now that we’ve added a new entity to our context, how do we submit the unit for persistence?

context.SaveChanges();

Well that was easy. All we had to do was create an instance of the Carrier class, assign the proper values, add it to the context, and save the changes. Pretty streamlined eh? Even cooler is that after the call to SaveChanges, all added entities attached to the context are refreshed so that any data that was modified as a result of a trigger or newly generated value will be present in your application. This means that even though we didn’t set an Id on the above Carrier instance, after the call to SaveChanges, the database generated value will be used to fill the Id property and brought back to the client. This behavior relies on the WebDataContext’s MergeOption property being set to OverwriteChanges.

context.MergeOption = MergeOption.OverwriteChanges;

Setting the MergeOption to OverwriteChanges is telling the WebDataContext that you want it to overwrite any local property values with new values that were generated as a result of a call to SaveChanges. Setting MergeOption to anything else would effectively disable the aforementioned behavior, and the WebDataContext wouldn’t refresh your generated identity properties for you, so make sure you are using the correct MergeOption value when performing data modification operations.

If we needed to add a series of carriers, we could have, and bundled them all into a single persistence request. If you are adding a good amount of data to the context, a call to SaveChanges might block the current thread for longer than you’d like. In that case you also have the BeginSaveChanges/EndSaveChanges method pair. They work just like every other Begin/End method set in the .NET framework, and as such would be boring to cover in extent here.

context.BeginSaveChanges(callback =&gt; { context.EndSaveChanges(callback); }, null);

U is for Update

Did anyone notice that I spelled “Tangent” wrong in the Carrier we added above? I’d really feel silly if that got seen by someone, so we’ve better retreive that data and modify before anyone notices.

Carrier litCarrier = (from carrier in context.Carriers
                      where carrier.Id == 67
                      select carrier).Single();   
 
litCarrier.Name = "Lost In Tangent Mobile";   
 
context.UpdateObject(litCarrier);
context.SaveChanges();

Easy enough? I was very tempted to draw that out, but I didn’t want to insult the intelligence of my readers. Updating data is just as easy and intuitive as you would think. The data refresh feature mentioned above works the same way with updated entity instances. If any data was modified within the database on the above carrier as a result of the call to SaveChanges, those changes will be put into the carrier instance (as long as the proper MergeOption is set).

Note also, instead of having to query the service for the carrier we wanted to modify, we could have just as easily reconstructed it, attached it to the context, and marked it as updated.

Carrier sprint = new Carrier();
sprint.Id = 1;
sprint.Name = "Sprint!";
context.AttachObject("Carriers", sprint);
context.UpdateObject(sprint);
context.SaveChanges();

If you call UpdateObject, without calling AttachObject, an exception will be thrown. This is because UpdateObject works only with entity instances that are already being tracked by the context. When you retrieve an entity instance from the context via a query (like we did above), that object is automatically attached to the context for you. By using this method we are spared a hit to the service, but are required to have to know the entire object graph (and its values). In this case, it’s pretty simple to reconstruct because we only have two properties, but if you had a complex object, a call to the service would probably make more sense than trying to manually re-build the entity. I only bring this up so that you are aware of the option. In some cases, it may make sense to save some bandwidth and re-build lookup entities, instead of querying for them.

Now you may be looking at that code and thinking “Wait a second, why did you have to call UpdateObject? If the WebDataContext class is providing change tracking, shouldn’t it be smart enough to know that it owns that Carrier instance, and that its Name property has changed?” That would be a valid point, since both LINQ To SQL, and the Entity Framework provide that ability, and folks have no doubt become accustomed to it. Well, the question is: how would you expect that to work with the generated client model we have?

LINQ To SQL works because the generated model classes implement the INotifyPropertyChanging/INotifyPropertyChanged pair. In addition, every property setter within the generated model classes includes code that fires the events of the aforementioned interfaces. This allows the DataContext to observe its owned entity instances, and take note of any changes made to them.

The Entity Framework works essentially the same way, but a little more elegantly. It also uses the INotifyPropertyChanging/INotifyPropertyChanged pair, but instead of having every generated model class implement them, the StructuralObject class does that for us. StructuralObject is the base class for EntityObject, which is the base class for every generated model class. As you could expect, the generated model class’s property setters also have code for firing the respective events, notifying the ObjectContext of any changes, so it can track them.

Do the generated model classes for a data service implement the INotifyPropertyChanging/INotifyPropertChanged pair? No. Do they include any boilerplate notification code in their property setters? Well, yeah, but not the kind we’re looking for. Therefore it’s very clear why LINQ To SQL and the Entity Framework provide such a rich change tracking context, while ADO.NET Data Services doesn’t, and requires you to call UpdateObject.

Is this a negative? Well, it depends on how you look at it. As I’ve said a million times, the more POCO you want, the less functionality you get. Data service models are as plain and simple as you can get (well almost), and therefore lack certain features. I personally think the call to UpdateObject adds a nice explicitness to the code.

D is for Delete

You know, now that I think about it, adding a Lost In Tangent carrier was a bad idea. I better delete that thing before anyone sees is.

Carrier litCarrier = (from carrier in context.Carriers
                      where carrier.Id == 67
                      select carrier).Single();   
 
context.DeleteObject(litCarrier);
context.SaveChanges();

Enough said? With that, we have completed the ABCs of CRUD with ADO.NET Data Services.

Now, the above examples left out one important scenario: modifying relational properties. When we created and modified the Carrier, all we had to worry about were scalar properties, but in the real-world, there are going to be tons of entity references. How does that work with data services? To illustrate this, let’s create an instance of the CellPhone entity.

CellPhone newPhone = new CellPhone();
newPhone.ModelName = "Best Phone Ever!";
newPhone.Megapixels = 2.1;
newPhone.Price = 328.34;
newPhone.Weight = 3;

So far so good. We also know that the CellPhone entity has two relational properties: Carrier, and Manufacturer. A carrier isn’t required, but a manufacturer is, so before we can add this phone to the context and save it, we have to associate it with a manufacturer. First, we need to retrieve the manufacturer entity instance that will own this phone, which for the sake of example will be Nokia.

Manufacturer nokia = (from manufacturer in context.Manufacturers
                      where manufacturer.Name == "Nokia"
                      select manufacturer).Single();

So we’ve got our new CellPhone instance, and we’ve got the correct Manufacturer entity instance, how do we associate the two?

newPhone.Manufacturer = nokia;
context.AddBinding(newPhone, "Manufacturer", nokia);

Unfortunately just setting the Manufacturer property isn’t enough. A call to the AddBinding method is also needed in order to let the WebDataContext know about the new association. This shouldn’t come as a surprise since we already knew that automatic change tracking wasn’t available for scalar properties, it certainly wouldn’t make sense for it to be available for relational ones. Once again, another extra line of code is necessary, but it does add explicitness and enable less code in the generated client classes. I’m really tempted to go on a tangent about how this works for LINQ To SQL and the Entity Framework, but not for ADO.NET Data Services, but this article is already getting long enough, so I’ll leave that determination up to you :)

In data service terms, a “binding” is an association between two related entities. The AddBinding method takes three parameters: the parent entity instance, the name of the parent entity’s property that is being bound to, and the child entity instance. In this case, we’re associating the Nokia Manufacturer instance with the new CellPhone entity. The WebDataContext also has a DeleteBinding method that takes the same parameters as the AddBinding method, and is responsible for clearing an entity association.

context.DeleteBinding(newPhone, "Manufacturer", nokia);

What has this ridiculously long article taught us? That we can take pride in our data services, knowing that they are just as good (and manly) as any other service out there. Keep your head up friends, pretty soon all the cool kids will envy you and your l337 data service skills ;)

So now that we’ve learned how to perform full data modification on a data service, only one question remains: how can we achieve this feat using the AJAX libraries we learned about in the last article? That will be the topic of the next article.

22
Dec

ADO.NET Data Service Aggregates

The Astoria team posted another article in their transparent design process series (which I absolutely love by the way). The design discussion essentially touches on the need to bring DDD’s aggregate pattern into both the EDM and data service, and make it a first class citizen. I think their approach is great and will enable a much richer/meaningful model. This just goes to show you that the team is going in the right direction.

Be sure to visit the team’s blog and leave some feedback on what you think. The Astoria team has made a great provision by opening up their design thoughts to the public. It would be a shame for us not to leverage it :)

20
Dec

Ode To Mike Taulty

As detailed and granular as I try to get with my posts on ADO.NET Data Services, I always seem to miss something important/cool. Luckily for me, Mike Taulty has been picking up a lot of the slack. He has been writing a ton of content around ADO.NET Data Services and is covering some great topics. One thing I appreciate about his writing style is that he isn’t satisfied with doing high-level overviews, he goes really deep into the code/concepts. I would suggest subscribing to his blog, because he churns out posts pretty fast, and you don’t want to get behind :)

Here are some great posts he’s made lately that are recommended reading:

  1. ADO.NET Data Services - Using LINQ to SQL rather than LINQ to Entities
  2. ADO.NET Data Services - Experimenting with .NET Client Access Whilst Sniffing the HTTP Traffic
  3. ADO.NET Data Services - Getting Started
19
Dec

ADO.NET Data Services Part 6: AJAX

Phillip gets a call today from a customer:

“Philly my boy, I love the service you setup. This data is going to jump start my website immediately! You know what though, I’ve been trying to get into this AJAX stuff, and as such I was curious whether or not that would work with your service? I also wanted to thank you for dinner last night, I had no idea how sweet you could be.”

Alright, so that call was half weird (alright, a lot weird), but half intriguing. Phillip had to ponder for a minute whether or not his cell phone data could in fact be consumed with JavaScript. He had been spending so much time and effort figuring out how to write client code in .NET (about 2 articles worth), he completely forgot about the possibility of customers wanting their web sites to consume his data client-side.

The ASP.NET AJAX client library does in fact contain the Sys.Net.WebRequest type, and since an ADO.NET Data Service is exposed through a simple REST interface, you could get away with using it to make simple service requests. That seems like a pretty poor option, as we’ve already gotten comfortable writing client code that had semantic value for working with data services (i.e. WebDataContext). Luckily for us we weren’t the only ones thinking that. The ADO.NET team got together with the ASP.NET AJAX team and said “Let’s merge our two worlds together and make something awesome!”

There is a new namespace in the ASP.NET AJAX client library: Sys.Data. This new namespace houses one particular object of interest to us: DataService. “What exactly is DataService and what can it do for me?”, you say. It is pretty much everything you’d need from a lightweight, JavaScript abstraction over a data service. Behind the scenes it’s actually using WebRequest, but at least we don’t have to worry about those details :)

“How do I get the DataService object into my app?” You simply add a script reference to your ScriptManager like so:

snag-0008.jpg

That will add the reference to the ADO.NET Data Service client library, and make the DataService object available for use.

“How did I use the Data Service?” Just like the WebDataContext, the DataService’s constructor takes the URL of the service:

astoria71.JPG

Pretty simple huh? Indeed. “How do you get data out of it?”

snag-0009.jpg

Notice a pattern? Consuming a data service from JavaScript is really easy.

The four parameters the DataService’s query method takes are pretty self-explanatory: the data service query, a method that will be called upon success, a method that will be called upon failure, and an arbitrary piece of context data. The failure callback and context data are optional, so if you really trust your query and/or don’t need any context, you could certainly omit them :)

The query parameter uses the same syntax that we’ve already gone over in previous articles, so there is nothing new going on here. The DataService object calls the specified data service, uses the specified query, and then when complete, will call the success or failure method based on what happened, passing the context data, if provided. It doesn’t get much easier than that.

Ok great, so we make make the above query call, now what? Well we have to implement the actual methods for our success/failure callbacks. The signature for each take three parameters: the query result, the context data (if specified), and the actual query itself.

Our success method looks like so:

snag-0012.jpg

In the case of the success method, the result parameter would be the data requested from the service call. It could be in the form of a collection of objects, a single object, or a scalar property value, depending on what your query was. The context parameter would hold whatever was passed to the context parameter in the call to the DataService’s query method. If null was passed, or the parameter was omitted, then the context parameter of the success method would be null. In our case, the context parameter would be the string “Context Data”, because we passed that in when we called service.query(). The operation parameter is the query you made that succeeded. In this case it would be “Carriers?$orderby=Name”.

The context and operation parameters are more for diagnostic purposes, and probably won’t be used all that much, and as such can be omitted from the method signature if you’d like. The result parameter is the primary focus here. As was mentioned previously, the result parameter will contain whatever data it is we requested, and can be categorized into three types:

  1. Collection of objects
  2. Single object
  3. Scalar property value

If your query is targeting an entity set, like we are doing above (Carriers?$filter=Name), then the result parameter will contain an array of objects. The object type will correspond to whatever the service model object is. In this case it would be Carrier objects. Using the above query, I could modify my success method like so (notice I’ve omitted the context and operation parameters to show how that could look):

snag-0014.jpg

This would result in an alert message reading “Alltel”. As you can see, I’m accessing the carrier through an index on the result parameter (which in this case is just a simple array). I could just as easily enumerate through the list, and perform whatever actions I needed to with the returned data. The possibilities are endless, and also out of the scope of this article. The point is that you can see how amazingly easy I can write JavaScript code to perform an AJAX call to a data service, and then receive my requested data.

What if I modified my query like so (I’ve omitted the failure callback and context parameters to show how that could look):

snag-0015.jpg

In this case, the result parameter passed to the success method wouldn’t be an array of carriers, but instead a single instance of a carrier. I could modify my success method code like so:

snag-0016.jpg

Notice that I no longer have to access the carrier via an index on the result parameter, because it’s receiving a single carrier. Nice and clean.

Now what if I modified the query like so?:

snag-0017.jpg

In this case, the result parameter passed to the success method wouldn’t be an instance of a carrier, but simply the requested carrier’s name. I could modify my success method code like so:

snag-0018.jpg

You might be wondering why I just wasted all that time to illustrate something that probably could have been deduced by someone with half a brain? Because I wanted to make it crystal clear how flexible the DataService object is. When making AJAX calls, you know that bandwidth and processing time are important, so you should be able to request/work with only the data you absolutely need. With the DataService object you aren’t forced to pull down extra bloat, you can streamline your data access as granular as you need.

So what about the failure method? It takes the same three parameters as the success method: result, context, operation. The context and operation parameters are identical to their success method counterparts. The only difference in the failure method over the success method is that the result parameter isn’t the requested data, but rather an object that explains why the query failed.

As I mentioned above, the query method’s failure callback and context parameters are optional. What I didn’t mention is that you can set default values for them on the DataService, which will be used every time you call query and omit those parameters. I could use the following code:

snag-0020.jpg

This would obviously fail due to my gross misspelling of the word “Carriers”. Notice I didn’t pass a failure callback to the query method, which would normally mean that the error would get “swallowed”, but because I set a default failure callback, onFailure will be called. Also notice that I omitted the context data parameter, but I set a default context value of “Sweet1″, which will be passed to the call of the onFailure method when this query fails. You can begin to see the value of the context parameter now knowing that you can use a common failure method accross all queries. The context parameter can well, add context to the query that failed.

Aside from the query method not requiring a failure callback or context parameter, you can also omit a success callback. Obviously it wouldn’t make much sense to omit the success callback, since your call to query would become completely pointless. But, just like the failure callback and context data, you can set a default success callback as well, like so:

snag-0022.jpg

Notice my call to the query method takes only the actual data query, all the other parameters come from the default set on the DataService. In some situations this would make the code much cleaner, but in others it might encourage some nastyness. The point is to use what makes sense for your scenario. Note, that the query method can still accept all of its parameters, which would then override whatever default values had been set. Hence, you could imagine a nice setup with default callbacks and context, that would only be used for query calls that could be generically handled, while other query calls had their own tailored callbacks.

If you don’t want to let the query call hang forever, you can set a timeout on the DataService, which will kill the query call after the specified amount of time (in milliseconds), and then call the failure method. The following code would set a 1 second timeout on the DataService:

snag-0024.jpg

Now every query call made on that instance of the DataService would be limited to a second of processing time.

Remember when I mentioned that the result parameter passed to the failure method contained information about why the query failed? That parameter is actually an object of type Sys.Data.DataServiceError. That object is perfect for determining whether the query failed because of an actual exception, or simply a timeout, since it has a property called timedOut. I could modify the failure method like so:

snag-0027.jpg

Also notice the use of the message property, which provides us with the underlying exception message. The DataServiceError also has the following properties for determining the cause of the error: exceptionType, statusCode, and stackTrace. The exceptionType property returns the name of the exception type that actually occurred (i.e. System.OutOfMemoryException). The statusCode property returns the HTTP error that occured (i.e. 404). The stackTrace property returns what it sounds like, the stack trace that led to the query failure.

At this point Phillip feels pretty proud of himself and all that he had discovered about using ADO.NET Data Services together with AJAX. Just as he is about to call his client back to tell him the good news, he realizes something: XMLHttpRequest (and JavaScript for that matter) can’t make cross-domain calls! If his client wants to access his cell phone data via JavaScript, he’ll have to make his own service that interfaces with the cell phone service, then he can use the DataService object to access his local service. Not exactly ideal, but it shouldn’t cause to much pain. With the said, it should be noted that the above samples and newly introduced classes are meant only for accessing data services within the same domain. That constraint shouldn’t really come as a surprise, since we already had to come to grips with that with web services within our application, data services are no different.

You may be thinking “Aha! I see what this is good for! All those times that I created a web service in my web application purely for the sake of calling it using ASP.NET AJAX to get and update data could now be replaced with a data service! That would allow me to create the same user-experience, but without having to write all that data-centric logic myself! Eureka!” You would be a pretty smart person if you said that, and that is exactly the use of the DataService that I would recommend :) A data service is by no means meant to be exposed over the web and usable by other people’s scripts. It is meant to compliment its parent web application, in the same domain.

The DataService has a couple more methods, but they pertain to change modification, something that we haven’t covered yet. In our next article we’re going to jump back to writing .NET data service client code to go over how we can add/update/delete data from a data service. We’ll also cover how to achieve the same functionality in JavaScript using the client-side DataService object.

17
Dec

ADO.NET Data Services Part 5: LINQ

When I first realized that the WebDataQuery<T> class implemented the IQueryable<T> interface, two things became very apparent to me: this baby was going to be LINQable (that’s a sweet word), and I could now die a happy, yet pathetically nerdy man. Now, you may be wondering how I came to the conclusion that LINQ could be used in conjuction with a data service query, and to explain that let me go off on a brief tangent…

Brief LINQ Tangent

LINQ’s foundation is essentially composed of a collection of extension methods called the Standard Query Operators. Those methods represent all possible functions that can be performed on a data source, and are global to all flavors of LINQ (some LINQ flavors define their own operators, but they are not considered standard). Those extension methods are defined for two types: IEnumerable<T>, and IQueryable<T>, within the Enumerable and Queryable classes respectively. What that means is that any class that implements either of those two interfaces, would automatically “inherit” the list of standard query operators, allowing LINQ statements to be written against it. All that would be left is for the flavor specific logic to be implemented that would enable the LINQ query to actually work.

Keep in mind, that is a pretty watered down explanation of how LINQ works, but hopefully it provides enough primer to merit my above reasoning. Now that we understand that ADO.NET Data Services has “tapped” into the LINQ world by making it’s query object (WebDataQuery<T>) implement IQueryable<T> (with the corresponding implementation logic), we can begin to leverage that beautiful truth for our own benefit.

There are two questions you may be asking yourself right now:

  1. “What is so special about ADO.NET Data Services being queryable with LINQ?”
  2. “If LINQ support is so important, why the hell did you wait till part 5 to introduce it?!”

If you have already embraced LINQ, then question #1 would be pretty much laughable. I don’t want to get into a description of LINQ or its benefits too much here, but I will say a few things:

  1. One syntax to rule them all! When you learn LINQ, and become familiar with the standard query operators, you can seamlessly work with any data source that supports it, with extreme ease.
  2. You don’t have to be too concerned with the underlying implementation of each LINQ flavor to work with its respective data source. LINQ is a wonderful facade that makes all data sources look the same (to a degree).
  3. Most importantly, LINQ allows you to query data intuitively. When you get comfortable with the LINQ syntax/operators you become very capable of writing sophisticated queries that might be otherwise tricky using the native query approach of the underlying data source.

What do those three points mean for using LINQ with ADO.NET Data Services? It means that any preexisting knowledge of LINQ becomes completely transferable to the context of a data service. All the time you’ve spent on mastering LINQ was not in vain, and in fact, was a great mental investment on your part. You may have noticed that while the URL parameters and RESTful interface of the data service is pretty straightforward, it’s not exactly the most intuitive coding style, it’s yet another API to learn, and as the queries get more advanced, the uglier the URL’s become. Sounds like the perfect candidate for LINQ to me…

So getting back to question #2 above. I waited this long to cover LINQ To Data Services for one reason: because while LINQ provides a powerful abstraction of the underlying query logic, it shouldn’t be a replacement for understanding how the underlying data is actually manipulated. LINQ should be a tool used by developers who know how to work with the data natively, but would prefer not to. Does that mean you should go out and ensure you understand every single detail of how each LINQ flavor works? Absolutely not, that would take forever, and probably wouldn’t have much value for you. But, since ADO.NET Data Services is brand new, we can all take the time to learn it from the ground up, and have it mastered long before it’s officially released. Plus, how could you fully appreciate just how awesome LINQ To Data Services is without knowing what life was like without it? :)

“Alright Jonathan, enough of this high-level theory jabber. I already know all of this. Get to some actual code for god’s sake!!!” Let’s take a look at some code we’ve created in previous posts, and see how the same logic could be accomplished, but using LINQ.

To retreive all carriers, ordered by name, we were previously doing:

astoria61.JPG

Which could be converted into:

astoria62.JPG

Or:

astoria63.JPG

Depending on which syntax you prefer. All three examples achieve the exact same result, but each express a different level of clarity. Example two is arguably the most intuitive, while example three is a little more compact. The question of which form to use it up to you. LINQ should be viewed as an alternative, not a doctrine.

“Big deal!”, you might say, “that example is so trivial, neither method really stands out as being that much better than the other.” That may be true, so let’s see some an example that sets using LINQ apart from querying the data service using a URL. Here is a somewhat involved LINQ query:

astoria64.JPG

Would you agree that it’s pretty clear what I’m trying to achieve with that query? I’d say so. The code is explicit, and it’s easy to read. What would that same query look like without LINQ?:

astoria65.JPG

That isn’t the worst code in the world, but in comparison to the LINQ statement, it sure looses a lot of it’s appeal. Aside from being more explicit, the LINQ query is also strongly typed, and has full intellisense. If you were typing a long URL in a call to CreateQuery, the more complex the query gets, the more likely it becomes that you’ll make a typo. We all know there is nothing more beautiful than hand-typing an obnoxiously long string, just to get a run-time error mocking us of a misspelled word. That’s why the DataTable’s Compute and Select methods were so successful (by successful I mean completely deprecated in favor of the extremely superior LINQ To DataSet).

As much as I love LINQ I could pretty much go on all day showing examples of queries that would make you cry out with joy. As this article wasn’t meant to teach LINQ, there isn’t really much more to say (that’s a first for me right?) that is specific to ADO.NET Data Services. There is no rocket-science behind using LINQ To Data Services. Like I mentioned before, if you already know LINQ, then that is all you need to hit the ground running here. If fact, someone with knowledge of LINQ, and virtually no knowledge of ADO.NET Data Services could easily begin writing queries.

The important aspect to take away from this article is to deepen your appreciation for both LINQ and ADO.NET Data Services. When LINQ was first introduced, it was pigeon-holed by many as a way to write SQL in code. Obviously that stereotype has since been done away with, but how much deeper does it’s value grow now that we see it fully implemented as an abstraction over building URLs? ADO.NET Data Services was initially seen as a way to query your database over the web. Throughout this article series we’ve shattered that ridiculous rumor, and seen just how powerful and extremely flexible data services are. And with data services having full LINQ support, what better argument could there be that they fit nicely into the .NET ecosystem? When two new technologies have an integration story like this, everybody wins.

So now that we’ve covered the entire gamut of consuming a data service from within a client application, we need to focus our attention on another client environment, namely JavaScript. ADO.NET Data Services has a full-featured JavaScript API that works beautifully with the ASP.NET AJAX libraries, enabling you to do some pretty sweet stuff. In the next article, we’ll cover just how exactly to achieve said sweetness.