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 => { 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.











