Как изменить родителя ребенка в NHibernate, когда каскад удален-все-сирота? - PullRequest
13 голосов
/ 04 мая 2010

У меня есть две сущности в двунаправленном отношении один-ко-многим:

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>

A Storage может иметь много Boxes, но Box может принадлежать только одному Storage. Я сопоставил их так, чтобы каскад один-ко-многим был 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 из его коллекции и пометил его для удаления. Однако, когда я добавляю его ко второму Storage (в том же сеансе), он пытается снова сохранить ящик и выбрасывается ObjectDeletedException.

Мой вопрос: как мне заставить Box изменить его родителя Storage, не встречая этого исключения? Я знаю, что одним из возможных решений является изменение каскада на all, но тогда я теряю возможность автоматически удалять NHibernate Box, просто удаляя его из Storage и не связывая его повторно с другим. Или это единственный способ сделать это, и мне нужно вручную вызвать Session.Delete на коробке, чтобы удалить его?

1 Ответ

9 голосов
/ 11 апреля 2011

См. http://fabiomaulo.blogspot.com/2009/09/nhibernate-tree-re-parenting.html

По сути, это сводится к следующему ... Вам нужно определить собственный тип коллекции для NHibernate, который заново определяет, что значит быть сиротой.Поведение NHibernate по умолчанию состоит в том, чтобы сделать так, как вы обнаружили - считать ребенка сиротой, если он был удален из родительского.Вместо этого вам нужен NHibernate, чтобы проверить ребенка, чтобы увидеть, был ли он назначен новому родителю.По умолчанию NHibernate не делает этого, потому что для этого требуется дополнительная информация о сопоставлении «один ко многим» - ему нужно знать имя соответствующего свойства «многие к одному» у дочернего элемента.

Изменить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 список Box es, которые он считает сиротами, а затем фильтруем его до набора Box es, которые фактически являются сиротами.

...