2012-06-22 6 views
6

以下のクラスは、私の実際のシナリオをレガシーデータベースで最小限の方法で表します。私はそれに新しい列を追加することができますが、これは私ができることです.310 + 100のテーブルデータベースはNHibernateに移植されない他の多くのレガシーアプリケーションで使用されているので(コンポジットキーからの移行はオプションではありません) :これは私がと3人の子供と1人の子供を持つ親を挿入するために使用しているコードがある関係の適切な逆(流暢なnhibernate)設定に関係なく、無関係の更新ステートメントを出力するNHibernate

public class MapeamentoParent : ClassMap<Parent> 
{ 
    public MapeamentoParent() 
    { 
     Id(_ => _.Id, "PARENT_ID").GeneratedBy.Identity(); 
     HasMany(_ => _.Children) 
      .Inverse() 
      .AsSet() 
      .Cascade.All() 
      .KeyColumn("PARENT_ID"); 
    } 
} 
public class MapeamentoChild : ClassMap<Child> 
{ 
    public MapeamentoChild() 
    { 
     CompositeId() 
      .KeyReference(_ => _.Parent, "PARENT_ID") 
      .KeyProperty(_ => _.ChildId, "CHILD_ID"); 
     HasMany(_ => _.Items) 
      .AsSet() 
      .Cascade.All() 
      .KeyColumns.Add("PARENT_ID") 
      .KeyColumns.Add("CHILD_ID"); 
     Version(Reveal.Member<Child>("version")); 
    } 
} 
public class MapeamentoItem : ClassMap<Item> 
{ 
    public MapeamentoItem() 
    { 
     Id(_ => _.ItemId).GeneratedBy.Assigned(); 
     Version(Reveal.Member<Item>("version")); 
    } 
} 

public class Parent 
{ 
    public virtual long Id { get; protected set; } 
    ICollection<Child> children = new HashSet<Child>(); 
    public virtual IEnumerable<Child> Children { get { return children; } } 
    public virtual void AddChildren(params Child[] children) 
    { 
     foreach (var child in children) AddChild(child); 
    } 
    public virtual Child AddChild(Child child) 
    { 
     child.Parent = this; 
     children.Add(child); 
     return child; 
    } 
} 
public class Child 
{ 
    public virtual Parent Parent { get; set; } 
    public virtual int ChildId { get; set; } 
    ICollection<Item> items = new HashSet<Item>(); 
    public virtual ICollection<Item> Items { get { return items; } } 
    long version; 
    public override int GetHashCode() 
    { 
     return ChildId.GetHashCode()^(Parent != null ? Parent.Id.GetHashCode() : 0.GetHashCode()); 
    } 
    public override bool Equals(object obj) 
    { 
     var c = obj as Child; 
     if (ReferenceEquals(c, null)) 
      return false; 
     return ChildId == c.ChildId && Parent.Id == c.Parent.Id; 
    } 
} 
public class Item 
{ 
    public virtual long ItemId { get; set; } 
    long version; 
} 

これは、私が「既存の」データベースへのこれらのマッピングされてきた方法です品目:

 using (var tx = session.BeginTransaction()) 
     { 
      var parent = new Parent(); 
      var child = new Child() { ChildId = 1, }; 
      parent.AddChildren(
       child, 
       new Child() { ChildId = 2, }, 
       new Child() { ChildId = 3 }); 
      child.Items.Add(new Item() { ItemId = 1 }); 
      session.Save(parent); 
      tx.Commit(); 
     } 

これらは、前のコードのために生成されたSQL文である:

-- statement #1 
INSERT INTO [Parent] 
DEFAULT VALUES; 

select SCOPE_IDENTITY() 

-- statement #2 
INSERT INTO [Child] 
      (version, 
      PARENT_ID, 
      CHILD_ID) 
VALUES  (1 /* @p0_0 */, 
      1 /* @p1_0 */, 
      1 /* @p2_0 */) 

INSERT INTO [Child] 
      (version, 
      PARENT_ID, 
      CHILD_ID) 
VALUES  (1 /* @p0_1 */, 
      1 /* @p1_1 */, 
      2 /* @p2_1 */) 

INSERT INTO [Child] 
      (version, 
      PARENT_ID, 
      CHILD_ID) 
VALUES  (1 /* @p0_2 */, 
      1 /* @p1_2 */, 
      3 /* @p2_2 */) 


-- statement #3 
INSERT INTO [Item] 
      (version, 
      ItemId) 
VALUES  (1 /* @p0_0 */, 
      1 /* @p1_0 */) 

-- statement #4 
UPDATE [Child] 
SET version = 2 /* @p0 */ 
WHERE PARENT_ID = 1 /* @p1 */ 
     AND CHILD_ID = 1 /* @p2 */ 
     AND version = 1 /* @p3 */ 

-- statement #5 
UPDATE [Child] 
SET version = 2 /* @p0 */ 
WHERE PARENT_ID = 1 /* @p1 */ 
     AND CHILD_ID = 2 /* @p2 */ 
     AND version = 1 /* @p3 */ 

-- statement #6 
UPDATE [Child] 
SET version = 2 /* @p0 */ 
WHERE PARENT_ID = 1 /* @p1 */ 
     AND CHILD_ID = 3 /* @p2 */ 
     AND version = 1 /* @p3 */ 

-- statement #7 
UPDATE [Item] 
SET PARENT_ID = 1 /* @p0_0 */, 
     CHILD_ID = 1 /* @p1_0 */ 
WHERE ItemId = 1 /* @p2_0 */ 

文4、すべての情報が既に文でバッチ挿入にデータベースに送信されてから5及び6は、余分/外来である2

これは、親マッピングがHasMany(一対多)の関係でInverseプロパティを設定していない場合に予想される動作です。

我々はこのような子供の項目に1対多の関係を取り除く際に実際には、それはまだ見知らぬ人となっ:

は子供からコレクションを削除し、項目に子プロパティを追加します。

public class Child 
    { 
     public virtual Parent Parent { get; set; } 
     public virtual int ChildId { get; set; } 
     long version; 
     public override int GetHashCode() 
     { 
      return ChildId.GetHashCode()^(Parent != null ? Parent.Id.GetHashCode() : 0.GetHashCode()); 
     } 
     public override bool Equals(object obj) 
     { 
      var c = obj as Child; 
      if (ReferenceEquals(c, null)) 
       return false; 
      return ChildId == c.ChildId && Parent.Id == c.Parent.Id; 
     } 
    } 

    public class Item 
    { 
     public virtual Child Child { get; set; } 
     public virtual long ItemId { get; set; } 
     long version; 
    } 

変更項目からのhasManyを削除し、戻って子供の項目の複合キーの参照を追加するために子どもと項目のマッピング:

public class MapeamentoChild : ClassMap<Child> 
{ 
    public MapeamentoChild() 
    { 
     CompositeId() 
      .KeyReference(_ => _.Parent, "PARENT_ID") 
      .KeyProperty(_ => _.ChildId, "CHILD_ID"); 
     Version(Reveal.Member<Child>("version")); 
    } 
} 
public class MapeamentoItem : ClassMap<Item> 
{ 
    public MapeamentoItem() 
    { 
     Id(_ => _.ItemId).GeneratedBy.Assigned(); 
     References(_ => _.Child).Columns("PARENT_ID", "CHILD_ID"); 
     Version(Reveal.Member<Item>("version")); 
    } 
} 

変更C以下の(今私たちは、明示的にアイテムを保存呼び出す必要が予告)への頌歌:

 using (var tx = session.BeginTransaction()) 
     { 
      var parent = new Parent(); 
      var child = new Child() { ChildId = 1, }; 
      parent.AddChildren(
       child, 
       new Child() { ChildId = 2, }, 
       new Child() { ChildId = 3 }); 
      var item = new Item() { ItemId = 1, Child = child }; 
      session.Save(parent); 
      session.Save(item); 
      tx.Commit(); 
     } 

結果のSQL文は以下のとおりです。

-- statement #1 
INSERT INTO [Parent] 
DEFAULT VALUES; 

select SCOPE_IDENTITY() 

-- statement #2 
INSERT INTO [Child] 
      (version, 
      PARENT_ID, 
      CHILD_ID) 
VALUES  (1 /* @p0_0 */, 
      1 /* @p1_0 */, 
      1 /* @p2_0 */) 

INSERT INTO [Child] 
      (version, 
      PARENT_ID, 
      CHILD_ID) 
VALUES  (1 /* @p0_1 */, 
      1 /* @p1_1 */, 
      2 /* @p2_1 */) 

INSERT INTO [Child] 
      (version, 
      PARENT_ID, 
      CHILD_ID) 
VALUES  (1 /* @p0_2 */, 
      1 /* @p1_2 */, 
      3 /* @p2_2 */) 

-- statement #3 
INSERT INTO [Item] 
      (version, 
      PARENT_ID, 
      CHILD_ID, 
      ItemId) 
VALUES  (1 /* @p0_0 */, 
      1 /* @p1_0 */, 
      1 /* @p2_0 */, 
      1 /* @p3_0 */) 

あなたは無関係な/余分UPDATE文はありません見ることができるように、私はItemにChildへのリンクを持たせたくないので、オブジェクトモデルは自然にモデル化されていません。そして、私はChild内のItemsのコレクションが必要です。

ChildからHasManyリレーションシップを削除することを除いて、これらの不要/不要なUPDATEステートメントを防ぐ方法はありません。それは、子がすでに「倒立した」1対多の関係(それ自体を保存する責任がある)からの「多数」であるため、別のものから「1」の部分であるときに逆設定を尊重しないように見える多対の逆の関係...

これは私をナットにしています。私はこれらの余分なUPDATE文をよく考えて説明することなく受け入れることはできません:-)ここで何が起こっているのか知っていますか?

答えて

8

夜間これに苦労して、ここでも答えが見つからない場合は、スタックオーバーフロー:-)私は解決策を思いつきました...私は、 Parentのコレクションの変更と見なされ、Entityのバージョンが変更された子オブジェクト。私の推測では、これを読んだ後固化し始めた:

(13)楽観的ロック(オプション - デフォルトはtrue):種 所有エンティティのバージョンの増分で収集結果の状態に 変化していること。 (多くの団体への1つは、それがこの設定を無効にすることが多い 合理的であるため。) (ここで見つける:http://nhibernate.info/doc/nh/en/index.html#collections)を

私は、単純に次のように楽観的ロックを使用しないように親にマッピングを変更:

public MapeamentoParent() 
    { 
     Id(_ => _.Id, "PARENT_ID").GeneratedBy.Identity(); 
     HasMany<Child>(_ => _.Children) 
      .Inverse() 
      .AsSet() 
      .Cascade.All() 
      .Not.OptimisticLock() 
      .KeyColumn("PARENT_ID"); 
    } 

これは機能しませんでした。しかし、私は余分なアップデートで興味深いの何かを気づいた:

-- statement #1 
UPDATE [Child] 
SET version = 2 /* @p0 */ 
WHERE PARENT_ID = 1 /* @p1 */ 
     AND CHILD_ID = 1 /* @p2 */ 
     AND version = 1 /* @p3 */ 

-- statement #2 
UPDATE [Child] 
SET version = 2 /* @p0 */ 
WHERE PARENT_ID = 1 /* @p1 */ 
     AND CHILD_ID = 2 /* @p2 */ 
     AND version = 1 /* @p3 */ 

-- statement #3 
UPDATE [Child] 
SET version = 2 /* @p0 */ 
WHERE PARENT_ID = 1 /* @p1 */ 
     AND CHILD_ID = 3 /* @p2 */ 
     AND version = 1 /* @p3 */ 

私はバージョンが2に更新されていたことに気づくことがラッキーでした! (デグレス:私はDateTimeのバージョンフィールドを使用していましたが、無限の精度ではないので、意図的にそれを整数バージョンに変更しました。これはバージョニングの問題だと思ったので、ミリ秒未満で発生する増分を見逃すことはありません。これは、精度や不足のためDateTimeバージョンでは追跡できません。だから、私はもう一度絶望する前に、親のHasManyを元に戻して(可能な解決法を切り離そうとした)、代わりにNot.OptimisticLock()をChildのマップに追加しました。そのバージョンは更新されている)子供だった:

public class MapeamentoChild : ClassMap<Child> 
    { 
     public MapeamentoChild() 
     { 
      CompositeId() 
       .KeyReference(_ => _.Parent, "PARENT_ID") 
       .KeyProperty(_ => _.ChildId, "CHILD_ID"); 
      HasMany(_ => _.Items) 
       .AsSet() 
       .Cascade.All() 
       .Not.OptimisticLock() 
       .KeyColumns.Add("PARENT_ID") 
       .KeyColumns.Add("CHILD_ID"); 
      Version(Reveal.Member<Child>("version")); 
     } 
    } 

そして、それは完全に次のSQL文を発行していない働いた:!全く無関係のUPDATE文が

-- statement #1 
INSERT INTO [Parent] 
DEFAULT VALUES; 

select SCOPE_IDENTITY() 

-- statement #2 
INSERT INTO [Child] 
      (version, 
      PARENT_ID, 
      CHILD_ID) 
VALUES  (1 /* @p0_0 */, 
      1 /* @p1_0 */, 
      1 /* @p2_0 */) 

INSERT INTO [Child] 
      (version, 
      PARENT_ID, 
      CHILD_ID) 
VALUES  (1 /* @p0_1 */, 
      1 /* @p1_1 */, 
      2 /* @p2_1 */) 

INSERT INTO [Child] 
      (version, 
      PARENT_ID, 
      CHILD_ID) 
VALUES  (1 /* @p0_2 */, 
      1 /* @p1_2 */, 
      3 /* @p2_2 */) 

-- statement #3 
INSERT INTO [Item] 
      (version, 
      ItemId) 
VALUES  (1 /* @p0_0 */, 
      1 /* @p1_0 */) 

-- statement #4 
UPDATE [Item] 
SET PARENT_ID = 1 /* @p0_0 */, 
     CHILD_ID = 1 /* @p1_0 */ 
WHERE ItemId = 1 /* @p2_0 */ 

を! :-)

問題は、以前にはうまくいかなかった理由を説明できないということです。何らかの理由で、Childが別のエンティティと1対多の関係を持つ場合、無関係のSQL文が実行されます。 Childオブジェクトのこれらの一対多コレクションでは、楽観的ロックをfalseに設定する必要があります。私は、ChildクラスがItemに1対多の関係を持っていたからといって、すべてのChildオブジェクトのバージョンが同時に変更された理由はわかりません。そのうちの1つだけが変更された場合、すべてのChildオブジェクトのバージョン番号を増やすことは意味がありません!

私の最大の問題は、子オブジェクトにアイテムを追加しなくても、親のコレクション上のすべての子オブジェクトが更新された理由です。 ChildがItemとHasManyの関係を持っているという事実だけで起こっていた...(追加の更新を "取得"するために子供にアイテムを追加する必要はない)。 NHibernateはここで間違ったことを考え出しているようですが、NHibernateを完全に理解していないので、確かに言いませんし、どこに問題があるのか​​を正確に特定することもできません。それは本当にNHibernate grokking力の私の完全な欠如本当の犯人かもしれません! :-)

ドキュメントが示唆しているように、オプティミスティックロックをfalseに設定すると、ドキュメンテーションの示唆どおりにその問題が解決されたと思います。

+0

parent.AddChild(...)とchild.Parent = parentの間に違いがあることがわかりました。後で_not_を実行すると、不要なバージョンが更新されます(セッション中に子をParent.Childrenコレクションに追加することはありませんが、後のセッションで親がロードされている場合はそこにあります)。) – BatteryBackupUnit

+0

@BatteryBackupUnitそれは本当ですが、ORMとしてコレクションに追加するときや親を直接設定したときに違うように誤解してはいけません。データベースからロードする前に、すぐにコレクションに置く必要があることがあります。そのような場合、これらの無関係のクエリはまだそこに存在します。しかし、私たちが望むのは、子どもを親に "結びつける"ことだけが望めば、私たちが "防ぐ"ことができることを知ってうれしいです...親のコレクションに追加するのではなく、child.Parentを使用してください。 :-) – Loudenvier

+1

私は全く同意します。私はFluentNHibernate 'IHasManyConvention'に' .Not.OptimisticLock() 'を追加して、これがデフォルト動作であることを確認しました。 – BatteryBackupUnit