simulo su situación problema, el niño con clave principal nulo al inserto que después se actualiza más tarde con la clave principal derecha.
BEGIN; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
select nextval ('person_person_id_seq')
select nextval ('person_person_id_seq')
select nextval ('phone_number_phone_number_id_seq')
select nextval ('phone_number_phone_number_id_seq')
select nextval ('phone_number_phone_number_id_seq')
select nextval ('phone_number_phone_number_id_seq')
INSERT INTO person (lastname, firstname, person_id) VALUES (((E'McCartney')::text), ((E'Paul')::text), ((109)::int4))
INSERT INTO person (lastname, firstname, person_id) VALUES (((E'Lennon')::text), ((E'John')::text), ((110)::int4))
INSERT INTO phone_number (the_phone_number, person_id, phone_number_id) VALUES (((E'9')::text), ((NULL)::int4), ((306)::int4))
INSERT INTO phone_number (the_phone_number, person_id, phone_number_id) VALUES (((E'8')::text), ((NULL)::int4), ((307)::int4))
INSERT INTO phone_number (the_phone_number, person_id, phone_number_id) VALUES (((E'6')::text), ((NULL)::int4), ((308)::int4))
INSERT INTO phone_number (the_phone_number, person_id, phone_number_id) VALUES (((E'1')::text), ((109)::int4), ((309)::int4))
UPDATE phone_number SET person_id = ((110)::int4) WHERE phone_number_id = ((306)::int4)
UPDATE phone_number SET person_id = ((110)::int4) WHERE phone_number_id = ((307)::int4)
UPDATE phone_number SET person_id = ((110)::int4) WHERE phone_number_id = ((308)::int4)
UPDATE phone_number SET person_id = ((110)::int4) WHERE phone_number_id = ((309)::int4)
COMMIT
En ausencia de Inverse ...
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Id (x => x.PersonId).GeneratedBy.Sequence("person_person_id_seq");
Map (x => x.Lastname).Not.Nullable();
Map (x => x.Firstname).Not.Nullable();
// No Inverse
HasMany(x => x.PhoneNumbers).Cascade.All();
}
}
public class PhoneNumberMap : ClassMap<PhoneNumber>
{
public PhoneNumberMap()
{
References(x => x.Person);
Id (x => x.PhoneNumberId).GeneratedBy.Sequence("phone_number_phone_number_id_seq");
Map (x => x.ThePhoneNumber).Not.Nullable();
}
}
... Es responsabilidad de los padres de poseer las entidades secundarias.
Es por eso que incluso usted no indicó inverso al niño (colección) y el niño no tiene ninguna matriz predefinida, su hijo es aparentemente capaces de persistir en sí adecuadamente ...
public static void Main (string[] args)
{
var sess = Mapper.GetSessionFactory().OpenSession();
var tx = sess.BeginTransaction();
var jl = new Person { Firstname = "John", Lastname = "Lennon", PhoneNumbers = new List<PhoneNumber>() };
var pm = new Person { Firstname = "Paul", Lastname = "McCartney", PhoneNumbers = new List<PhoneNumber>() };
// Notice that we didn't indicate Parent key(e.g. Person = jl) for ThePhoneNumber 9.
// If we don't have Inverse, it's up to the parent entity to own the child entities
jl.PhoneNumbers.Add(new PhoneNumber { ThePhoneNumber = "9" });
jl.PhoneNumbers.Add(new PhoneNumber { ThePhoneNumber = "8" });
jl.PhoneNumbers.Add(new PhoneNumber { ThePhoneNumber = "6" });
jl.PhoneNumbers.Add(new PhoneNumber { Person = pm, ThePhoneNumber = "1" });
sess.Save (pm);
sess.Save (jl);
tx.Commit();
}
..., por lo tanto, podemos decir que con la ausencia de atributo Inverso, la persistencia de nuestro gráfico de objetos es solo un golpe de suerte; en una base de datos con un buen diseño, es primordial que nuestros datos sean coherentes, es decir, que nunca debemos indicar que se aceptan nulos en las claves externas del niño, especialmente si ese niño está estrechamente vinculado al padre. Y en el escenario anterior, incluso si ThePhoneNumber "1" indica que Paul McCartney es su padre, John Lennon será el propietario de PhoneNumber, ya que está incluido en las entidades de menores de John; esta es la naturaleza de no etiquetar las entidades secundarias con Inverso, un padre es agresivo para ser el dueño de todas las entidades menores que pertenecen a él, incluso si el niño quería pertenecer a otro padre. Sin inversa, los niños no tienen ningún derecho a elegir su propio padre :-)
Tome una mirada en el registro de SQL para ver por encima de la salida de esta principal
inverso
Luego, al indicar Invertir en entidades secundarias, significa que es responsabilidad del niño elegir su propio padre; la entidad madre nunca se entrometerá.
Así que dado el mismo conjunto de datos sobre el método principal de arriba, aunque con atributo inverso en entidades secundarias ...
HasMany(x => x.PhoneNumbers).Inverse().Cascade.All();
..., John Lennon no va a tener ningún hijo, ThePhoneNumber "1 "que elige a su propio padre (Paul McCartney) incluso ese número de teléfono está en las entidades de menores de John Lennon, aún se mantendrá en la base de datos con Paul McCartney como su padre.Otros números de teléfono que no eligieron a sus padres permanecerán sin padres. Con Inverse, un niño puede elegir libremente a su propio padre, no hay un padre agresivo que pueda ser el dueño del hijo de nadie.
back-end-sabia, así es como se conserva el gráfico de objetos:
BEGIN; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
select nextval ('person_person_id_seq')
select nextval ('person_person_id_seq')
select nextval ('phone_number_phone_number_id_seq')
select nextval ('phone_number_phone_number_id_seq')
select nextval ('phone_number_phone_number_id_seq')
select nextval ('phone_number_phone_number_id_seq')
INSERT INTO person (lastname, firstname, person_id) VALUES (((E'McCartney')::text), ((E'Paul')::text), ((111)::int4))
INSERT INTO person (lastname, firstname, person_id) VALUES (((E'Lennon')::text), ((E'John')::text), ((112)::int4))
INSERT INTO phone_number (the_phone_number, person_id, phone_number_id) VALUES (((E'9')::text), ((NULL)::int4), ((310)::int4))
INSERT INTO phone_number (the_phone_number, person_id, phone_number_id) VALUES (((E'8')::text), ((NULL)::int4), ((311)::int4))
INSERT INTO phone_number (the_phone_number, person_id, phone_number_id) VALUES (((E'6')::text), ((NULL)::int4), ((312)::int4))
INSERT INTO phone_number (the_phone_number, person_id, phone_number_id) VALUES (((E'1')::text), ((111)::int4), ((313)::int4))
COMMIT
Entonces, ¿qué es una buena práctica para la persistencia de las entidades objeto de raíz y sus niños?
En primer lugar, podríamos decir que es un descuido en la parte del equipo de Hibernate/NHibernate hacer que el inverso sea un comportamiento no predeterminado. La mayoría de nosotros que consideramos la coherencia de los datos con sumo cuidado, nunca hacen que las claves foráneas sean nulables. Por lo tanto, siempre debemos indicar explícitamente el inverso como el comportamiento predeterminado.
En segundo lugar, siempre que agreguemos una entidad secundaria a un elemento principal, hagámoslo mediante el método de ayuda del padre. Así que incluso nos olvidamos de indicar el padre del niño, el método auxiliar puede poseer explícitamente esa entidad hijo.
public class Person
{
public virtual int PersonId { get; set; }
public virtual string Lastname { get; set; }
public virtual string Firstname { get; set; }
public virtual IList<PhoneNumber> PhoneNumbers { get; set; }
public virtual void AddToPhoneNumbers(PhoneNumber pn)
{
pn.Person = this;
PhoneNumbers.Add(pn);
}
}
Esta es la forma en nuestra rutina persistencia de objetos se vea como:
public static void Main (string[] args)
{
var sess = Mapper.GetSessionFactory().OpenSession();
var tx = sess.BeginTransaction();
var jl = new Person { Firstname = "John", Lastname = "Lennon", PhoneNumbers = new List<PhoneNumber>() };
var pm = new Person { Firstname = "Paul", Lastname = "McCartney", PhoneNumbers = new List<PhoneNumber>() };
jl.AddToPhoneNumbers(new PhoneNumber { ThePhoneNumber = "9" });
jl.AddToPhoneNumbers(new PhoneNumber { ThePhoneNumber = "8" });
jl.AddToPhoneNumbers(new PhoneNumber { ThePhoneNumber = "6" });
pm.AddToPhoneNumbers(new PhoneNumber { ThePhoneNumber = "1" });
sess.Save (pm);
sess.Save (jl);
tx.Commit();
}
Así es como se conservan los objetos:
BEGIN; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
select nextval ('person_person_id_seq')
select nextval ('phone_number_phone_number_id_seq')
select nextval ('person_person_id_seq')
select nextval ('phone_number_phone_number_id_seq')
select nextval ('phone_number_phone_number_id_seq')
select nextval ('phone_number_phone_number_id_seq')
INSERT INTO person (lastname, firstname, person_id) VALUES (((E'McCartney')::text), ((E'Paul')::text), ((113)::int4))
INSERT INTO phone_number (the_phone_number, person_id, phone_number_id) VALUES (((E'1')::text), ((113)::int4), ((314)::int4))
INSERT INTO person (lastname, firstname, person_id) VALUES (((E'Lennon')::text), ((E'John')::text), ((114)::int4))
INSERT INTO phone_number (the_phone_number, person_id, phone_number_id) VALUES (((E'9')::text), ((114)::int4), ((315)::int4))
INSERT INTO phone_number (the_phone_number, person_id, phone_number_id) VALUES (((E'8')::text), ((114)::int4), ((316)::int4))
INSERT INTO phone_number (the_phone_number, person_id, phone_number_id) VALUES (((E'6')::text), ((114)::int4), ((317)::int4))
COMMIT
Otra buena analogía para Inverso: https://stackoverflow.com/a/1067854
gracias, lo he intentado previamente con ningún efecto, es decir: ' mapping.HasMany (x => x .Addresses) .KeyColumn ("PersonId"). Inverse(). Cascade.All(); ' – wal
¿Has probado eliminar' KeyColumn' y 'Cascade' para ver si los mapas básicos' HasMany' están correctos? – R0MANARMY
@ R0MANARMY Sí, si hago eso, la 'Dirección' no se guarda en absoluto – wal