I have the case where Update Date column mapped with:
public abstract class AuditableEntityMapBase<T> : ClassMap<T>
{
protected AuditableEntityMapBase()
{
...
OptimisticLock.Version();
Version(x => (x as AuditableEntityBase).UpdateDT).Nullable();
...
}
}
on a AuditableEntityMapBase base class that Parent (Person entity) and Child (PersonTelephone entity with Cascade.None for Person) inherit mapped as follows:
public class PersonTelephoneMap : AuditableEntityMapBase<PersonTelephone>
{
public PersonTelephoneMap()
{
Table("pers_PersonTelephone");
Id(x => x.Id, "PersonTelephoneId");
References(x => x.Person, "PersonId")
.Cascade.None();
...
}
}
and
public class PersonMap : AuditableEntityMapBase<Person>
{
public PersonMap()
{
Table("pers_Person");
Id(x => x.Id, "PersonId"); //.Unique().GeneratedBy.Native();
...开发者_开发百科
HasMany(x => x.Phones)
.KeyColumn("PersonId")
.Inverse()
.Cascade.All();
...
}
}
Saving Child and flushing session as in the following test causes "object references an unsaved transient instance -save the transient instance before flushing" on Parent:
/// <summary>
/// Tests nHibernate for concurrency (dirty read)
/// 1. Telephone1 and Telephone2 entities are loaded in separate sessions
/// 2. Telephone1 is updated - Telephone2 now has a dirty read
/// 3. Update Telephone2 and expect NHibernate.StaleObjectStateException error
/// </summary>
[Test]
[ExpectedException("NHibernate.StaleObjectStateException")] //Assert
public void CanVersionConcurrencyPersonTelephone()
{
//Arrange
const string telNo1 = "911";
const string telNo2 = "999";
Person person2 = null;
PersonTelephone personTelephone2 = null;
var person = CreatePerson(); //Create a new person entity
var personManager = new PersonManager();
//Act
//var person1 = personManager.Read(person.Id);
var personTelephone1 = person.Phones[0];
SessionContext.Current.AttachEntity(personTelephone1);
SessionContext.Flush();
using (SessionContext.Open())
{
person2 = personManager.Read(person.Id);
personTelephone2 = person2.Phones[0];
SessionContext.Flush();
}
System.Threading.Thread.Sleep(2000); //Arrange for a dirty read by user delay
using (SessionContext.Open())
{
personTelephone1.Number = telNo1;
personManager.UpdateTelephone(personTelephone1); //simulate dirty read for personTelephone2
SessionContext.Flush();
}
using (SessionContext.Open())
{
personTelephone2.Number = telNo2;
personManager.UpdateTelephone(personTelephone2); //expect NHibernate.StaleObjectStateException
SessionContext.Flush();
}
}
It is not viable for me to hydrate the Person entity and have nHibernate update using Cascade.SaveUpdate instead of Cascade.SaveUpdate on the PersonTelephone mapping as follows:
References(x => x.Person, "PersonId")
.Cascade.SaveUpdate();
I also tried using the ReadOnly method, which worked at first:
References(x => x.Person, "PersonId")
.Cascade.None.ReadOnly();
However, that caused an issue with me inserting to the PersonTelephone table because PersonId is a Not Null column and was not injected during the nHibernate insert becasue it is read-only.
Pessimistic locking doesn't fit my user requirements and there is a performance hit with OptimisticLock.All(). I also tried to use .Cascade.None() on the Person entity mapping.
The only thing that worked was to have a unique Update field on the Person and PersonTelephone tables. This solution smells to me. I then tried to give the nHibernate entity fields unique names but thid did not work. Has any one else come across this? Is there an elegant solution?
You have the "ownership" of the Person record reversed; PersonTelephones "own" their reference to Person. However, the cascading relationship is top-down; PersonTelephones are saved when their Person is, but not the other way around.
So, what you're getting is that you have a new Person with a new PersonTelephone, and you're saving the PersonTelephone. The Person doesn't yet exist in the DB, and NH is told not to save the Person when the PersonTelephone is saved, so the only thing it can do is complain.
To fix this, save the Person, not the PersonTelephone. That will insert or update the Person, and then cascade down to insert or update the PersonTelephones including your new one.
精彩评论