2010-12-03 9 views
8

PreUpdateイベントにフックするNhibernateの監査追跡を作成しようとしています。 AuditLogEntryDetails(変更された個々のプロパティ)のリストを含むAuditLogEntryクラス(when、whoなど)があります。監査対象のエンティティからAuditLogEntryクラスを分離すると、エラーなしでコードが実行されます。私はエンティティにAuditLogEntryのリストを追加した場合しかし、その後、監査されている私のコードはNHibernate:EventListener "PreUpdateEvent"中にコレクションを更新する

コレクションは[DomainObjects.AuditTracking.AuditLogEntry.Details] フラッシュ()

アサーションの失敗によって処理されなかったスロー私は変更されたリストをイベントリスナーの中に保存しようとします。これは、監査対象のアイテムにすでにリスト内に1つ(またはそれ以上)のAuditLogEntryインスタンスがある場合にのみ発生します。エントリがない場合、新しいリストが作成され、監査対象のエンティティに追加されます。これは問題ありません。

上記の問題を特定することで、既存のリストを読み込んでAuditLogEntryの新しいインスタンスを追加する(遅延している)ように見えます。しかし私はこれ以上進歩することができませんでした。リストのマッピングに 'Lazy = "False"を追加することは役に立ちません。私はHibernate Cookbookとblog postの両方から概念を借りて、NHibernateを使用していた初期の段階にあります。私のコードはこれに非常に似ていますが、監査履歴をリストで監査されている項目に追加しようとします(更新イベントをポストするのではなく、事前にそれを行う必要があると思います)。

問題の実体インタフェース/クラスのスナップショットは、次のとおりです。私が持っているマッピングのために

public class AuditLogEntry : Entity 
{ 
    public virtual AuditEntryTypeEnum AuditEntryType { get; set; } 
    public virtual string EntityFullName { get; set; } 
    public virtual string EntityShortName { get; set; } 
    public virtual string Username { get; set; } 
    public virtual DateTime When { get; set; } 
    public virtual IList<AuditLogEntryDetail> Details { get; set; } 
} 

public interface IAuditTrackedEntity 
{ 
    Guid Id { get; } 
    IList<AuditLogEntry> ChangeHistory { get; set; } 
} 

public class AuditTrackedEntity : StampedEntity, IAuditTrackedEntity 
{ 
    public virtual IList<AuditLogEntry> ChangeHistory { get; set; } 
} 

public class LookupValue : AuditTrackedEntity 
{ 
    public virtual string Description { get; set; } 
} 

AuditTrackedEntry.hbm.xml:

<?xml version="1.0" encoding="utf-8" ?> 
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DomainObjects" namespace="DomainObjects.AuditTracking"> 
    <class name="AuditLogEntry"> 
    <id name="Id"> 
     <generator class="guid.comb" /> 
    </id> 
    <version name="Version" /> 
    <property name="AuditEntryType"/> 
    <property name="EntityFullName"/> 
    <property name="EntityShortName"/> 
    <property name="Username"/> 
    <property name="When" column="`When`"/> 
    <list name ="Details" cascade="all"> 
     <key column="AuditLogEntryId"/> 
     <list-index column="DetailsIndex" base="1"/> 
     <one-to-many class="AuditLogEntryDetail"/> 
    </list> 
    </class> 
</hibernate-mapping> 

lookupvalue.hbm .xml:

<?xml version="1.0" encoding="utf-8" ?> 
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DomainObjects" namespace="DomainObjects"> 
    <class name="LookupValue"> 
    <id name="Id"> 
     <generator class="guid.comb" /> 
    </id> 
    <discriminator type="string"> 
     <column name="LookupValueType" unique-key="UQ_TypeName" not-null="true" /> 
    </discriminator> 
    <version name="Version" /> 
    <property name="Description" unique-key="UQ_TypeName" not-null="true" /> 
    <property name="CreatedBy" /> 
    <property name="WhenCreated" /> 
    <property name="ChangedBy" /> 
    <property name="WhenChanged" /> 
    <list name ="ChangeHistory"> 
     <key column="EntityId"/> 
     <list-index column="ChangeIndex" base="1"/> 
     <one-to-many class="DomainObjects.AuditTracking.AuditLogEntry"/> 
    </list> 
    </class> 
</hibernate-mapping> 

はのEventListener更新前イベントハンドラは以下のコードを呼び出します。問題が発生 ラインは、コードブロック先に述べたように

public void TrackPreUpdate(IAuditTrackedEntity entity, object[] oldState, object[] state, IEntityPersister persister, IEventSource eventSource) 
    { 
     if (entity == null || entity is AuditLogEntry) 
      return; 

     var entityFullName = entity.GetType().FullName; 
     if (oldState == null) 
     { 
      throw new ArgumentNullException("No old state available for entity type '" + entityFullName + 
              "'. Make sure you're loading it into Session before modifying and saving it."); 
     } 

     var dirtyFieldIndexes = persister.FindDirty(state, oldState, entity, eventSource); 
     var session = eventSource.GetSession(EntityMode.Poco); 

     AuditLogEntry auditLogEntry = null; 
     foreach (var dirtyFieldIndex in dirtyFieldIndexes) 
     { 
      if (IsIngoredProperty(persister, dirtyFieldIndex)) 
       continue; 

      var oldValue = GetStringValueFromStateArray(oldState, dirtyFieldIndex); 
      var newValue = GetStringValueFromStateArray(state, dirtyFieldIndex); 

      if (oldValue == newValue) 
      { 
       continue; 
      } 
      if (auditLogEntry == null) 
      { 
       auditLogEntry = new AuditLogEntry 
            { 
             AuditEntryType = AuditEntryTypeEnum.Update, 
             EntityShortName = entity.GetType().Name, 
             EntityFullName = entityFullName, 
             Username = Environment.UserName, 
             //EntityId = entity.Id, 
             When = DateTime.Now, 
             Details = new List<AuditLogEntryDetail>() 
            }; 


       //********************** 
       // The next three lines cause a problem when included, 
       // collection [] was not processed by flush() 
       //********************** 
       if (entity.ChangeHistory == null) 
        entity.ChangeHistory = new List<AuditLogEntry>(); 
       entity.ChangeHistory.Add(auditLogEntry); 

       session.Save(auditLogEntry);  
      } 

      var detail = new AuditLogEntryDetail 
          { 
           //AuditLogEntryId = auditLogEntry.Id, 
           PropertyName = persister.PropertyNames[dirtyFieldIndex], 
           OldValue = oldValue, 
           NewValue = newValue 
          }; 
      session.Save(detail); 
      auditLogEntry.Details.Add(detail); 

     } 

     session.Flush(); 
    } 

の終わり近くにコメントしている、この構成では、私は「アサーション失敗を取得しますコレクション[]はflush()によって処理されませんでした。上記の3行を削除し、lookupcode.hmb.xmlのリストマッピングをすべて削除すると、監査対象のエンティティに自身の監査対象項目への参照が含まれていることを除いて、すべてが正常に機能します。

答えて

4

私たちはまったく同じ問題を抱えていましたが、まったく同じ例外ですが、状況は異なります。解決策が見つかりません...

監査ログには、IPreUpdateEventListenerOnPreUpdateメソッドを実装したNHイベントリスナーがあります。単純なプロパティの更新では問題ありませんが、ダーティーチェックはうまくいきますが、遅延コレクションには問題があります。イベントリスナOnPreUpdateメソッドで、遅延コレクションを持ちオブジェクトフィールドにアクセスしているオブジェクトを更新すると、上記と同じ例外がスローされます。 lazyがfalseに設定されていると、問題が解消されます。

したがって、レイジーコレクションには問題があります(保存前にコレクションの初期化の影響はありません)。私たちの問題は、新しいコレクションアイテムの作成に関連していません。既存のオブジェクトを読み取るだけで、イベントリスナーからアクセスするフィールドだけが問題を引き起こします。

あなたのケースでは、おそらくlazyがfalseに設定されていると、関連付けのためだけに問題が解決する可能性がありますが、おそらくあなたは本当にコレクションを怠惰にしたいと思うかもしれません。問題が解決した場合はIInterceptorを代わりに使用する必要があります。

2

あなたの問題を発見しました。この行は実際問題を引き起こしています。

Details = new List<AuditLogEntryDetail>() 

EntityPersisterコレクションを保持されませんので、あなたが保存する前に、あなたは空のコレクションを初期化することはできませんが、それはコレクションが処理されていないことをエラーになります。

また、nHibernateがイベントリスナーを呼び出すと、カスケードは機能しません(これが仕様であるかどうかはわかりません)。そのため、後でコレクションに詳細項目を追加しても、親ではなく詳細の保存だけが呼び出されているので、変更は伝播されません。アイテムがこの順番で完了するように、再ファクタリングをお勧めします...

詳細は、その後、

エンティティ、その後更新し、[保存、

AuditLogEntryを保存します。

+0

うまくいけば、私は明日のコードを再検討し、あなたのソリューションを試すチャンスがあるでしょうアップデートのおかげで、。私はあなたに私が見つけたものを知らせます。 –

+0

あなたの助けていただきありがとうございますが、私はまだ問題があると思っています。異なる構成では、異なるエラーを得ることができますが、すべて同じエラーになります。私はそれがコレクションと思われることに同意し、コードがエンティティへのコレクションの追加を反映するために新しい状態を更新していなかったためだと思っていましたが、それを追加すると古い状態または更新中のエラー2つのセッションでオブジェクト。 –

2

EventListenerを使用しているときとまったく同じ問題がありました。私はコレクションを列挙することを含む変更を検出するためにプロパティを1つずつループしていました。しかし、NHibernateUtil.IsInitialized(collection)を使用してコレクションのチェックを追加すると、問題は消えました。私はassertionfailure例外を捕まえたり無視したりすることは知られていませんでした。

+0

NHibernateUtil.IsInitialized(コレクション)の使い方をより詳しく説明できますか? – Giedrius

関連する問題