I cannot update created previously entity. I’m getting a StaleObjectException exception with message:
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
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. 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(); after Merge gives the same exception.
Also I’m wondering should I expose, using hidden input, the entity ID on the Edit view?
EDIT
So entity really detaches. When it passes to controller the ID equals Guid.Empty. How do I handle it, Merge or Reattach?
There are two scenarios that you can run into, given your code pattern.
You could retrieve the object from the db using
ISession.Get()which can be followed by a change/update to the retrieved object. 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.You have a transient instance, an object that is not associated with the
ISessionin context, from which you want to update. In this case, from my experience, the best practice is toISession.Get()the object and make the corresponding changes to the object you just retrieve. (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 useISession.SaveOrUpdate().Also, notice that your controller is probably instantiated on a per-request basis, hence, the lifetime of your
ISessiondoes not span multiple calls to the different methods you have in your controller. In other words, every method is almost always working within the context of a newISession(unit of work).