2012-03-14 3 views
10

Я не могу обновить созданный ранее объект. Я получаю StaleObjectException исключение с сообщением:Как обрабатывать обновления объектов. NHibernate + ASP.NET MVC

Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [Project.DomainLayer.Entities.Employee#00000000-0000-0000-0000-000000000000]

Я не разделяю процесс обновления с кем-либо. В чем проблема?

Доступ к данным/DI

public class DataAccessModule : Ninject.Modules.NinjectModule 
{ 
    public override void Load() 
    { 
     this.Bind<ISessionFactory>() 
      .ToMethod(c => new Configuration().Configure().BuildSessionFactory()) 
      .InSingletonScope(); 

     this.Bind<ISession>() 
      .ToMethod(ctx => ctx.Kernel.TryGet<ISessionFactory>().OpenSession()) 
      .InRequestScope(); 

     this.Bind(typeof(IRepository<>)).To(typeof(Repository<>)) 
      .InRequestScope(); 
    } 
} 

доступа к данным/Отображения

<?xml version="1.0" encoding="utf-8" ?> 
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Project.DomainLayer" namespace="Project.DomainLayer.Entities"> 
<class name="Employee" optimistic-lock="version"> 
    <id name="ID" column="EmployeeID" unsaved-value="00000000-0000-0000-0000-000000000000"> 
     <generator class="guid.comb" /> 
    </id> 
    <version name="Version" type="Int32" column="Version" /> 
    <!-- properties --> 
    <property name="EmployeeNumber" /> 
    <!-- ... --> 
    <property name="PassportRegistredOn" not-null="true" /> 
    <!-- sets --> 
    <set name="AttachedInformation" cascade="all"> 
     <key column="EmployeeID" /> 
     <element column="Attachment" /> 
    </set> 
    <set name="TravelVouchers" cascade="all"> 
     <key column="EmployeeID" /> 
     <one-to-many class="TravelVoucher" /> 
    </set> 
    </class> 
</hibernate-mapping> 

Доступ к данным/Repository

public class Repository<T> : IRepository<T> where T : AbstractEntity<T>, IAggregateRoot 
{ 
    private ISession session; 

    public Repository(ISession session) 
    { 
     this.session = session; 
    } 

    // other methods are omitted 

    public void Update(T entity) 
    {    
     using(var transaction = this.session.BeginTransaction()) 
     { 
      this.session.Update(entity); 
      transaction.Commit(); 
     } 
    } 
    public void Update(Guid id) 
    {    
     using(var transaction = this.session.BeginTransaction()) 
     { 
      this.session.Update(this.session.Load<T>(id)); 
      transaction.Commit(); 
     } 
    } 
} 

Внутри контроллера

public class EmployeeController : Controller 
{ 
    private IRepository<Employee> repository; 

    public EmployeeController(IRepository<Employee> repository) 
    { 
     this.repository = repository; 
    }   
    public ActionResult Edit(Guid id) 
    { 
     var e = repository.Load(id); 
     return View(e); 
    } 
    [AcceptVerbs(HttpVerbs.Post)] 
    public ActionResult Edit(Employee employee) 
    { 
     if(ModelState.IsValid) 
     { 
      repository.Update(employee); 
      return RedirectToAction("Deatils", "Employee", new { id = employee.ID }); 
     } 
     else 
     { 
      return View(employee); 
     } 
    } 
} 

Как обновить свои объекты? Спасибо!

EDIT

Поэтому я добавил unsaved-value="{Guid.Empty goes here}" к моей разметке. Кроме того, я пытался сделать следующую вещь:

public void Update(T entity) 
{ 
    using(var transaction = this.session.BeginTransaction()) 
    { 
     try 
     { 
      this.session.Update(entity); 
      transaction.Commit(); 
     } 
     catch(StaleObjectStateException ex) 
     { 
      try 
      { 
       session.Merge(entity); 
       transaction.Commit(); 
      } 
      catch 
      { 
       transaction.Rollback(); 
       throw; 
      } 
     } 

    } 
} 

И это дает мне тот же самый эффект .. я имею в виду transaction.Commit(); после Merge дает такое же исключение.

Также мне интересно, следует ли открывать, используя скрытый ввод, объект ID на вид Edit?

EDIT

Так предприятие действительно отрывается. Когда он переходит к контроллеру, ID равен Guid.Empty. Как мне это обработать, Merge или Reattach?

+0

Не могли бы вы разместить некоторые из SQL, которые были сгенерированы? Кроме того, вы подтвердили, что у сотрудника есть идентификатор на нем после отправки? – shanabus

ответ

9

Существует два сценария, с которыми вы можете столкнуться, учитывая ваш код.

1) Вы можете получить объект из базы данных, используя ISession.Get, за которым может последовать изменение/обновление получаемого объекта. Чтобы это изменение было эффективным, все, что вам нужно сделать, это очистить сеанс или совершить транзакцию, поскольку Nhibernate автоматически отслеживает все изменения.

2) У вас есть временный экземпляр, объект, который не связан с ISession в контексте, из которого вы хотите обновить. В этом случае, по моему опыту, наилучшей практикой является ISession. Получите объект и сделайте соответствующие изменения для объекта, который вы только что извлекли (обычно ваша модель просмотра отличается от вашей модели домена, не смешивайте оба) Этот шаблон показан ниже. Он работает все время. Убедитесь, что вы также используете ISession.SaveOrUpdate.

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Employee employee) 
{ 
    if(ModelState.IsValid) 
    { 
     var persistentEmployee = repository.Get(employee.Id); 
     if( persistentEmployee == null){ 
      throw new Exception(String.Format("Employee with Id: {0} does not exist.", employee.Id)); 
     } 
     persistentEmployee.Name = employee.Name; 
     persistentEmployee.PhoneNumber = employee.PhoneNumber; 
     //and so on 
     repository.Update(persistentEmployee); 
     return RedirectToAction("Deatils", "Employee", new { id = employee.ID }); 
    } 
    else 
    { 
     return View(employee); 
    } 
} 

Кроме того, обратите внимание, что ваш контроллер, вероятно, экземпляр на каждый запрос основе, следовательно, срок службы вашего ISession не охватывает несколько вызовов различных методов, которые вы имеете в вашем контроллере. Другими словами, каждый метод почти всегда работает в контексте нового ISession (единицы работы).

+0

Кажется, что я всегда получаю 'employee' с' id == Guid.Empty'? – lexeme

+0

Когда вы обновляете объект, идентификатор не должен быть Guid.Empty. Только когда вы в процессе создания/сохранения нового объекта, он будет. Идентификатор может быть создан вашим приложением или базой данных. См. [Nhibernate documentation] (http://www.nhforge.org/doc/nh/en/index.html#mapping-declaration-id-generator) – Newbie

+0

Посмотрите на мой ответ ниже. Так оно и работает. Советы приветствуются. – lexeme

1

Отсутствует несохраненная стоимость. следовательно, NH считает, что Guid.Empty является действительным ID

<id name="ID" column="EmployeeID" unsaved-value="0000000-0000-0000-0000-000000000000"> 
+0

Это не помогает ( – lexeme

1

Если вы хотите обновить поля некоторой сущности в вам не нужно использовать Session.update(), использование session.Flush() перед непосредственной сделки.

session.Update() -> Обновить постоянный экземпляр с идентификатором данного экземпляра переходного процесса.

2

Я считаю, что ваш объект Employee стал тем, что NHibernate называет «отделенным» между GET и POST ваших методов действий «Редактировать». См. NHibernate documentation на эту тему для получения более подробной информации и некоторых решений. Фактически, ссылка описывает точный сценарий GET-POST, который вы, кажется, используете.

Возможно, вам понадобится повторно привязать объект Employee и/или указать «несохраненное значение», как предложено Firo, чтобы NHibernate знал сотрудника с идентификатором Guid.Empty еще не сохранялся в базе данных. В противном случае, как предположил Фиро, NHibernate видит Guid.Empty в качестве действительного идентификатора и считает, что объект уже сохранен в базе данных, но сеанс, в котором он был извлечен, был отброшен (следовательно, объект становится «отсоединенным»).

Надеюсь, это поможет.

3

Ваша логика не очень хорошая, потому что вы используете модель домена, например Employee, как ViewModel. Лучшей практикой является использование CreateEmploeeViewModel и EditEmployeeViewModel и отдельная логика логики и просмотра модели. Для примера:

public class Employee 
{ 
     public virtual int Id { get; set; } 

     public virtual string FirstName { get; set; } 

     public virtual string LastName { get; set; } 

     public virtual string MiddleName { get; set; } 
} 

public class CreateEmployeeViewModel 
{ 
     public virtual string FirstName { get; set; } 

     public virtual string LastName { get; set; } 

     public virtual string MiddleName { get; set; } 
} 

public class EditEmployeeViewModel : CreateEmployeeViewModel 
{ 
     public virtual int Id { get; set; } 
} 

Для перехода от Работника к ViewModel Я предпочитаю использовать йо Automapper.

Так контроллер действия становятся взорам как:

[HttpGet] 
    public virtual ActionResult Edit(int id) 
    { 
     Employee entity = GetEntityById(id); 
     EmployeeEditViewModel model = new EmployeeEditViewModel(); 

     Mapper.Map(source, destination);    

     return View("Edit", model); 
    } 

    [HttpPost] 
    public virtual ActionResult Edit(EmployeeEditViewModel model) 
    { 
     if (ModelState.IsValid) 
     { 
      Employee entity = GetEntityById(model.Id); 

      entity = Mapper.Map(model, entity);    
      EntitiesRepository.Save(entity); 

      return GetIndexViewActionFromEdit(model); 
     }   

     return View("Edit", model); 
    } 

В этом случае NHibernate знает, что вы обновляете Employee, и вы не можете удалить некоторые свойства, которые не существуют в вашем View.

+0

Вы упустили точку вопроса op. Ваш ответ не учитывает параллелизм. – Chev

+0

хотя вы правы, domainmodel vs viewmodel - это хорошо, но он не имеет никакого отношения к вопросу. –

+0

Если вы посмотрите на anwser, вы можете увидеть, что является решением. Вариант 2. –

2

Вы спрашиваете,

Также мне интересно, я должен выставить, используя скрытый ввод, идентификатор объекта на экране редактора?

Да, вам следует. Вы также должны разоблачить Версия в скрытом вводе, поскольку его бизнес помогает предотвратить одновременные изменения одного и того же объекта. StaleObjectException указывает, что вы включили управление версиями, и в этом случае обновление будет работать только в том случае, если значение версии (Int32), которое вы отправляете обратно, идентично таковой в базе данных.

Вы всегда можете обойти его, перезагрузив объект и скопировав его, гарантируя, что значение версии, вероятно, будет соответствовать, но это, кажется, подорвет его цель.

IMHO, я бы поместил идентификатор сущности и версию в скрытый ввод, а после обратной передачи перезагрузите объект и сопоставьте данные. Таким образом, как предлагает Ivan Korytin, вам не придется носить с собой свойства, которые вам не нужны. Вы также можете справиться с неторопливостью на уровне контроллера и добавить ошибку проверки, а не NHibernate сказать, что ваш объект устарел.

Ivan Korytin описывает стандартный процесс обработки простого редактирования объекта. Единственный вопрос с его ответом в том, что он не касается свойства Version. IMHO, база данных не должна быть версией, или значение свойства Version должно иметь значение.

+0

Итак, не использовал бы 'Automapper' с соответствующими режимами просмотра в результате отстранения объектов при редактировании/обновлении? Использование другого 3D-приложения sowtware делает приложение более сложным. Я хочу знать, как сделать редактирование/обновление с помощью 'nhibernate' без отсоединения объектов. – lexeme

+0

Процесс, который вызывает отсоединение, в конечном счете состоит в том, что вы находитесь в веб-среде, а не в среде рабочего стола. Предположим, вы используете ASP.Net MVC, и ваш пользователь вносит изменения в форму редактирования и нажимает кнопку отправки. Модельное связующее будет привязываться к «model» (в данном случае, ваш объект домена), который имеет нулевой конструктор и делает все возможное, чтобы присвоить значения этому «объекту модели» на основе того, что было отправлено обратно. – brightgarden

+0

Если бы вы абсолютно настаивали на поиске решения, в котором результирующий объект модели является полностью прикрепленной сущностью, я уверен, что есть способ, но это было бы интересно только в академическом смысле. С точки зрения создания программного обеспечения и эффективного предоставления программного обеспечения и поддержки базы кода, которая легко для других разработчиков, которые приходят позже, гораздо разумнее следовать стандартным подходам. Нет необходимости в стороннем automapper: вы можете сопоставить свойства вручную, и нет ничего недействительного в отношении этого подхода. – brightgarden

0

В конце концов это помогает, но я думаю, что это ужасно:

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Employee employee) 
{ 
    if(ModelState.IsValid) 
    { 
     var e = repository.Get(id); 

     if(Guid.Empty != e.ID) 
     { 
      e.Department = employee.Department; 
      repository.Update(employee.ID); 
      return RedirectToAction("Details", "Employee", new { id = e.ID }); 
     } 
     /*...*/ 
    } 
} 

Даже если я ставлю HiddenFor поля на Edit зрения для IDVersion) идентификатор передается обыденно Guid.Empty в котором говорится, что employee является преходящи.

Я очень благодарен вам за помощь!

Вопросы

I know what viewmodels are, but quite not understood how does it help with detaching.

Why if I put TextBoxFor(e => e.ID) on Edit view it binds employee like a transient entity without saving the ID value?

+0

Хмм. Может быть, вы должны поставить это как отдельный вопрос, так как @newbie уже решил исходную проблему. – DashK

+0

@helicera позволяет попытаться сохранить все содержание вашего вопроса в самом вопросе. Это часть Этикета здесь, и всем легче понять вопрос. В любом случае, как вы генерируете свои идентификаторы (идентификаторы)? – Newbie

1

Если вы один из нас, что нет ответа здесь не помогло, попробуйте посмотреть, что за «ID» в вашей организации посылает.

У меня такая же проблема, но в конце я увидел, что меняю идентификатор на другой номер (в NHibernate id будет сгенерирован сам, , если вы настроите его таким образом!).

Итак, в нижней части строки проверьте, соответствует ли структура данных, которые вы отправляете, и значениям, которые вы ожидаете отправить.

Надеюсь, я могу помочь любому! :)