The sample code for this part can be found here.
In part 1 we looked at the [R] read operations of Ria services, and in part 2 we looked at Service Operations. In this part, we’ll dig into Create, Update, and Delete operations in Ria Services [CUD]. You should consider reading the previous posts if you have not already, as we build on those concepts and assume that knowledge. All of these posts assume intermediate knowledge of .net fundamentals, ASP.Net and the basics of the HTTP protocol. Microsoft has a great Ria Services overview PDF you can find here for much more information on Ria Services as a whole.
Ria services has a naming convention to designate members on Domain Service objects which are CRUD-related, or you can explicitly decorate your methods with [Insert], [Update] or [Delete] attributes. Refer to that pdf I mentioned to see the exact naming conventions, but that implicit style seems kinda silly to me, so I’d recommend the more explicit option of decorating with attributes.
Once you have your CRUD operations on your domain service, you can interact with those client-side through a change-notification-enabled collection of your entities. Ria Services will provide that too, in the form of the EntityList<T> collection. For example, in our Domain Service object, we have all 4 CRUD operations for our Person entity:
[Query]
public IEnumerable<Person> SnagAllPeeps()
{
return FakeDataAccessLayer.GetAllPeeps();
}
[Insert]
public void CreatePerson(Person person)
{
person.Id = Guid.NewGuid();
person.DateCreated = person.LastModified = DateTime.Now;
FakeDataAccessLayer.CreatePerson(person);
}
[Update]
public void UpdatePerson(Person person)
{
person.LastModified = DateTime.Now;
FakeDataAccessLayer.UpdatePerson(person);
}
[Delete]
public void DeletePerson(Person person)
{
FakeDataAccessLayer.DeletePerson(person);
}
Because we have all 4 operations involving the same Entity, we’ll be good to go on the client (again, this is available via the sample code download). On the client, simply add, remove, or edit the Person entities. For example, to add a new Person, we add a new Person object to the Persons EntityList which was generated onto the Domain Context on the client:
private void AddNewPerson(object sender, RoutedEventArgs e)
{
_domainService.Persons.Add(new Person());
}
In our sample, because our datagrid was bound to that same Persons EntityList, its taking care of the Updates:
private void LoadUpTheGrid()
{
// Bind to the Persons EntityList. EntityList contains all the logic for change-tracking. So, when
// our service call returns with a bag full of Persons, the change tracking will fire off, and the
// data grid will load up. Sweet, eh?
this.grdPeeps.ItemsSource = _domainService.Persons;
this.lblMessageCenter.Text = "Loading...";
// Get the peeps, handling the callback inline.
_domainService.Load(_domainService.SnagAllPeepsQuery(), (LoadOperation<Person> loadOperation) =>
{
this.lblMessageCenter.Text = string.Empty;
}, null);
}
We’re trapping the KeyDown event in the grid, and deleting Person objects from the Persons EntityList of the delete key was hit:
private voidgrdPeeps_KeyDown(objectsender, KeyEventArgs e)
{
if(e.Key == Key.Delete && this.grdPeeps.SelectedItem != null)
{
_domainService.Persons.Remove(this.grdPeeps.SelectedItem asPerson);
}
}
Once we’re done making changes, we call the Submit method on the client-side Domain Context objects. For example, here’s our simple “Save” button’s click event:
private void SaveOperation(object sender, RoutedEventArgs e)
{
if (_domainService.HasChanges)
{
_domainService.SubmitChanges();
}
}
Under the hood, this calls the System.Windows.Ria.Data.DomainContext.SubmitChanges() method. Within that method, validation is processed, the DomainContext and each entity within is marked “IsSubmitting” as True, and the configured DomainClient’s BeginSubmit method is called, passing in the changeset (the Inserts, Updates, and Deletes), a callback (either provided by the caller, or a default one), and the [optional] “userState” provided by the caller. An internal “ChangeSetBuilder” object is used to inspect the changeset to build a list of System.Windows.Ria.Data.EntityOperation(s). These objects store state about each entity and the operation on it (CREATE, UPDATE, DELETE). The list of EntityOperations is serialized into JSON for transmission over the wire to the server. As a test, I added 1, deleted 1, and edited 1 person, and here’s what that looks like as raw HTTP on the wire (note formatting for ease-of-reading). The first {Entity” is the addition, the 2nd one is the deletion, and the 3rd one is the update, hence the “OriginalEntity” data along with it).
———————————————————————————————
POST /ClientBin/DataService.axd/SilverlightApplication1-Web-DomainService1/$Submit HTTP/1.1
Accept: */*
Content-Length: 1064
Content-Type: text/json
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; WOW64; Trident/4.0; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.5.21022; .NET CLR 3.5.30729; .NET CLR 3.0.30618; .NET CLR 1.1.4322; OfficeLiveConnector.1.3; OfficeLivePatch.0.0; msn OptimizedIE8;ENUS)
Host: localhost.:53937
Connection: Keep-Alive
Pragma: no-cache
[{"Entity":{"__type":"Person:#SilverlightApplication1.Web","Age":0,"DateCreated":"\/Date(-62135578800000-0500)\/","Id":"00000000-0000-0000-0000-000000000000","LastModified":"\/Date(-62135578800000-0500)\/","Name":"New guy"},"Id":0,"Operation":1,"OperationData":null,"OperationName":null},
{"Entity":{"__type":"Person:#SilverlightApplication1.Web","Age":41,"DateCreated":"\/Date(-62135578800000)\/","Id":"fec4fbde-a101-4608-ba53-50fe8e6eebd7","LastModified":"\/Date(-62135578800000)\/","Name":"Bob"},"Id":1,"Operation":3,"OperationData":null,"OperationName":null},
{"Entity":{"__type":"Person:#SilverlightApplication1.Web","Age":35,"DateCreated":"\/Date(-62135578800000)\/","Id":"6a49aea6-c5e9-4ed6-a881-8a71bafd6a0a","LastModified":"\/Date(-62135578800000)\/","Name":"Jason Edited"},"Id":2,"Operation":2,"OperationData":null,"OperationName":null,"OriginalEntity":{"__type":"Person:#SilverlightApplication1.Web","Age":35,"DateCreated":"\/Date(-62135578800000)\/","Id":"6a49aea6-c5e9-4ed6-a881-8a71bafd6a0a","LastModified":"\/Date(-62135578800000)\/","Name":"Jason"}}]
———————————————————————————————
We’re now on the server, and the same System.Web.Ria.DataServiceFactory object [we’ve seen in previous posts] is used to process the incoming Http request. The correct domain service is determined and initialized using reflection (in the example HTTP post above, you can see the name of the domain service as “SilverlightApplication1-Web-DomainService1"; the dashes are replaced with dots to obtain the fully-qualified type name). The operation to invoke is part of that initialization, which in this case is the “Submit” operation. A System.Web.Ria.DataService is the handler returned by the DataServiceFactory, and within its IHttpHandler.ProcessRequest, a few checks are made to determine the type of DataService request to make. For this sample, the DataServiceSubmitRequest object is created, and its Invoke method is fired off. Within the callstack, the payload is deserialized using the JavaScriptSerializer to pull out the same List<EntityOperation> which was sent from the client, and initializes a ChangeSet object from that list. The DomainService.Submit() method is called, passing down the changeset. Within this method, authorization checks and validation of operations for the changeset prior to calling the ExecuteChangeSet method. Within this method, the CUD portion of CRUD is performed (literally, a foreach loop is performed on the changeSet.EntityOperations which aren’t custom), followed by execution of the Custom methods (we have not covered custom operations in these posts as of yet), and committing (not in the transactional sense, hold *that* thought for a second) the entity values within the changeSet (essentially setting original equal to current).
Speaking of Committing (*in* the transactional sense), Ria Services itself does not setup any transactions, but provides easy-to-use-hooks. For example, you can override the Submit method to and add transactional code (see that PDF for a specific example).
Another sweet spot in the Ria Services story is the facility to send back entity changes made on the server, back to the client. If you look at our sample code, you’ll see in our Insert and Update Domain Service operations, we set a couple of simple audit columns (DateCreated and LastModified). In the Create operation, we also set the ID of the new entity. These server-side updates are streamed back to the client (see the HTTP response below, with some formatting for readability):
———————————————————————————————
HTTP/1.1 200 OK
Server: ASP.NET Development Server/9.0.0.0
Date: Sun, 25 Oct 2009 21:09:03 GMT
X-AspNet-Version: 2.0.50727
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: text/json; charset=utf-8
Content-Length: 806
Connection: Close
{"__type":"DataServiceResult:DomainServices","IsDomainServiceException":false,"Results":[{"__type":"EntityOperation:DomainServices",
"Entity":{"__type":"Person:http://schemas.datacontract.org/2004/07/SilverlightApplication1.Web","Id":"6abd2fcd-a994-41d7-9451-d35a81d7fc5a","Name":"New guy","Age":0,"DateCreated":"\/Date(1256504935062)\/","LastModified":"\/Date(1256504935062)\/"},"Id":0,"Operation":1,"OperationData":null,"OperationName":null},{"__type":"EntityOperation:DomainServices",
"Entity":{"__type":"Person:http://schemas.datacontract.org/2004/07/SilverlightApplication1.Web","Id":"6a49aea6-c5e9-4ed6-a881-8a71bafd6a0a","Name":"Jason Edited","Age":35,"DateCreated":"\/Date(-62135578800000)\/","LastModified":"\/Date(1256504935066)\/"},"Id":2,"Operation":2,"OperationData":null,"OperationName":null}]}
———————————————————————————————
You can visually see these taking place if you run the sample code for this post, and you’ll see the data-bound DataGrid update its ID, DateCreated, and LastModified columns. This little bit of magic occurs because as the results are getting ready to be packaged, the DataServiceSubmitRequest.GetSubmitResults method is called, and within if any entities’ members have changed, they are added to a list which ultimately determines what gets burned into the HTTP JSON response. Nice!
So, there’s a little whirlwind tour of the client-to-server stack trace of a submit. There’s a lot more to the Ria Services story, as we’ve conveniently left out the rich Validation and security features, as well as extensibility points.