When working with a DataServiceContext
from PowerShell you might run into concurrency update situations after updating entities via SaveChanges
.
This is due to the behaviour of the DataServiceContext ChangeTracker which will not reflect changes performed by the server (as a result of our update operation). Let me illustrate this with a quick example:
Suppose we have a Catalogue
entity where we want to update the Description
property. In addition to the Description
property the entity also has a RowVersion
and Modified
property which will be automatically updated on every save operation by the server.
PS > $dataServiceContext.GetType().BaseType IsPublic IsSerial Name BaseType -------- -------- ---- -------- True False DataServiceContext System.Object PS > $catalogue = $dataServiceContext.Catalogues | ? Id -eq 21 PS > $catalogue Status : Published Version : 1.0 Id : 21 Tid : ad8f50df-2a5d-4ea5-9fcc-05882f16a9fe Name : Fantabulous Catalogue Description : 098428b7-5529-499f-bfb8-65cf9da07cae CreatedById : 1 ModifiedById : 1 Created : 5/26/2016 2:35:47 PM +02:00 Modified : 7/24/2016 1:11:42 PM +02:00 RowVersion : {0, 0, 0, 0...} CatalogueItems : {} Tenant : CreatedBy : ModifiedBy : PS > [System.BitConverter]::ToString($catalogue.RowVersion) 00-00-00-00-00-0D-27-0E
We can see from the example that the RowVersion
currently is 00-00-00-00-00-0D-27-0E
and the last modification date is 7/24/2016 1:11:42 PM +02:00
.
So we now update the entity and save it back to the server:
PS > $catalogue.Description = [Guid]::NewGuid().Guid.ToString(); PS > $dataServiceContext.UpdateObject($catalogue); PS > $dataServiceContext.SaveChanges(); Descriptor Headers StatusCode Error ---------- ------- ---------- ----- System.Data.Services.Client.EntityDescriptor {[Pragma, no-cache], [Persistent-Auth, true], [Cache-Control, no-cache], [Date, Sun, 24 Jul 2016 11:50:31 GMT]...} 204
The server responds with an HTTP 204
so the client has no clue about these changed properties.
MERGE http://www.example.com/Appclusive/api/Core/Catalogues(21L) HTTP/1.1 DataServiceVersion: 3.0;NetFx MaxDataServiceVersion: 3.0;NetFx Content-Type: application/json;odata=minimalmetadata Accept: application/json;odata=minimalmetadata Accept-Charset: UTF-8 User-Agent: Microsoft ADO.NET Data Services Authorization: Basic ZWRnYXI6d2l0aEFGYW5jeVBhc3N3b3Jk Host: www.example.com Content-Length: 405 Expect: 100-continue { "odata.type": "biz.dfch.CS.Appclusive.Core.OdataServices.Core.Catalogue", "Created": "2016-05-26T14:35:47.7796257+02:00", "CreatedById": "1", "Description": "0f5fe70f-1414-4822-b4a0-9c7b21aebff6", "Id": "21", "Modified": "2016-07-24T13:11:42.4725002+02:00", "ModifiedById": "1", "Name": "Fantabulous Catalogue", "RowVersion": "AAAAAAANJw4=", "Status": "Published", "Tid": "ad8f50df-2a5d-4ea5-9fcc-05882f16a9fe", "Version": "1.0" } HTTP/1.1 204 No Content Cache-Control: no-cache Pragma: no-cache Expires: -1 Server: Microsoft-IIS/8.5 X-AspNet-Version: 4.0.30319 Persistent-Auth: true X-Powered-By: ASP.NET Date: Sun, 24 Jul 2016 11:50:31 GMT
When we now try to re-read and update the entity we get the following output and error:
PS > $catalogue = $dataServiceContext.Catalogues | ? Id -eq 21 PS > $catalogue Status : Published Version : 1.0 Id : 21 Tid : ad8f50df-2a5d-4ea5-9fcc-05882f16a9fe Name : Fantabulous Catalogue Description : 0f5fe70f-1414-4822-b4a0-9c7b21aebff6 CreatedById : 1 ModifiedById : 1 Created : 5/26/2016 2:35:47 PM +02:00 Modified : 7/24/2016 1:11:42 PM +02:00 RowVersion : {0, 0, 0, 0...} CatalogueItems : {} Tenant : CreatedBy : ModifiedBy : PS > [System.BitConverter]::ToString($catalogue.RowVersion) 00-00-00-00-00-0D-27-0E PS > $catalogue.Description = [Guid]::NewGuid().Guid.ToString() PS > $dataServiceContext.UpdateObject($catalogue) PS > $dataServiceContext.SaveChanges() Exception calling "SaveChanges" with "0" argument(s): "An error occurred while processing this request." At line:1 char:1 + $dataServiceContext.SaveChanges() + ~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : DataServiceRequestException
The HTTP traffic however shows us that the server actually returned the updated information:
HTTP/1.1 200 OK Cache-Control: no-cache Pragma: no-cache Content-Type: application/json; odata=minimalmetadata; charset=utf-8 Expires: -1 Server: Microsoft-IIS/8.5 DataServiceVersion: 3.0 X-AspNet-Version: 4.0.30319 Persistent-Auth: true X-Powered-By: ASP.NET Date: Sun, 24 Jul 2016 11:55:47 GMT Content-Length: 1764 { "odata.metadata": "http://www.example.com/Appclusive/api/Core/$metadata#Catalogues", "value": [ { "Status": "Published", "Version": "1.0", "Id": "21", "Tid": "ad8f50df-2a5d-4ea5-9fcc-05882f16a9fe", "Name": "Fantabulous Catalogue", "Description": "0f5fe70f-1414-4822-b4a0-9c7b21aebff6", "CreatedById": "1", "ModifiedById": "1", "Created": "2016-05-26T14:35:47.7796257+02:00", "Modified": "2016-07-24T13:50:31.6745857+02:00", "RowVersion": "AAAAAAANJ3c=" } ] }
The Modified
property is set to 2016-07-24T13:50:31.6745857+02:00 but the DataServiceContext showed us 7/24/2016 1:11:42 PM +02:00. Therefore the server will respond with an HTTP 409
:
MERGE http://www.example.com/Appclusive/api/Core/Catalogues(21L) HTTP/1.1 DataServiceVersion: 3.0;NetFx MaxDataServiceVersion: 3.0;NetFx Content-Type: application/json;odata=minimalmetadata Accept: application/json;odata=minimalmetadata Accept-Charset: UTF-8 User-Agent: Microsoft ADO.NET Data Services Authorization: Basic ZWRnYXI6YWdhaW5Bbm90aGVyUGFzc3dvcmQ= Host: www.example.com Content-Length: 405 Expect: 100-continue { "odata.type": "biz.dfch.CS.Appclusive.Core.OdataServices.Core.Catalogue", "Created": "2016-05-26T14:35:47.7796257+02:00", "CreatedById": "1", "Description": "a70a355d-6af2-4587-b561-b742afbb8a20", "Id": "21", "Modified": "2016-07-24T13:11:42.4725002+02:00", "ModifiedById": "1", "Name": "Fantabulous Catalogue", "RowVersion": "AAAAAAANJw4=", "Status": "Published", "Tid": "ad8f50df-2a5d-4ea5-9fcc-05882f16a9fe", "Version": "1.0" } HTTP/1.1 409 Conflict Cache-Control: no-cache Pragma: no-cache Content-Type: application/json; odata=minimalmetadata; charset=utf-8 Expires: -1 Server: Microsoft-IIS/8.5 DataServiceVersion: 3.0 X-AspNet-Version: 4.0.30319 Persistent-Auth: true X-Powered-By: ASP.NET Date: Sun, 24 Jul 2016 11:56:53 GMT Content-Length: 198 { "odata.error": { "code": "", "message": { "lang": "en-US", "value": "[ActivityID: 00000000-0000-0000-0000-000000000000] Assertion failed: !entityHasChangedInDataAccessLayer" } } }
To work around this behaviour we can set a property call MergeOption
:
PS > $dataServiceContext.MergeOption.GetType().FullName; System.Data.Services.Client.MergeOption PS > [Enum]::GetNames([System.Data.Services.Client.MergeOption]) AppendOnly OverwriteChanges PreserveChanges NoTracking PS > # current setting PS > $dataServiceContext.MergeOption AppendOnly PS > $dataServiceContext.MergeOption = "OverwriteChanges" PS > # new setting PS > $dataServiceContext.MergeOption OverwriteChanges
When we now try to update an entity it works as expected:
PS > $dataServiceContext.MergeOption = "OverwriteChanges" PS > $catalogue = $dataServiceContext.Catalogues | ? Id -eq 21 PS > $catalogue Status : Published Version : 1.0 Id : 21 Tid : ad8f50df-2a5d-4ea5-9fcc-05882f16a9fe Name : Fantabulous Catalogue Description : 21fc4a41-f7a8-4316-8e57-df4185c6eef9 CreatedById : 1 ModifiedById : 1 Created : 5/26/2016 2:35:47 PM +02:00 Modified : 7/24/2016 1:50:31 PM +02:00 RowVersion : {0, 0, 0, 0...} CatalogueItems : {} Tenant : CreatedBy : ModifiedBy : PS > [System.BitConverter]::ToString($catalogue.RowVersion) 00-00-00-00-00-0D-27-77 PS > $catalogue.Description = [Guid]::NewGuid().Guid.ToString() PS > $dataServiceContext.UpdateObject($catalogue) PS > $dataServiceContext.SaveChanges() Descriptor Headers StatusCode Error ---------- ------- ---------- ----- System.Data.Services.Client.EntityDescriptor {[Pragma, no-cache], [Persistent-Auth, true], [Cache-Control, no-cache], [Date, Sun, 24 Jul 2016 12:08:44 GMT]...} 204