0

私のアプリケーションでは、デタッチされたオブジェクトを操作する必要があり、リポジトリ パターンを既に実装しています。問題は、次のコード サンプルに示されています。

var shops = ShopRepository.GetAll();           //Session.Clear() called inside, CacheMode = CacheMode.Ignore
var employees = EmployeeRepository.GetAll();   //same here

// Case 1: Session.Save() works, Session.Merge() irgnores the changes

employees.First().WorksAt.Name = "le new Shop in Town";
EmployeeRepository.Save(employees.First());

// Case 2: Session.Merge() works, Session.Save() throws NonUniqueObjectException

employees.First().WorksAt = employees.Last().WorksAt;
EmployeeRepository.Save(employees.First());

ショップを最初にロードする必要があるため、ユーザーは ComboBox (または同等のもの) を介して従業員の "WorksAt" プロパティを設定できます。

私の期待は、Session.Clear() を呼び出した後、nhibernate がすべてのインスタンス (および基になるグラフも) を「忘れる」ことです。そうではありません。私はまだ Update() - 操作で NonUniqueObjectException を取得し続けています。そのため、オブジェクト グラフに加えられたあらゆる種類の変更を適切に処理しますが、SaveOrUpdate を使用できません。

Session.Merge() は仕事の半分しか完了しません:

例外はスローされず、新しい参照は保存されましたが、shop-properties に加えられた変更は保存されません (マージは単純にキャッシュから「古い」従業員/shop オブジェクトをロードし、employee-properties を古いインスタンスにコピーするため)

私がすでに試したこと:

  • カスケードを「マージ」に設定
  • 構成で第 2 レベルのキャッシュを無効にする
  • Session.Lock(myObj, LockMode.None)
  • 自作の半マージ: Session.Merge() によって返されたオブジェクトの Repository.Save() のリフレクションを介してすべての参照をリセットします
  • Equals() および GetHashCode() メソッドをオーバーライドする

モデルには最大 50 個のエンティティが含まれているため、保存/更新時にセッション内で参照されているすべての bo-instance を (再) ロードすることはオプションではありません。現在、nHibernate 3.3.0.4 と FluentNHibernate 1.3 を使用しています。

両方のケースをカバーするコンパクトでエレガントなソリューションはありますか? NHibernate の内部 ID 追跡を無効にすると、問題が解決するのでしょうか?

マッピングは次のようになります。

public class Shop
{
public virtual Int Id  { get; set; };
public virtual string Name  { get; set; };

public virtual ISet<Employee> AllEmployees{ get; set; }

  public Shop()
  {
    AllEmployees = new HashedSet<Employee>();
  }
}

public class ShopMap: ClassMap<Shop>
{
  public ShopMap()
  {
    Id(x => x.Id);
    Map(x => x.Name);
  }

  HasMany(c => c.AllEmployees).Cascade.None().NotFound.Ignore();
}

public class Employee
{
public virtual Int Id  { get; set; };
public virtual string FirstName  { get; set; };
public virtual string SureName  { get; set; };

public virtual Shop WorksAt{ get; set; }

  public Employee()
  {
  }
}

public class EmployeeMap: ClassMap<Employee>
{
  public EmployeeMap()
  {
    Id(x => x.Id);
    Map(x => x.FirstName);
    Map(x => x.SureName);

    References(c => c.WorksAt).Cascade.SaveUpdate().Fetch.Join().NotFound.Ignore();
  } 
}

アップデート

リポジトリ基本クラス内の Save() メソッドのコードは次のとおりです。

public static void Save(T pSaveObj)
{
  using (ISession session = GetSession())
  {
    using (ITransaction trans = session.BeginTransaction())
    {          
      // session.Save(pSaveObj);
      session.Save(session.Merge(pSaveObj));                                         
      session.Flush();
      session.Clear();
    }
  }
}
4

1 に答える 1

0

まあ..あなたはNHibernateを完全に誤用しています。Clear と Flush がセッションで呼び出されることはほとんどありません。保存を実行するためだけにセッションを作成すると、第 1 レベルのキャッシュのメリットがなくなります。トランザクションは破棄時に暗黙的にロールバックされるため (コミットがなかった場合)、実際には何もしません。

私があなたの意図を正しく読んだ場合、次のようなコードが必要になります。

public static void Save(T pSaveObj)
{
  using (ISession session = GetSession())
  {
    using (ITransaction trans = session.BeginTransaction())
    {          
      session.SaveOrUpdate(pSaveObj);                                         
      trans.Commit()
    }
  }
}
  • pSaveObj がセッションにアタッチされていないと仮定します (アタッチされている場合は、session.Update を使用します)。
  • NH の使い方がまだ悪い
    • リポジトリ パターンを使用する強い理由がない場合は、それを忘れて生のセッション オブジェクトを操作すると、何千もの問題を回避できます。

PS SQLプロファイラーでNHが何をしているか見てください。良い学習方法だと思います。

于 2012-08-29T13:43:28.600 に答える