0

I'm new at EF in MVC. I'm needed to implement a concurrency check in the application.

I was following the MSDN help regarding built-in suport for Concurrency handling in EF: http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application

But in my case, I'm doing a partial update, so I'm using AJAX to call a server side method to do that. in this server side method I need to have the concurrency check implemented.

Following the tutorial I made the required changes in my code:

First in my DB I created the timestamp column:

ALTER TABLE dbo.Job
ADD RowVersion rowversion NULL;

in Entity class:

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

in my Mapping class (using fluent api):

this.Property(t =>     
    t.RowVersion).HasColumnName("RowVersion").IsConcurrencyToken();

in my view model class:

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

in my view file:

@Html.HiddenFor(x => x.RowVersion)

in JS file (the ajax call):

function UpdateJobTransferStatus(jobTransferStatusId, newJobTransferSatusId, currentAction, statusName) {
    var hdnJobId = $("#JobId").val();
    //var hdnRowVersion = $("#RowVersion").val();
    var hdnAccountingId = $("#hdnAccountingSystemId").val();
    $.ajax(
           {
               type: "GET",
               url: ResolveUrl("~/Api/Job/UpdateJobTransferStatus"),
               dataType: "json",
               contentType: "application/json; charset=utf-8",
               data: { jobId: hdnJobId, currentAction: currentAction, jobTransferStatusId: newJobTransferSatusId, accountingSystemId: hdnAccountingId },//,rowVersion: hdnRowVersion },
               success: function (data) {
                   GetPreviousOrNextStatus(jobTransferStatusId, currentAction, statusName, hdnAccountingId);
               },
               error: function (result) {

               }
           });

}

Finally in my controller (on my ajax call): I added the namespace as per MSDN (using System.Data.Entity.Infrastructure;)

[System.Web.Http.HttpGet]
public HttpResponseMessage UpdateJobTransferStatus(int jobId, int currentAction,
    int jobTransferStatusId, short accountingSystemId, byte[] rowVersion)

Here I'm always getting 'null' for 'byte[] rowVersion', regardless of if I sent the value as a param in AJAX call or not (I kept in commented in my code snippet, that I pasted here).

I've checked, the column is getting updated in DB for each successful Insert/Update execution and also the view model is getting the latest value from DB for this RowVersion column on each page load.

Here in the MSDN example they have submitted the form, but I'm doing it using AJAX call, except that I tried to kept every aspect the same.

Sending the entire view model object may do the job for me, but I don't need the entire object to perform my partial update (I've not tried that though).

How can I pass the rowVersion to the ajax call?

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
Souvik Das
  • 41
  • 1
  • 7
  • Thanks Paul, I realized my formatting mistake right after I submitted it, but you were way quicker than me. :D – Souvik Das Jun 28 '16 at 17:22

1 Answers1

3

You should send it as a string and convert it back to a byte array on the server. This is because the HTML page stores it as a string in the input tag (of type hidden) in base64 format. A slight change on the c# side should fix it.

View: no change needed to your existing code, the default behavior of HiddenFor when a byte[] is passed in is to convert it to a Base 64 string

c# code

[System.Web.Http.HttpGet]
public HttpResponseMessage UpdateJobTransferStatus(int jobId, int currentAction, int jobTransferStatusId, short accountingSystemId, string rowVersion){
    var rowVersionBytes = System.Convert.FromBase64String(rowVersion); // convert to byte array
}

Javascript:

function UpdateJobTransferStatus(jobTransferStatusId, newJobTransferSatusId, currentAction, statusName) {
    var hdnJobId = $("#JobId").val();
    var hdnRowVersion = $("#RowVersion").val(); // this will be a string
    var hdnAccountingId = $("#hdnAccountingSystemId").val();
    $.ajax(
       {
           type: "GET",
           url: ResolveUrl("~/Api/Job/UpdateJobTransferStatus"),
           dataType: "json",
           contentType: "application/json; charset=utf-8",
           data: { jobId: hdnJobId, currentAction: currentAction, jobTransferStatusId: newJobTransferSatusId, accountingSystemId: hdnAccountingId, rowVersion: hdnRowVersion }
           success: function (data) {
               GetPreviousOrNextStatus(jobTransferStatusId, currentAction, statusName, hdnAccountingId);
           },
           error: function (result) {
           }
       });
}

Finally because you are actually executing an update this should be a HttpPost or an HttpPut and not an HttpGet.

  • HttpGet should only be used when retrieving data and no data is altered.
  • HttpPut should be used when the same modification can be called multiple times with the exact same expected result (this is also called idempotence), this is usually used for Updates.
  • HttpPost should be used when the same call will result in a different result (either on the server or in the response) each time it is called, this is usualy used for Creates

I see one more mistake in your sql which might create an unexpected issue with existing data. The column RowVersion should be marked as NOT NULL as even on an existing table Sql Server will populate all existing records with a value when the rowversion type is specified.

ALTER TABLE dbo.Job
ADD RowVersion rowversion NOT NULL
Igor
  • 60,821
  • 10
  • 100
  • 175
  • 1
    Wouldn't it be better to serialize the timestamp as base64? What you have there would not work if any of the bytes in the timestamp were outside the UTF8 range. – Heretic Monkey Jun 28 '16 at 17:29
  • @MikeMcCaughan - would that be possible? A `rowversion` is composed of 8 bytes (each having a value of 0-255). I do not think UTF8 would have a problem with this as a single utf8 character can be 1 to 4 bytes in size. However it would not be a bad idea to explicitly specify the same conversion on both the Controller and the View so there is no confusion as to the format being used for byte array conversion. Let me update my answer. – Igor Jun 28 '16 at 17:36
  • Well, **the** Jon Skeet says (paraphrasing), [one should not use strings to encode arbitrary bytes which are not themselves strings](http://stackoverflow.com/a/7997022/215552). – Heretic Monkey Jun 28 '16 at 17:42
  • @MikeMcCaughan - I updated my answer again. It also turns out that when a byte array (`byte[]`) is passed to `HiddenFor` the default is to also convert it to a base 64 string (no idea if that is configurable or hard coded etc). So you were right, the correct answer is call `FromBase64String` in the Controller. – Igor Jun 28 '16 at 18:02
  • This needs more upvotes. Building your own model binder necessitates handling the timestamp manually. – Glitcher Aug 24 '16 at 09:43