开发者

Fluent nHIbernate column mapped with Version on base class - saving Child causes "object references an unsaved transient instance..." error on Parent

开发者 https://www.devze.com 2023-03-05 16:56 出处:网络
I have the case where Update Date column mapped with: public abstract class AuditableEntityMapBase<T> : ClassMap<T>

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消