0

Update Date列が次のようにマッピングされている場合があります:

public abstract class AuditableEntityMapBase<T> : ClassMap<T>
{
    protected AuditableEntityMapBase()
    {
        ...
        OptimisticLock.Version();
        Version(x => (x as AuditableEntityBase).UpdateDT).Nullable();
        ...
    }
}

親 (Person エンティティ) と子 (Person の Cascade.None を持つ PersonTelephone エンティティ) が継承する AuditableEntityMapBase 基本クラスで、次のようにマップされます。

public class PersonTelephoneMap : AuditableEntityMapBase<PersonTelephone>
{
    public PersonTelephoneMap()
    {
        Table("pers_PersonTelephone");

        Id(x => x.Id, "PersonTelephoneId");

        References(x => x.Person, "PersonId")
            .Cascade.None();
        ...
    }
}

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

        ...
    }
}

次のテストのように子を保存してセッションをフラッシュすると、親で「オブジェクトは保存されていない一時インスタンスを参照します - フラッシュする前に一時インスタンスを保存します」:

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

Person エンティティをハイドレートし、次のように PersonTelephone マッピングで Cascade.SaveUpdate の代わりに Cascade.SaveUpdate を使用して nHibernate を更新することは実行できません。

 References(x => x.Person, "PersonId")
                .Cascade.SaveUpdate();

また、最初は機能した ReadOnly メソッドを使用してみました。

References(x => x.Person, "PersonId")
                    .Cascade.None.ReadOnly();

ただし、PersonId は Not Null 列であり、読み取り専用であるため nHibernate 挿入中に挿入されなかったため、PersonTelephone テーブルへの挿入で問題が発生しました。

悲観的ロックはユーザーの要件に合わず、OptimisticLock.All() でパフォーマンスが低下します。また、 Person エンティティ マッピングで .Cascade.None() を使用しようとしました。

機能する唯一の方法は、Person テーブルと PersonTelephone テーブルに一意の Update フィールドを設定することでした。このソリューションは私に匂いがします。次に、nHibernate エンティティ フィールドに一意の名前を付けようとしましたが、うまくいきませんでした。他の誰かがこれに出くわしましたか? エレガントなソリューションはありますか?

4

1 に答える 1

0

Person レコードの「所有権」が逆になっています。PersonTelephones は、Person への参照を「所有」します。ただし、カスケード関係はトップダウンです。PersonTelephones は、Person のときに保存されますが、その逆は保存されません。

したがって、得られるのは、新しい PersonTelephone を持つ新しい Person があり、PersonTelephone を保存していることです。Person は DB にまだ存在しておらず、NH は PersonTelephone が保存されたときに Person を保存しないように指示されているため、できることは文句を言うことだけです。

これを修正するには、PersonTelephone ではなく Person を保存します。これにより、Person が挿入または更新され、カスケード ダウンして、新しいものを含む PersonTelephones が挿入または更新されます。

于 2011-05-12T17:25:36.250 に答える