nhibernate nhibernate-mapping cascade

¿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?