Zoals ik in mijn vorige blog over datajs al vermeldde, is het aanroepen
van een OData service met deze DataJS bibliotheek stukken
eenvoudig geworden. Hoewel we dicht tegen het ijzer (lees: de soap berichten)
aan programmeren, blijft het samenstellen van de te versturen opdrachten simpel en leesbaar.
En de grootste winst van datajs is de mogelijkheid om meerdere opdrachten
tegelijkertijd te versturen. Het Open Data Protocol ondersteunt namelijk batch operaties. Hierdoor kunnen in één keer verschillende zoekopdrachten verstuurd worden of juist meerdere gewijzigde entiteiten in één keer opgeslagen worden.
Hier duiken we vandaag eens in.
OData maakt bij batch opdrachten gebruik van twee krachten. Ten eerste beidt
Soap de mogelijkheid om in één keer een “multi-part” bericht te sturen.
Daarnaast kan de WCF Data Service meerdere gewijzigde entiteiten in één keer
submitten naar de data-context via de SaveChanges methode.
Dit is dus twee (of meer) vliegen in één slag. Er moet minder over de
lijn gestuurd worden wat dus een besparing van bandbreedte en tijd is. En dit
maakt het ook mogelijk om meerdere wijzigingen als één transactie te
verwerken.
Om dit te demonstreren gaan we eerst twee aparte queries uitvoeren. Kijk
naar het volgende voorbeeld. Op zich zijn dit niet de meest spannende zoekacties
(we vragen twee entiteiten apart van elkaar op via de unieke sleutels) maar dit
zijn aparte selecties!
De code is eenvoudig en goed leesbaar, los van de {} en [] tekens hier en
daar…
// GET two calls within BatchOData.request({ requestUri: "htt p://localhost:2976/WcfDataService1.svc/$batch", method: "POST", data: { __batchRequests: [ { requestUri: "Machines(1)?$select=MachineId,MachineName", method: "GET" }, { requestUri: "Machines(2)?$select=MachineId,MachineName", method: "GET" } ] } }, BatchSuccess, Error, OData.batchHandler );
Als we dit aanroepen wordt met slechts één call beide requests uitgevoerd
en als één call afgehandeld. Dit is mooi te bekijken met Fiddler. Deze geeft
een mooi overzicht van wat er bij de request en response over het lijntje
gestuurd wordt:
POST http://localhost:2976/WcfDataService1.svc/$batch HTTP/1.1Content-Type: multipart/mixed;boundary=batch_f2ba-bfa6-d506 Accept-Language: nl,en-US;q=0.5 dataserviceversion: 1.0 Accept: multipart/mixed Referer: http://localhost:2976/datajs_GET_HTMLPage.htm Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C; .NET4.0E) Host: localhost:2976 Content-Length: 482 Connection: Keep-Alive Pragma: no-cache --batch_f2ba-bfa6-d506 Content-Type: application/http Content-Transfer-Encoding: binary GET Machines(1)?$select=MachineId,MachineName HTTP/1.1 Accept: application/atomsvc+xml;q=0.8, application/json;q=0.5, */*;q=0.1 --batch_f2ba-bfa6-d506 Content-Type: application/http Content-Transfer-Encoding: binary GET Machines(2)?$select=MachineId,MachineName HTTP/1.1 Accept: application/atomsvc+xml;q=0.8, application/json;q=0.5, */*;q=0.1 --batch_f2ba-bfa6-d506--
Wat opvalt is dat de aanroep met context-type : multipart wordt
opgemaakt. Vervolgens staan de twee aparte GET acties in de body.
Dit geeft het volgende response als antwoord:
HTTP/1.1 202 AcceptedCache-Control: no-cache Content-Length: 903 Content-Type: <strong>multipart</strong>/mixed; boundary=batchresponse_f259fb24-2f68-41d6-95a4-fbe89cf29362 Server: Microsoft-IIS/7.5 DataServiceVersion: 1.0; X-AspNet-Version: 4.0.30319 X-SourceFiles: =?UTF-8?B?QzpcU0RFXFN0 [knip] mF0Y2g=?= X-Powered-By: ASP.NET Date: Mon, 04 Apr 2011 16:58:10 GMT --batchresponse_f259fb24-2f68-41d6-95a4-fbe89cf29362 Content-Type: application/http Content-Transfer-Encoding: binary HTTP/1.1 <strong>200</strong> OK Cache-Control: no-cache DataServiceVersion: 1.0; Content-Type: application/json;charset=utf-8 { "d" : { "__metadata": { "uri": "http://localhost:2976/WcfDataService1.svc/Machines(1)", "type": "SdnODataModel.Machine" }, "MachineId": 1, "MachineName": "Blender" } } --batchresponse_f259fb24-2f68-41d6-95a4-fbe89cf29362 Content-Type: application/http Content-Transfer-Encoding: binary HTTP/1.1 <strong>200</strong> OK Cache-Control: no-cache DataServiceVersion: 1.0; Content-Type: application/json;charset=utf-8 { "d" : { "__metadata": { "uri": "http://localhost:2976/WcfDataService1.svc/Machines(2)", "type": "SdnODataModel.Machine" }, "MachineId": 2, "MachineName": "Machine that goes Ping" } } --batchresponse_f259fb24-2f68-41d6-95a4-fbe89cf29362--
Ook de response heeft een multipart context-type. En in hetzelfde bericht
zijn de responses van beide aanvragen opgenomen, ieder met een 200 OK antwoord. Overigens zijn de geretourneerde entiteiten in JSON formaat beschreven. Dit wordt door datajs weer netjes in objecten omgezet.
function BatchSuccess(data) { var html = data.__batchResponses[0].data.MachineId + "-" + data.__batchResponses[0].data.MachineName + " / " + data.__batchResponses[1].data.MachineId + "-" + data.__batchResponses[1].data.MachineName; $("#responsePlaceHolder").html(html); }
En op deze manier kan dus heel efficiënt data opgehaald worden waarbij de
aanvraag over meerdere queries is verdeeld.
Maar hoe zit het met wijzigingen van data? Nou, dat zit wel goed. Met
hetzelfde gemak kunnen ook meerdere insert, updates en deletes uitgevoerd
worden.
Pas echt interessant is de mogelijkheid om verschillende types aan
wijzigingen te combineren.
Stel je voor dat we een soort van master-detail scherm hebben. Zou het
mogelijk zijn zowel wijzigingen aan de master (bv. klantgegevens) als
toevoegingen aan de details (bv. orders) in één keer te versturen? In mijn
vorige blog bleek al dat een MERGE (het opsturen van alleen de gewijzigde
kolommen) veel efficienter is dan een PUT maar dat de MERGE een buitenbeentje is qua notatie.
Zou een merge samen met een POST (het toevoegen van records) kunnen
plaatsvinden?
Nou, dat kan. Kijk maar eens naar onderstaande voorbeeld. Hierin
combineer ik een wijziging van een bestaande entiteit (MERGE) met het opvoeren van een nieuwe entiteit (POST):
// BATCH MERGE-POSTOData.request({ requestUri: "htt p://localhost:2976/WcfDataService1.svc/$batch", method: "POST", data: { __batchRequests: [ { __changeRequests: [ { requestUri: "Machines(1)", method: "MERGE", data: { MachineName: 'MixerNew'} } , { requestUri: "Machines", method: "POST", data: { MachineName: 'DataNew', SupervisorName: 'userNew'} } ] } ] } }, Success, Error, OData.batchHandler );
Ook hier komt Fiddler ons helpen met het doorgronden van de request en de
response.
De request ziet er als volgt uit:
POST http://localhost:2976/WcfDataService1.svc/$batch HTTP/1.1Content-Type: multipart/mixed;boundary=batch_1451-c1ce-8a01 Accept-Language: nl,en-US;q=0.5 dataserviceversion: 1.0 Accept: multipart/mixed Referer: http://localhost:2976/datajs_POST_HTMLPage.htm Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C; .NET4.0E) Host: localhost:2976 Content-Length: 746 Connection: Keep-Alive Pragma: no-cache --batch_1451-c1ce-8a01 Content-Type: multipart/mixed; boundary=changeset_b3dd-3981-60b2 --changeset_b3dd-3981-60b2 Content-Type: application/http Content-Transfer-Encoding: binary MERGE Machines(1) HTTP/1.1 Accept: application/atomsvc+xml;q=0.8, application/json;q=0.5, */*;q=0.1 DataServiceVersion: 1.0 Content-Type: application/json {"MachineName":"MixerNew"} --changeset_b3dd-3981-60b2 Content-Type: application/http Content-Transfer-Encoding: binary POST Machines HTTP/1.1 Accept: application/atomsvc+xml;q=0.8, application/json;q=0.5, */*;q=0.1 DataServiceVersion: 1.0 Content-Type: application/json {"MachineName":"DataNew","SupervisorName":"userNew"} --changeset_b3dd-3981-60b2-- --batch_1451-c1ce-8a01--
Ook hier is het een multipart bericht met daarin de MERGE en de POST
gebroederlijk naast elkaar.
De daarop volgende response is niet veel spannender:
HTTP/1.1 202 AcceptedCache-Control: no-cache Content-Length: 1137 Content-Type: multipart/mixed; boundary=batchresponse_a689df61-3d2a-4533-9e88-522b3d519196 Server: Microsoft-IIS/7.5 DataServiceVersion: 1.0; X-AspNet-Version: 4.0.30319 X-SourceFiles: =?UTF-8?B?QzpcU0RFXFN [knip] wkYmF0Y2g=?= X-Powered-By: ASP.NET Date: Mon, 04 Apr 2011 17:36:25 GMT --batchresponse_a689df61-3d2a-4533-9e88-522b3d519196 Content-Type: multipart/mixed; boundary=changesetresponse_17e960ab-699f-40f0-8f1b-6003cf4dd4ae --changesetresponse_17e960ab-699f-40f0-8f1b-6003cf4dd4ae Content-Type: application/http Content-Transfer-Encoding: binary HTTP/1.1 204 No Content Cache-Control: no-cache DataServiceVersion: 1.0; --changesetresponse_17e960ab-699f-40f0-8f1b-6003cf4dd4ae Content-Type: application/http Content-Transfer-Encoding: binary HTTP/1.1 201 Created Cache-Control: no-cache DataServiceVersion: 1.0; Content-Type: application/json;charset=utf-8 Location: http://localhost:2976/WcfDataService1.svc/Machines(54) { "d" : { "__metadata": { "uri": "http://localhost:2976/WcfDataService1.svc/Machines(54)", "type": "SdnODataModel.Machine" }, "MachineId": 54, "MachineName": "DataNew", "SupervisorName": "userNew", "Image": null, "ImageName": null, "Parts": { "__deferred": { "uri": "htt p://localhost:2976/WcfDataService1.svc/Machines(54)/Parts" } } } } --changesetresponse_17e960ab-699f-40f0-8f1b-6003cf4dd4ae-- --batchresponse_a689df61-3d2a-4533-9e88-522b3d519196--
De MERGE wordt met een 204 (no content) beantwoord. Dit betekent dat de
opslag heeft plaatsgevonden maar er is geen nieuwe entiteit opgestuurd. Vanuit
het oogpunt van javascript heeft de client al alle kennis over het gewijzigde
object op de client.
De POST wordt met een 201 (Created) afgehandeld. Dit betekent dat de
entiteit in de context op de server is geaccepteerd. En we krijgen tevens de
gehele entiteit terug, compleet met de unieke sleutel van het object.
Met bovenstaande voorbeelden wordt duidelijk hoe efficient over het
netwerk gecommuniceerd kan worden als men zich verdiept in het Open Data
Protocol. Natuurlijk zal een batch opdracht niet altijd op zijn plaats zijn,
maar het is een krachtig stuk gereedschap die iedere OData ontwikkelaar moet
kunnen beheersen. En datajs maakt het wel heel eenvoudig te gebruiken.