简体   繁体   English

如何处理更新实体。 NHibernate + ASP.NET MVC

[英]How to handle updating entities. NHibernate + ASP.NET MVC

I cannot update created previously entity. 我无法更新以前创建的实体。 I'm getting a StaleObjectException exception with message: 我收到带有消息的StaleObjectException异常:

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

I don't share the update process with anyone. 我不与任何人分享更新过程。 What's the problem? 有什么问题?

Data Access / DI 数据访问/ 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();
    }
}

Data Access / Mappings 数据访问/映射

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

Data Access / 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();
        }
    }
} 

Inside a Controller 控制器内部

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);
        }
    }
}

How do I update my entities? 如何更新我的实体? Thanks! 谢谢!

EDIT 编辑

So I added unsaved-value="{Guid.Empty goes here}" to my markup. 所以我将unsaved-value="{Guid.Empty goes here}"到我的标记中。 Moreover I've tried to do the next thing: 而且我试着做下一件事:

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;
            }
        }

    }
}

And this gives me the same effect.. I mean transaction.Commit(); 这给了我同样的效果..我的意思是transaction.Commit(); after Merge gives the same exception. Merge之后给出相同的异常。

Also I'm wondering should I expose, using hidden input, the entity ID on the Edit view? 另外我想知道我应该使用隐藏输入在Edit视图中公开实体ID吗?

EDIT 编辑

So entity really detaches. 所以实体真的脱离了。 When it passes to controller the ID equals Guid.Empty . 当它传递给控制器​​时, ID等于Guid.Empty How do I handle it, Merge or Reattach ? 我该如何处理, MergeReattach

There are two scenarios that you can run into, given your code pattern. 根据您的代码模式,您可以遇到两种情况。

  1. You could retrieve the object from the db using ISession.Get() which can be followed by a change/update to the retrieved object. 您可以使用ISession.Get()从数据库中检索对象,然后可以对检索到的对象进行更改/更新。 For this change to be effective, all you need to do is flush the session or commit the transaction as Nhibernate will track all the changes for you automatically. 要使此更改生效,您需要做的就是刷新会话或提交事务,因为Nhibernate会自动跟踪所有更改。

  2. You have a transient instance, an object that is not associated with the ISession in context, from which you want to update. 您有一个瞬态实例,一个与上下文中的ISession无关的对象,您要从中更新。 In this case, from my experience, the best practice is to ISession.Get() the object and make the corresponding changes to the object you just retrieve. 在这种情况下,根据我的经验,最佳实践是ISession.Get()对象并对您刚刚检索的对象进行相应的更改。 (usually your view model is different from your domain model as well, don't mix both) This pattern is shown below. (通常您的视图模型也与您的域模型不同,不要混合使用)此模式如下所示。 It works all the time. 它一直有效。 Make sure you also use ISession.SaveOrUpdate() . 确保您还使用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);
    }
}

Also, notice that your controller is probably instantiated on a per-request basis, hence, the lifetime of your ISession does not span multiple calls to the different methods you have in your controller. 另外,请注意您的控制器可能是基于每个请求实例化的,因此,您的ISession的生命周期不会跨越您对控制器中不同方法的多次调用。 In other words, every method is almost always working within the context of a new ISession (unit of work). 换句话说,每个方法几乎总是在新的ISession (工作单元)的上下文中工作。

Your logic is not good, becouse you use domain model like Employee as ViewModel. 你的逻辑不好,因为你使用像Employee这样的域模型作为ViewModel。 Best practice is use CreateEmploeeViewModel and EditEmployeeViewModel and separate Domain Logic and View Model logic. 最佳实践是使用CreateEmploeeViewModel和EditEmployeeViewModel并分离Domain Logic和View Model逻辑。 For Example: 例如:

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; }
 }

To convert from Employee to ViewModel I prefer yo use Automapper . 要从Employee转换为ViewModel,我更喜欢使用Automapper

So controller Actions become to looks like: 所以控制器动作看起来像:

[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);
    }

In this case NHibernate knows that you update Employee, and you can`t remove some properties which not exist in your View. 在这种情况下,NHibernate知道您更新了Employee,并且您不能删除View中不存在的一些属性。

I believe your Employee object has become what NHibernate calls "detached" between the GET and POST of your Edit action methods. 我相信你的Employee对象已成为NHibernate在Edit操作方法的GET和POST之间调用“分离”的对象。 See the NHibernate documentation on this topic for more details and some solutions. 有关更多详细信息和一些解决方案,请参阅有关此主题的NHibernate文档 In fact, the link describes the exact GET-POST scenario you seem to be using. 实际上,该链接描述了您似乎正在使用的确切GET-POST方案。

You may need to reattach your Employee object and/or specify the "unsaved value" as Firo suggested so that NHibernate knows an Employee with an ID of Guid.Empty has not been persisted to the database yet. 您可能需要重新附加Employee对象和/或指定Firo建议的“未保存的值”,以便NHibernate知道ID为Guid.Empty的Employee尚未持久保存到数据库中。 Otherwise, as Firo suggested, NHibernate sees Guid.Empty as a valid ID, and thinks the object has already been saved to the database but the session in which it was retrieved has been discarded (hence, the object becoming "detached"). 否则,正如Firo建议的那样,NHibernate认为Guid.Empty是一个有效的ID,并且认为该对象已经被保存到数据库中,但是它被检索的会话已经被丢弃(因此,对象变得“分离”)。

Hope this helps. 希望这可以帮助。

You ask, 你问,

Also I'm wondering should I expose, using hidden input, the entity ID on the Edit view? 另外我想知道我应该使用隐藏输入在Edit视图中公开实体ID吗?

Yes, you should. 是的你应该。 You should also expose the Version in a hidden input as its business is to help prevent concurrent edits to the same entity. 您还应该在隐藏输入中公开版本 ,因为它的业务是帮助防止对同一实体的并发编辑。 The StaleObjectException hints that you've got versioning turned on, and in that case, the update will only work if the version value (Int32) that you send back is identical to the one in the database. StaleObjectException暗示您已启用版本控制,在这种情况下,只有在您发回的版本值(Int32)与数据库中的版本值相同时,更新才会起作用。

You can always get around it by reloading the entity and mapping it, ensuring that the Version value is likely to match, but that seems to subvert its purpose. 你总是可以通过重新加载实体并映射它来确保版本值可能匹配,但这似乎颠覆了它的目的。

IMHO, I'd put the entity ID and Version in a hidden input, and on postback, reload the entity and map the data. 恕我直言,我将实体ID和版本放在隐藏的输入中,并在回发时重新加载实体并映射数据。 That way, like Ivan Korytin suggests above, you would not have to carry around properties that aren't needed in your view. 这样,就像上面提到的Ivan Korytin所说的那样,您不必携带视图中不需要的属性。 You can also handle the staleness at the controller level and add a validation error rather than have NHibernate tell you your object is stale. 您还可以处理控制器级别的陈旧性并添加验证错误,而不是让NHibernate告诉您对象是陈旧的。

Ivan Korytin outlines the standard process for handling a simple edit of an entity. Ivan Korytin概述了处理实体的简单编辑的标准流程。 The only issue with his answer is that it does not address the Version property. 他的答案唯一的问题是它没有解决Version属性。 IMHO, the database should not be versioned, or the Version property should matter. 恕我直言,数据库不应该版本化,或版本属性应该重要。

If you are one of us that no answer from here helped, try looking what for an "ID" in your entity is sending. 如果您是我们中的一员,这里没有答案,请尝试查看您实体发送的“ID”。

I have the same problem but in the end, I saw that I was changing the ID to another number (in NHibernate the id will be self generated, if you set it up that way! ). 我有同样的问题,但最后,我看到我正在将ID更改为另一个数字(在NHibernate中,id将自行生成, 如果你这样设置它! )。

So, bottom of line, check if the structure of the data that you are sending and the values, match what you are expecting to send. 因此,在行的底部,检查您要发送的数据的结构和值是否与您期望发送的内容相匹配。

Hope I can help anyone! 希望我可以帮助任何人! :) :)

"unsaved value" is missing. 缺少“未保存的价值”。 hence NH thinks that Guid.Empty is a valid id 因此NH认为Guid.Empty是一个有效的id

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

If you want to update some entity's fields you don't need to use session.Update(), use session.Flush() before close transaction. 如果要更新某些实体的字段,则不需要使用session.Update(),请在关闭事务之前使用session.Flush()。

session.Update() -> Update the persistent instance with the identifier of the given transient instance. session.Update() - >使用给定瞬态实例的标识符更新持久化实例。

After all it helps, but I think it's horrible: 毕竟它有所帮助,但我认为这很糟糕:

[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 });
        }
        /*...*/
    }
}

Even if I put HiddenFor fields on Edit view for ID (and Version ) the ID being passed is ordinary a Guid.Empty which states that employee is transient. 即使我在Edit视图中为ID (和Version )放置了HiddenFor字段,传递的ID也是普通的Guid.Empty ,它表明employee是暂时的。

I'm really appreciated for your help guys! 我非常感谢你的帮助!

Questions 问题

I know what viewmodels are, but quite not understood how does it help with detaching . 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?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 ASP.NET MVC 4-自定义成员资格和角色实体未更新 - ASP.NET MVC 4 - Custom Membership & Role Entities Not Updating LINQ to Entities仅支持无参数的构造函数和初始化程序。 (ASP.NET Web API) - Only parameterless constructors and initializers are supported in LINQ to Entities. (ASP.NET Web API) 如何在ASP.NET MVC中处理会话 - How to handle session in asp.net mvc 如何将对象从Asp.Net MVC 4重新附加到Fluent NHibernate - How to reattach objects from Asp.Net MVC 4 to Fluent NHibernate ASP.NET MVC中的CreateCriteria和旧的NHibernate - CreateCriteria and old NHibernate in ASP.NET MVC ASP.NET MVC中的NHibernate上下文会话 - NHibernate Contextual Sessions in ASP.NET MVC ASP.NET MVC Fluent NHibernate更新记录,而无需我显式调用Repository.Update(item) - ASP.NET MVC Fluent NHibernate updating record without me explicitly calling Repository.Update(item) ASP.Net MVC和NHibernate Burrow&Fluent NHibernate - ASP.Net MVC & NHibernate Burrow & Fluent NHibernate 为什么我的ASP.NET MVC 4应用程序创建新实体而不是更新旧实体? - Why does my ASP.NET MVC 4 application create new entities instead of updating the old ones? 垃圾收集器将如何处理ASP.NET MVC中的会话 - How will the Garbage Collector handle a Session in ASP.NET MVC
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM