1

I'm trying to add optimistic concurrency to my application by following this tutorial.

Unfortunately the app works as if I haven't changed anything. I open two browsers, in both I edit the same line, overwrite the same value and put it at the first and the other doesn't respond at all. I will save the second and still no warning. I tried to step through it, and with the edit button and when saved, RowVersion changes me. Only SaveChanges() does not throw an exception.

SQL server:

CREATE TABLE [dbo].[Table] (
    [Id]         INT            IDENTITY (1, 1) NOT NULL,
    [RowVersion] ROWVERSION     NULL,
...

Model:

public partial class Table
{
    public int Id { get; set; }

    [Column(TypeName = "timestamp")]
    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    [MaxLength(8)]
    public byte[] RowVersion { get; set; }

Controller:

[HttpPost]
public ActionResult Table_Edit([Bind(Include = "Id,RowVersion")]Table tab)
{
    try
    {
        if (ModelState.IsValid)
        {
            db.Entry(tab).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
    }
    catch (DbUpdateConcurrencyException ex)
    {
        var entry = ex.Entries.Single();
        Table clientValues = (Table)entry.Entity;
        Table  databaseValues = (Table )entry.GetDatabaseValues().ToObject();
        //changed column
        if (databaseValues.Column_5 != clientValues.Column_5)
            ModelState.AddModelError("Name", "Current value: "
                + databaseValues.Column_5);

        ModelState.AddModelError(string.Empty, "The record you attempted to edit "
            + "was modified by another user after you got the original value. The "
            + "edit operation was canceled and the current values in the database "
            + "have been displayed. If you still want to edit this record, click "
            + "the Save button again. Otherwise click the Back to List hyperlink.");
        tab.RowVersion = databaseValues.RowVersion;
    }

    catch (DataException /* dex */)
    {
        //Log the error (uncomment dex variable name after DataException and add a line here to write a log.
        ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
    }
    return View(tab);
}
GSerg
  • 76,472
  • 17
  • 159
  • 346
Jana
  • 11
  • 2
  • 1
    I notice that you are using the Column attribute (and setting it's type to `timestamp`) instead of the attribute `Timestamp`. I do not know if they are equivalent; have you tried using the latter (which is the one referenced in the docs)? – ssarabando May 03 '19 at 10:34
  • I will upvote this because you have an ideal amount of information. However your test and assumptions leave a little to the imagination. Think of row-version as an id to your row at a point in time, just because you update a table doesn't force a concurrency check You need to update the row version with some previous version to have it complain – TheGeneral May 03 '19 at 10:35
  • Did you remember to round-trip `RowVersion` through your UI View as well (usually as a `hidden` input field. Also, AFAIK the TimeStamp `rowversion` column in SQL should have been generated as non-nullable. – StuartLC May 03 '19 at 10:35
  • I managed to rewrite it to [Timestamp] public byte[] RowVersion { get; set; } and it catch DbUpdateConcurrencyException – Jana May 03 '19 at 11:00
  • 1
    Then @ssarabando was correct - you don't want to be using SqlServer's TimeStamp - you'll want [rowversion](https://learn.microsoft.com/en-us/sql/t-sql/data-types/rowversion-transact-sql?view=sql-server-2017). Very confusingly, EF called the attribute `[Timestamp]`, but then halfway down the MSDN article you've referenced the author tries to explain that the legacy versions of SqlServer prior to 2008 used TimeStamp ... – StuartLC May 03 '19 at 11:03

0 Answers0