问题
I'm having trouble implementing optimisitc concurrency in NHibernate in a meaningful way in a web application. Here is the desired scenario:
- User A opens a form to edit a record
- User B opens the same form to edit the same record
- User A saves their data
- User B tries to save their data but get a warning that the data has been updated.
A common scenario. Here is the update code and entity mapping file:
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Entities" assembly="Domain">
<class name="Record" />
<id name="Id" column="Id" type="long">
<generator class="identity"/>
</id>
<version name="Version" column="Version" type="datetime" />
<property name="Description" column="`Description`" type="string" />
</class>
</hibernate-mapping>
public void Edit(int id, string description, DateTime version)
{
using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
var record = session.Get<Record>(id);
record.Version = version;
record.Description = description;
session.Update(record);
tx.Commit();
}
}
The version value is loaded when the user opens the form and is stored in a hidden field. I was hoping NHibernate would try to update with the version from the form in the WHERE clause, but instead it uses the value it has just loaded in the session.
Other things I have read tell me that I should manuall compare values, and (for example) throw an exception if it loads a newer version. But I can't believe there is a better way.
It seems to me that NHibernate's concurrency controls are only useful in the same session, and therefore completely useless when it comes to stale form data on web based applications.
I want to be able to manually set the version based on what was in the form when the user originally loaded it. Is this possible? Or am I missing something here?
回答1:
The answer is here:
I was hoping NHibernate would try to update with the version from the form in the WHERE clause, but instead it uses the value it has just loaded in the session.
NHibernate will do, exactly/and only what we are explicitly asing for. Other words, How can NHibernate know about the hidden field in a form? web form? win form? ...
So, the trick is, that we have to set back the value, sent from the client, and bind it to the version. Just an example. I do prefer Timestamps, auto generated byte[]
values. So while my mapping and property looks like this:
<version name="Timestamp" generated="always" unsaved-value="null" type="BinaryBlob">
<column name="RowVersion" not-null="false" sql-type="timestamp"/>
</version>
and these are two C# object properties:
protected virtual byte[] Timestamp { get; set; }
public virtual string Version
{
get { return Timestamp.IsEmpty() ? null : Convert.ToBase64String(Timestamp); }
set { Timestamp = value.IsEmpty() ? null : Convert.FromBase64String(value); }
}
So, now, the client "hidden" field can contain the Version
property string value. Once send back to the server/data layer:
- the object to be edited is loaded by NHibernate into the
Session
. at this moment, itsVersion
/Timestamps
does reflect loaded state from DB. So: - we have to bind the value coming from the Client (hidden field) .. and replace the fresh/just loaded value from DB.
And now all is working, exactly as expected... even in multi-user environment (web)
回答2:
I know this is an old question, but I'll leave my usual approach here.
This is a know "problem" when using ORMs. Both NHibernate and Entity Framework suffer this "problem"", and it happens because the ORM is tracking internally the version value, not using the one returned by the property. Unlike EF, where you can copy the byte[]
values using Array.Copy
, in NHibernate I usually evict the entity from the session and then make the update, which indicates to NHibernate that you are updating an existing entity but he will start tracking using the Version you just assigned.
Here's a snippet of your code:
public void Edit(int id, string description, DateTime version)
{
using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
var record = session.Get<Record>(id);
record.Version = version;
record.Description = description;
session.Evict(record); // evict the object from the session
session.Update(record); // NHibernate will attach the object, and will use your version
tx.Commit();
}
}
If you use interfaces like I usually do in my model classes (example), you can easily create some extension method, making it harder for people to forget.
To my knowledge, I haven't found any issues with this approach, but let me know if you detect any.
来源:https://stackoverflow.com/questions/20000276/optimistic-concurrency-out-of-session-in-nhibernate