- Data Services on the Server
- Data Services on the Client
- On Beyond Query: Create, Update and Delete
- Data Services + Entity Framework = Joy
- WPF Data Binding
- Paging
- Interceptors
- Authorization
- Functions
- JSON
- Debugging
- Where Are We?
On Beyond Query: Create, Update and Delete
The root protocol of OData, AtomPub, was chiefly designed to allow for creating, updating and deleting entries as well as for retrieving them. While the HTTP method of GET is used to retrieve service documents containing collections and feed or entry documents containing entries (that’s the method used when you type a URL into your browser), the following are the set of methods used in AtomPub to manipulate the entries exposed by the collections:
- GET: Read a collection of entries (as a feed document) or a single entry (as an entry document).
- POST: Create a new entry from an entry document.
- PUT: Update an existing entry with an entry document.
- DELETE: Delete an entry.
Any platform that provides support for HTTP (and ideally XML) is enough to form HTTP requests to interact with AtomPub. As we’ve already mentioned, this is also true for OData, as are the mappings of the HTTP verbs to CRUD operations, but in the case of OData, these operations can be used against any kind of entry, not just blog-style data. And again, you can do this at the raw HTTP level, but I’ll recommend the WCF Data Services client instead, which exposes create, update and delete functionality in a way natural to the .NET programmer and very natural to the Entity Framework programmer:
// Create a new user and link var bill1 = new TinyUser() { Name = "Bill" }; service.AddToTinyUsers(bill1); var link1 = new TinyLink() { IsActive = true, Url = "http://sempf.net", User = bill1 }; bill1.Links.Add(link1); service.AddToTinyLinks(link1); // Link the TinyLink and TinyUser together service.AddLink(bill1, "Links", link1); service.SetLink(link1, "User", bill1); // Flush the changes service.SaveChanges(SaveChangesOptions.Batch); var billId = bill1.Id; var linkId = link1.Id;
Notice that we’re creating and flushing changes very much like the EF code from previous chapters. Creation is a matter of creating an instance of one of the proxy objects, populating its values and calling the AddTo<<Collection>> method, e.g. AddToTinyUsers and AddToTinyLinks in our case. Also, the SaveChanges method, like in EF, is what flushes the changes back to the server. Notice also that we’re able to create multiple objects and relate them even before pushing them to the service, just like in EF.
There are differences on the client-side of Data Services from EF, however. One is that you must add both objects in a related pair to their respective collections in DS, whereas in EF, adding one is enough and the other one is implied. If you forget to add both to their collections, DS won’t create the one you forgot.
The second difference between DS and EF is that, in addition to adding both objects, you must add the “link” between them explicitly (known as the “association” in EF) via the AddLink and SetLink methods. Both take a source, a target and the name of the property on the source which relates the two objects. The difference is that AddLink adds a link to a collection whereas SetLink sets the link on a property. Remember the “link” document and the $links query option that we discussed earlier? If you don’t call the AddLink and SetLink methods, then the underlying OData “link” won’t be added and your association won’t be made, which, in the case of an EF backend, will result in the foreign keys going unset. When you’re removing associations, the inverse of AddLink is DeleteLink and the inverse of SetLink is SetLink with a null, e.g.
service.DeleteLink(bill1, "Links", link1); // Removing a link you added with AddLink service.SetLink(link1, "User", null); // Removing a link you set with SetLink
Finally, the third thing that’s different is that the DS SaveChanges method has an option that allows you to bundle everything up into a single round-trip called Batch, whereas EF currently has no such option. The Batch mode is not the default because not all OData servers support it, but you should definitely use it for the ones that do.
Moving on from creation, update is a matter of finding the object to work with, changing the properties in question and pushing the changes:
// Find and update a link var link2 = service.TinyLinks.Where(l => l.Id == linkId).First(); link2.Url = "http://http://www.sempf.net"; service.UpdateObject(link2); service.SaveChanges(SaveChangesOptions.Batch);
Notice the use of Where with a predicate following by First. That’s because neither First with a predicate nor Single is mapped to OData in the underlying Data Services LINQ provider, so would cause run-time errors. Experimentation is the best teacher for what parts of LINQ work and don’t.
Notice also the use of the UpdateObject method. Like AddLink, where you have to let the Data Services context know that explicitly that you’ve created a link between two objects, UpdateObject is a requirement to let the context know that you’ve updated an object. Unless you call this explicitly, your updates won’t be sent to the service endpoint.
Deletion works similarly to update in that you’ve got to find the object to work with before passing it to the DeleteObject method:
// Find and delete the link and the user var link3 = service.TinyLinks.Where(l => l.Id == linkId).First(); var bill2 = service.TinyUsers.Where(u => u.Id == billId).First(); service.DeleteObject(link3); service.DeleteObject(bill2); service.SaveChanges(SaveChangesOptions.Batch);
Notice that, like all changes, deletions happen in-memory and are pushed with the call to SaveChanges.
To enable changes on the server-side using Data Services, we have to update the permissions we set on our entities in the TinyLinkService.svc.cs file:
public class TinyLinkService : DataService<TinyLinkContainer> { public static void InitializeService(DataServiceConfiguration config) { // Set rules to allow create, update and delete config.SetEntitySetAccessRule("TinyLinks", EntitySetRights.All); config.SetEntitySetAccessRule("TinyUsers", EntitySetRights.All); config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; } }
And all of this work fabulously well except for one problem – our container doesn’t support writes, even if the permissions allow it.