0

This is the Patch method of my OdataController

public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Product> patch)
{
   Validate(patch.GetInstance());
   Product product = await service.Products.GetAsync(key);
   if (product == null)
       return NotFound();

   patch.Put(product);

   try
   {
       await service.Products.UpdateAsync(product);
   }
   catch (DbUpdateConcurrencyException)
   {
       if (!await service.Products.ExistAsync(key))
           return NotFound();
       else
           throw;
   }

   return Updated(product);
}

My model has a property:

[Timestamp]
 public byte[] RowVersion { get; set; }

the DbUpdateConcurrencyExceptionseems not working at all. I need to implement concurrency checking mechanism using Etag. I have seen some examples here.But they are not using Delta in there method.

  1. How can I check Concurrency using etags?
  2. Is it possible to implement a custom attribute for concurrency cheacking?

Something Like:

[CustomConcurrencyCheck]
public async Task<IHttpActionResult> Put([FromODataUri] int key, Delta<Product> patch)
{
...
}

Providing a simple example will be highly appreciated.

Rahul
  • 2,431
  • 3
  • 35
  • 77
  • If you use a delta you will only got those properties which were changed. ETag or RowVersion were properties but did you change them? Of course not. Then both are not in delta data. If you did not have the previous values form ETag or RowVersion how will you compare them? – Sir Rufo May 19 '17 at 07:21
  • At the current point you will only see a DbUpdateConcurrencyException if two (or more) Put actions for the same key were processed at the same time. – Sir Rufo May 19 '17 at 07:25

1 Answers1

1

First in WebApiConfig while creating your model you have to specify which property is the ETag, in your case:

var builder = new ODataConventionModelBuilder();
builder.EntityType<Product>()
    .Property(p => p.RowVersion)
    .IsConcurrencyToken();

Later you can retrieve the ETag from the ODataQueryOptions<T> parameter of your Patch method in the controller:

[AcceptVerbs("PATCH", "MERGE")]
public IHttpActionResult Patch([FromODataUri] int key, Delta<Product> delta, ODataQueryOptions<Product> options) {
    var existingEntity = //Code to get existing entity by ID, 404 if not found

    byte[] requestETag = options.IfMatch["RowVersion"] as byte[];
    if(!requestETag.SequanceEqual(existingEntity.RowVersion)) { //Simplified if-statement, also do null-checks and such
        // ETags don't match, return HTTP 412
        return StatusCode(HttpStatusCode.PreconditionFailed);
    } else {
        // The ETags match, implement code here to update your entity
        // You can use the 'Delta<Product> delta' parameter to get the changes and use the 'Validate' function here
        // ...

This is the solution I use, it is a simple check to see if the client which requests the update has the same version of the object the service has. A notable drawback of my solution is that I have to retrieve the existing object from the DB to get it to work, that costs some performance.

This is code for the If-Match header, ODataQueryOptions<T> also has .IfNoneMatch[VersionColumnName] available. Which you can use in your Get method. If the If-None-Match header equals your RowVersion you can return a HTTP 304 (Not modified) and save some bandwidth.

This is a very simple example, if you want to implement your own custom attribute that's up to you. At the very least I would move some of this logic to a helper class so it can be reused.

GWigWam
  • 2,013
  • 4
  • 28
  • 34