次のクラスは、最小限の方法で、レガシー データベースを使用した実際のシナリオを表しています。それに新しい列を追加することはできますが、300 以上のテーブル データベースが NHibernate に移植されない他の多くのレガシー アプリケーションで使用されているため、できることはこれだけです (したがって、複合キーからの移行はオプションではありません)。 :
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;
}
これは、これらを「既存の」データベースにマップした方法です。
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"));
}
}
これは、アイテムを持つ 3 つの子と 1 つの子を持つ親を挿入するために使用しているコードです。
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 のバッチ挿入でデータベースに既に送信されているため、余分です。
Parent マッピングが HasMany (1 対多) 関係で Inverse プロパティを設定していない場合、これは予想される動作です。
実際、次のように Child から Item への 1 対多の関係を取り除くと、さらに奇妙になります。
Child からコレクションを削除し、Child プロパティを Item に追加します。
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;
}
Child と Item のマッピングを変更して、Item から HasMany を削除し、Item の複合キーの References を Child に戻します。
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"));
}
}
コードを次のように変更します (save Item を明示的に呼び出す必要があることに注意してください)。
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 ステートメントを防ぐ方法が見つかりません。Child はすでに「逆」の 1 対多の関係からの「多」であるため (それ自体を保存する責任があります)、別の「1」の部分である場合、逆の設定を尊重しません。 -対多逆関係...
これは私を夢中にさせています。よく考えられた説明がなければ、これらの追加の UPDATE ステートメントを受け入れることはできません :-) この辺りで何が起こっているか知っている人はいますか?