Зачем Entity Framework выполнять обновление при изменении RowVersion?

У меня возникла проблема с перехватом исключения DbUpdateConcurrencyException с помощью Entity Framework 5. Проблема заключается в том, что EF обновляет запись, несмотря на то, что свойство RowVersion (Timestamp) изменилось с момента извлечения строки из базы данных. Действие HttpGet Edit получает профиль пользователя из базы данных, и я передаю значения в ViewModel, включая список флажков, чтобы пользователь мог выбрать роли и передать их в представление.

public ActionResult Edit(int id = 0)
    {
      UserProfile userProfile = unitOfWork.UserProfileRepository.GetUserProfileById(id);

      UserProfileEditViewModel viewModel = new UserProfileEditViewModel
      {
        UserId = userProfile.UserId,
        UserName = userProfile.UserName,
        FirstName = userProfile.FirstName,
        LastName = userProfile.LastName,
        Email = userProfile.Email,
        RowVersion = userProfile.RowVersion,
      };

      var allRoles = unitOfWork.RoleRepository.GetAllRoles();
      var userProfileRoles = userProfile.Roles;
        foreach (var role in allRoles)
        {
          if (userProfileRoles.Contains(role))
          {
            viewModel.Roles.Add(new RoleViewModel
            {
              RoleId = role.RoleId,
              RoleName = role.RoleName,
              Assigned = true,
            });
          }
          else
          {
            viewModel.Roles.Add(new RoleViewModel
            {
              RoleId = role.RoleId,
              RoleName = role.RoleName,
              Assigned = false,
            });
          }
        }
        return View(viewModel);
    }

Затем у меня есть базовое представление редактирования, в котором есть HiddenFor для свойства RowVersion.

@model MvcWebsite.ViewModels.UserProfileEditViewModel

@{
ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm()) {
@Html.AntiForgeryToken()
@Html.ValidationSummary(true)

<fieldset>
    <legend>UserProfile</legend>

    @Html.HiddenFor(model => model.UserId)
    @Html.HiddenFor(model => model.UserName)
    @Html.HiddenFor(model => model.RowVersion)
    <div class="editor-label">
        @Html.LabelFor(model => model.UserName)
    </div>
    <div class="editor-field">
        @Html.DisplayFor(model => model.UserName)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => model.FirstName)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.FirstName)
        @Html.ValidationMessageFor(model => model.FirstName)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => model.LastName)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.LastName)
        @Html.ValidationMessageFor(model => model.LastName)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => model.Email)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.Email)
        @Html.ValidationMessageFor(model => model.Email)
    </div>
    <div class="editor-field">
        <table>
            <tr>
                @Html.EditorFor(model => model.Roles)
                @Html.ValidationMessageFor(model => model.Roles)
            </tr>
        </table>
    </div>
    <p>
        <input type="submit" value="Save" />
    </p>
</fieldset>
}

<div>
@Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

Затем у меня есть действие HttpPost Edit, которое берет данные из viewModel и добавляет их в профиль пользователя, который я извлек из базы данных. Затем я изменяю свойства этого профиля на те, которые были получены от клиента, включая RowVersion (вернув RowVersion в исходное состояние).

[HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(UserProfileEditViewModel model)
    {
      try
      {
        if (ModelState.IsValid)
        {
          var userProfile = unitOfWork.UserProfileRepository.GetUserProfileById(model.UserId);

          userProfile.UserName = model.UserName;
          userProfile.FirstName = model.FirstName;
          userProfile.LastName = model.LastName;
          userProfile.Email = model.Email;
          userProfile.RowVersion = model.RowVersion;

          var roleAssignments = model.Roles;

          foreach (var roleAssignment in roleAssignments)
          {
            if (roleAssignment.Assigned)
            {
              userProfile.Roles.Add(unitOfWork.RoleRepository.GetRoleById(roleAssignment.RoleId));
            }
            else
            {
              userProfile.Roles.Remove(unitOfWork.RoleRepository.GetRoleById(roleAssignment.RoleId));
            }
          }

          unitOfWork.UserProfileRepository.UpdateUserProfile(userProfile);
          unitOfWork.Save();

          return RedirectToAction("Details", new { id = userProfile.UserId });
        }
      }
      catch (DbUpdateConcurrencyException ex)
      {
        ... Code omitted for brevity
      }
      }
      return View(model);
    }

Я проверяю это, дважды открывая страницу редактирования. Затем я обновляю вторую страницу и нажимаю «Сохранить», что фиксирует изменения в базе данных. База данных показывает, что версия строки фактически была изменена, чтобы отразить обновление. Когда я изменяю вторую страницу и нажимаю «Сохранить», несмотря на то, что версия строки этого профиля отличается от версии строки, созданной при сохранении первого профиля, изменения также сохраняются в базе данных. Я проверил, что действительно, версия строки в базе данных фактически изменена дважды.

У меня странное чувство, что я упускаю здесь очевидное, но любая помощь будет принята с благодарностью.


person MickySmig    schedule 04.06.2013    source источник


Ответы (1)


обычно вы не меняете версию строки явно. это будет обрабатываться самой ORM. Что бы вы сделали, так это сравните версию модели представления с версией домена. если они не совпадают с вами, вам нужно обработать этот сценарий.

if(viewmodel.RowVersion != domainModel.RowVersion)
{
    //model has changed, notify user...
}
else
{
   //update domain model
   //save changes
}

Вы можете предотвратить это, сделав RowVersion неизменяемым.

class domainmodel
{
    ...
    public int RowVersion {get; private set;}
}
person Jason Meckley    schedule 04.06.2013
comment
Причина, по которой я меняю версию строки, заключается в том, что ошибка параллелизма будет обнаружена в первую очередь. Версия строки, содержащаяся в модели представления, является исходной. если я не изменю версию строки, я никогда не получу ошибку параллелизма, поскольку профиль, состояние которого рисуется, извлекается после того, как произошло первое обновление. - person MickySmig; 04.06.2013
comment
просто любопытно, как вы настроили столбец версий? можешь выложить код? - person Jason Meckley; 05.06.2013
comment
stackoverflow .com/questions/16952031/ - person MickySmig; 07.06.2013
comment
Я понял, что причина, по которой происходило это обновление, заключалась в том, что я меняю текущее значение версии строки, а EF использует исходные значения для проверок параллелизма. Поскольку я получаю пользователя из базы данных после внесения изменений, исходная версия строки такая же. Извините, что потратил ваше время. Большое спасибо за помощь. - person MickySmig; 07.06.2013
comment
не нужно извиняться. вот как мы учимся, задавая вопросы с ошибками. Я рад, что ты смог это понять. понимание оптимистичного параллелизма и отключенных сред является обычным препятствием для многих разработчиков. Теперь у вас есть знания, которыми вы можете поделиться с другими :) - person Jason Meckley; 07.06.2013