[NoBrainer] Avoiding Concurrency Exceptions when working with a DataServiceContext in PowerShell

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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: