¿Cómo puedo cambiar el padre de un niño en NHibernate cuando cascade es delete-all-huphan?
nhibernate-mapping (1)
Ver http://fabiomaulo.blogspot.com/2009/09/nhibernate-tree-re-parenting.html
Básicamente, se reduce a esto ... Necesita definir un tipo de colección personalizada para NHibernate que redefine lo que significa ser huérfano. El comportamiento predeterminado de NHibernate es hacer exactamente lo que descubrió: considerar que un niño queda huérfano si se lo quitó del padre. En su lugar, necesita NHibernate para probar al niño para ver si ha sido asignado a un nuevo padre. NHibernate no hace esto de forma predeterminada porque requeriría información adicional sobre la asignación de uno a muchos: necesitaría conocer el nombre de la propiedad de muchos a uno correspondiente en el elemento secundario.
Cambie su asignación de Storage
para que se vea así:
<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>
Defina un nuevo tipo denominado StorageBoxBag
(nota: este código está escrito contra NHibernate 2.1). Si está utilizando NH3, puede que tenga que ajustarlo un poco):
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;
}
}
... y un nuevo tipo llamado 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;
}
}
El método GetOrphans
es donde ocurre la magia. Pedimos a NHibernate la lista de Box
que cree que son huérfanos, y luego la filtramos solo al conjunto de Box
que en realidad son huérfanos.
Tengo dos entidades en una relación bidireccional de uno a muchos:
public class Storage
{
public IList<Box> Boxes { get; set; }
}
public class Box
{
public Storage CurrentStorage { get; set; }
}
Y el mapeo:
<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>
Un Storage
puede tener muchas Boxes
, pero una Box
solo puede pertenecer a un Storage
. Los he mapeado para que el uno-a-muchos tenga una cascada de all-delete-orphan
.
Mi problema surge cuando trato de cambiar el Storage
una Caja. Asumiendo que ya ejecuté este código:
var storage1 = new Storage();
var storage2 = new Storage();
storage1.Boxes.Add(new Box());
Session.Create(storage1);
Session.Create(storage2);
El siguiente código me dará una excepción:
// 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
Obtengo la siguiente excepción:
NHibernate.ObjectDeletedException: el objeto eliminado se volvería a guardar por cascada (eliminar el objeto eliminado de las asociaciones)
Esta excepción se produce porque tengo el conjunto de cascadas para all-delete-orphan
. El primer Storage
detectó que eliminé el Box
de su colección y lo marqué para su eliminación. Sin embargo, cuando lo agregué al segundo Storage
(en la misma sesión), intenta guardar el cuadro nuevamente y se lanza ObjectDeletedException
.
Mi pregunta es, ¿cómo consigo que el Box
cambie su Storage
principal sin encontrar esta excepción? Sé que una solución posible es cambiar la cascada a all
, pero luego pierdo la capacidad de que NHibernate elimine automáticamente un Box
simplemente quitándolo de un Storage
y no reasociándolo con otro. ¿O es esta la única forma de hacerlo y tengo que llamar manualmente a Session.Delete
en el cuadro para eliminarlo?