14

私は双方向の1対多の関係にある2つのエンティティを持っています:

public class Storage
{
    public IList<Box> Boxes { get; set; }
}

public class Box
{
    public Storage CurrentStorage { get; set; }
}

そしてマッピング:

<class name="Storage">
    <bag name="Boxes" cascade="all-delete-orphan" inverse="true">
        <key column="Storage_Id" />
        <one-to-many class="Box" />
    </bag>
</class>

<class name="Box">
    <many-to-one name="CurrentStorage" column="Storage_Id" />
</class>

AStorageは多数持つことができますBoxesが、aBoxは1つにしか属することができませんStorage。1対多がのカスケードを持つようにそれらをマッピングしましたall-delete-orphan

ボックスを変更しようとすると問題が発生しStorageます。私がすでにこのコードを実行したと仮定します:

var storage1 = new Storage();
var storage2 = new Storage();
storage1.Boxes.Add(new Box());

Session.Create(storage1);
Session.Create(storage2);

次のコードは私に例外を与えます:

// get the first and only box in the DB
var existingBox = Database.GetBox().First();

// remove the box from storage1
existingBox.CurrentStorage.Boxes.Remove(existingBox);

// add the box to storage2 after it's been removed from storage1
var storage2 = Database.GetStorage().Second();
storage2.Boxes.Add(existingBox);

Session.Flush(); // commit changes to DB

次の例外が発生します。

NHibernate.ObjectDeletedException:削除されたオブジェクトはカスケードによって再保存されます(削除されたオブジェクトを関連付けから削除します)

この例外は、カスケードがに設定されているために発生しall-delete-orphanます。最初に、コレクションからStorageを削除したことを検出しBox、削除のマークを付けました。ただし、(同じセッションで)2番目に追加するとStorage、ボックスを再度保存しようとし、ObjectDeletedExceptionがスローされます。

私の質問は、この例外が発生することなくBox、親を変更するにはどうすればよいですか?Storage考えられる解決策の1つは、カスケードを単にに変更することですallが、NHibernateでを削除し、別のカスケードに再度関連付けないBoxようにするだけで、自動的に削除することができなくなります。Storageそれとも、これがそれを行う唯一の方法であり、それSession.Deleteを削除するためにボックスを手動で呼び出す必要がありますか?

4

1 に答える 1

9

http://fabiomaulo.blogspot.com/2009/09/nhibernate-tree-re-parenting.htmlを参照してください

基本的に、これに要約されます...孤立していることの意味を再定義するNHibernateのカスタムコレクションタイプを定義する必要があります。NHibernateのデフォルトの動作は、あなたが発見したとおりに実行することです。つまり、子が親から削除された場合、子が孤立していると見なします。代わりに、NHibernateを使用して子をテストし、新しい親に割り当てられているかどうかを確認する必要があります。NHibernateは、1対多のマッピングに関する追加情報を必要とするため、デフォルトではこれを行いません。子の対応する多対1のプロパティの名前を知る必要があります。

Storageマッピングを次のように変更します。

<class name="Storage">
    <bag name="Boxes" cascade="all-delete-orphan" inverse="true" collection-type="StorageBoxBag">
        <key column="Storage_Id" />
        <one-to-many class="Box" />
    </bag>
</class>

名前の付いた新しいタイプを定義しStorageBoxBagます(注-このコードはNHibernate 2.1に対して作成されています-NH3を使用している場合は、これを少し調整する必要があります):

public class StorageBoxBag : IUserCollectionType
{
    public object Instantiate(int anticipatedSize)
    {
        return new List<Box>();
    }

    public IPersistentCollection Instantiate(ISessionImplementor session, ICollectionPersister persister)
    {
        return new PersistentStorageBoxBag(session);
    }

    public IPersistentCollection Wrap(ISessionImplementor session, object collection)
    {
        return new PersistentStorageBoxBag(session, (IList<Box>)collection);
    }

    public IEnumerable GetElements(object collection)
    {
        return (IEnumerable)collection;
    }

    public bool Contains(object collection, object entity)
    {
        return ((IList<Box>)collection).Contains((Box)entity);
    }

    public object IndexOf(object collection, object entity)
    {
        return ((IList<Box>) collection).IndexOf((Box) entity);
    }

    public object ReplaceElements(object original, object target, ICollectionPersister persister, object owner, IDictionary copyCache, ISessionImplementor session)
    {
        var result = (IList<Box>)target;
        result.Clear();

        foreach (var box in (IEnumerable)original)
            result.Add((Box)box);

        return result;
    }
}

...そしてPersistentStorageBoxBag:という名前の新しいタイプ

public class PersistentStorageBoxBag: PersistentGenericBag<Box>
{
    public PersistentStorageBoxBag(ISessionImplementor session)
        : base(session)
    {
    }

    public PersistentStorageBoxBag(ISessionImplementor session, ICollection<Box> original)
        : base(session, original)
    {
    }

    public override ICollection GetOrphans(object snapshot, string entityName)
    {
        var orphans = base.GetOrphans(snapshot, entityName)
            .Cast<Box>()
            .Where(b => ReferenceEquals(null, b.CurrentStorage))
            .ToArray();

        return orphans;
    }
}

方法はGetOrphans魔法が起こるところです。NHibernateに孤立していると思われるesのリストを要求し、それBox実際に孤立しているesのセットのみにフィルターします。Box

于 2011-04-11T15:32:04.730 に答える