1

I have to manage concurrency in our Framework but I can not generate an DbUpdateConcurrencyException. We using SQL Server 2008, EF 6 and AutoMapper 5.

SQL

ALTER TABLE [Keyword].[Keyword] ADD Rowversion [Rowversion] NOT NULL

Model

[Table("Keyword", Schema = "Mailing")]
public class KeywordModel : _Auditable
{
    [Key]
    public int KeywordId { get; set; }

    public string Name { get; set; }
    public string Hashtag { get; set; }
    public string Namespaces { get; set; }
    public string Html { get; set; }

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

ViewModel

public class KeywordEditViewModel
{
    [HiddenInput(DisplayValue = false)]
    public int KeywordId { get; set; }

    [Display(Name = "Name")]
    public string Name { get; set; }

    [Display(Name = "Hashtag")]
    public string Hashtag { get; set; }

    [Display(Name = "Namespaces")]
    [Description("Separate namespaces by ','")]
    public string Namespaces { get; set; }

    [Required]
    [AllowHtml]
    [UIHint("MultilineText")]
    [Display(Name = "Html")]
    [Description("Prefix or sufix your razor variables with #")]
    public string Html { get; set; }

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

Controller

    [HttpPost]
    public virtual ActionResult Edit(KeywordEditViewModel model, string @return)
    {
        var data = new JsonResultData(ModelState);

        if (ModelState.IsValid)
        {
            data.RunWithTry((resultData) =>
            {
                KeywordManager.Update(model.KeywordId, model, CurrentUser.UserId);
                resultData.RedirectUrl = !string.IsNullOrEmpty(@return) ? @return : Url.Action("Index");
            });
        }

        return Json(data);
    }

Business

    public KeywordModel Update(int id, object viewmodel, int userId)
    {
        var model = Get(id);

        Mapper.Map(viewmodel, model);
        SaveChanges("Update mailing keyword", userId);

        return model;
    }

AutoMapper

CreateMap<KeywordModel, KeywordEditViewModel>();
CreateMap<KeywordEditViewModel, KeywordModel>();

In my tests, the RowVersion fields has a different value in the database but SaveChange does not generate exception DbUpdateConcurrencyException.

SQL trace

UPDATE [Mailing].[Keyword]
SET [Namespaces] = @0, [audit_LastUpdate] = @1
WHERE (([KeywordId] = @2) AND ([RowVersion] = @3))
SELECT [RowVersion]
FROM [Mailing].[Keyword]
WHERE @@ROWCOUNT > 0 AND [KeywordId] = @2 
@0: 'titi' (Type = String, Size = -1)
@1: '09/11/2016 13:35:54' (Type = DateTime2)
@2: '1' (Type = Int32)
@3: 'System.Byte[]' (Type = Binary, Size = 8)
  • Check what sql is generated by that update (context.Database.Log = ...) – Evk Nov 09 '16 at 10:43
  • You have something like `@Html.HiddenFor(m => m.RowVersion)` in your view? (I don't know automapper) So if you save, it is the same rowversion as you loaded before? – Christian Gollhardt Nov 09 '16 at 15:31
  • Yes, I have a hidden input 'RowVersion' in my view. Just before the 'SaveChange', the value in my object is different than the database value but the update works ... – Antony FERRIERE Nov 09 '16 at 16:28

1 Answers1

1

Better late than never...

I solved this issue after a few hours of searching for this exact scenario. EF, automapper, viewmodels, the whole nine. After dozens of articles, someone finally said something that led me the right direction and its actually quite a simple fix.

When you make this call: var model = Get(id); To get your current values and then map the updates over top, the new row version is selected with it.

The fix is to make "Get(id)" select without tracking. Problem solved. In my case my repo had a method for "Edit(entity)" that marked the object as modified and attached it if it was detached. You may be able to do the same here.

kd345205
  • 319
  • 1
  • 8