Как обрабатывать обновления сущностей.NHibernate + ASP.NET MVC - PullRequest
10 голосов
/ 14 марта 2012

Я не могу обновить созданную ранее сущность. Я получаю исключение StaleObjectException с сообщением:

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>

Доступ к данным / Репозиторий

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?

Ответы [ 8 ]

10 голосов
/ 23 марта 2012

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

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

  2. У вас есть временный экземпляр,объект, который не связан с ISession в контексте, из которого вы хотите обновить.В этом случае, исходя из моего опыта, лучше всего ISession.Get() объект и внести соответствующие изменения в объект, который вы только что получили.(обычно модель представления отличается от модели вашего домена, не смешивайте обе). Этот шаблон показан ниже.Это работает все время.Убедитесь, что вы также используете 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 (единицы работы).

3 голосов
/ 20 марта 2012

Ваша логика не очень хорошая, потому что вы используете модель домена, например 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; }
 }

Для преобразования из Employee в 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, и вы не можете удалить некоторые свойства, которых нет в вашем представлении.

2 голосов
/ 21 марта 2012

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

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

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

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

ИМХО, я бы поставил сущностьИдентификатор и версия в скрытом вводе и при обратной передаче перезагрузите объект и отобразите данные.Таким образом, как Иван Корытин предлагает выше, вам не нужно носить с собой свойства, которые вам не нужны.Вы также можете обрабатывать устаревание на уровне контроллера и добавлять ошибку проверки вместо того, чтобы NHibernate сообщал вам, что ваш объект устарел.

Иван Корытин описывает стандартный процесс обработки простого редактированиясущность.Единственная проблема с его ответом состоит в том, что он не обращается к свойству Version.ИМХО, база данных не должна быть версионной, или свойство Version должно иметь значение.

2 голосов
/ 14 марта 2012

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

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

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

1 голос
/ 14 апреля 2015

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

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

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

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

1 голос
/ 14 марта 2012

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

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

1 голос
/ 14 марта 2012

«несохраненное значение» отсутствует. следовательно, NH считает, что Guid.Empty является действительным идентификатором

<id name="ID" column="EmployeeID" unsaved-value="0000000-0000-0000-0000-000000000000">
0 голосов
/ 23 марта 2012

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

[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?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...