2012-04-27 18 views
5

que tiene un modelo simple que estoy tratando de persistir el uso de fluidez-nhibernate:nhibernate no ahorrar Id clave externa

public class Person 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public IList<Address> Addresses { get; set; } 
} 

public class Address 
{ 
    public int Id { get; set; } 
    public int PersonId { get; set; } 
    public string Street { get; set; }   
} 

algunos datos de ejemplo:

var person = new Person() {Name = "Name1", Addresses = new[] 
    { 
     new Address { Street = "Street1"}, 
     new Address { Street = "Street2"} 
    }}; 

cuando llamo session.SaveOrUpdate(person) ambos objetos son pero persistió la clave externa no se guarda en la tabla de direcciones:

enter image description here

¿Qué estoy haciendo mal? Mis asignaciones de las anulaciones son los siguientes:

public class PersonOverrides : IAutoMappingOverride<Person> 
{ 
    public void Override(AutoMapping<Person> mapping) 
    { 
     mapping.Id(x => x.Id); 
     mapping.HasMany(x => x.Addresses).KeyColumn("PersonId").Cascade.All();    
    } 
} 

public class AddressOverrides : IAutoMappingOverride<Address> 
{ 
    public void Override(AutoMapping<Address> mapping) 
    { 
     mapping.Id(x => x.Id);    
    } 
} 

Tenga en cuenta, Pienso en el uso List<Address> en otras entidades y no quiero añadir una propiedad Address.Person.

ACTUALIZA 1

Tengo esto que 'trabajar' mediante la sustitución de Address.PersonId con Address.Person pero yo no quiero que el Address que tienen una propiedad persona, ya que no quiero que la referencia circular. Además, al insertar el objeto anterior mirar los registros de NHibernate parece 1) insertar persona 2) Insertar dirección con NULL PERSONID 3) la actualización de direcciones con PERSONID (al lavar) cuando los pasos realmente 2 & 3 se puede hacer en el ¿Mismo tiempo? Esto provoca otro problema si NULL no está permitido en Address.PersonId

ACTUALIZACIÓN 2 Extracción de la propiedad Address.PersonId resultados en PersonId convertirse poblada en la base de datos. A nHibernate no le gusta que proporcione mi propio PersonId que es claramente utilizado internamente para insertar/recuperar registros. Así que realmente quiero marcar mi Address.PersonId con un 'oye, este no es un campo independiente es el campo que va a utilizar en la pista, por favor, hazlo de manera especial'. Además, como se indicó anteriormente, nHibernate parece insertar NULL en la columna PersonId (cuando Save ing) y ENTONCES lo actualiza luego (cuando Flush ing) ??

+0

gracias, lo he intentado previamente con ningún efecto, es decir: ' mapping.HasMany (x => x .Addresses) .KeyColumn ("PersonId"). Inverse(). Cascade.All(); ' – wal

+0

¿Has probado eliminar' KeyColumn' y 'Cascade' para ver si los mapas básicos' HasMany' están correctos? – R0MANARMY

+0

@ R0MANARMY Sí, si hago eso, la 'Dirección' no se guarda en absoluto – wal

Respuesta

0

Su propiedad de PersonId parece estar mapeada y es propiedad normal, no como referencia de otro objeto. Así que yo sugeriría a probar dos cosas:

  1. tratar de cambiar su propiedad PERSONID a ser de tipo Person y el nombre de la persona
  2. comprobar que el código se ejecuta en el interior transation (que puede afectar la eficacia de NHibernate con asociaciones)
  3. Guarde los automappings generados en archivos XML y ver cómo funciona realmente nhiberante con su modelo
+0

'Intente cambiar su propiedad PersonId para que sea del tipo Persona y asígnele el nombre Persona' Dije específicamente que no quería hacer eso. – wal

0

Es necesario utilizar referencias.No estoy familiarizado con IAutoMappingOverride, pero esto es como lo hago en el mapeo manual, observe las referencias del AnswerMap en Pregunta:

public class QuestionMap : ClassMap<Question> 
{ 
    public QuestionMap() 
    { 
     Id(x => x.QuestionId).GeneratedBy.Sequence("question_seq"); 

     Map(x => x.TheQuestion).Not.Nullable(); 

     HasMany(x => x.Answers).Inverse().Not.LazyLoad().Cascade.AllDeleteOrphan(); 
    } 
} 

public class AnswerMap : ClassMap<Answer> 
{ 
    public AnswerMap() 
    { 
     References(x => x.Question); 

     Id(x => x.AnswerId).GeneratedBy.Sequence("answer_seq"); 

     Map(x => x.TheAnswer).Not.Nullable(); 
    } 
} 


Modelo aquí:

public class Question 
{ 
    public virtual int QuestionId { get; set; } 

    public virtual string TheQuestion { get; set; }   

    public virtual IList<Answer> Answers { get; set; } 
} 

public class Answer 
{ 
    public virtual Question Question { get; set; } 

    public virtual int AnswerId { get; set; } 

    public virtual string TheAnswer { get; set; }     
} 

Nótese que se dejase' t use public virtual int QuestionId { get; set; } en clase de respuesta; en su lugar, deberíamos usar public virtual Question Question { get; set; }. De esta forma hay más OOP, básicamente está claro cómo se ve el modelo de dominio, sin el crumble de cómo los objetos deben relacionarse entre sí (no por int, no por cadena, etc., sino por referencias de objeto)

Para disipar sus preocupaciones, cargar el objeto (a través de session.Load) no incurre en la ida y vuelta de la base de datos.

var answer = new Answer { 
    // session.Load does not make database request 
    Question = session.Load<Question>(primaryKeyValueHere), 

    TheAnswer = "42" 
}; 
+0

'Tenga en cuenta que planeo utilizar la Lista

en otras entidades y no deseo agregar una propiedad Address.Person' – wal

+0

ya que se va a serializar – wal

+0

Hmm ... ese es un requisito extraño, pero su diseño (basado en su captura de pantalla) indica que la tabla de direcciones está estrechamente unida a la tabla Person –

10

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

+0

Lo sé, No es buena política de StackExchange escribir comentarios como este, pero esta es una de las mejores y más completas y útiles respuestas que he visto en StackOverflow. Gracias, Michael. –

1

Con un pequeño arreglo a su código original esto se puede lograr, para mayor claridad voy a publicar el código completo,

public class Person 
    { 
     public virtual int Id { get; set; } 

     public virtual string Name { get; set; } 

     public virtual IList<Address> Addresses { get; set; } 

    } 

public class Address 
    { 
     public virtual int Id { get; set; } 

     public virtual int PersonId { get; set; } 

     public virtual string Street { get; set; } 
    } 

public class PersonOverrides : IAutoMappingOverride<Person> 
    { 
     public void Override(AutoMapping<Person> mapping) 
     { 
      mapping.Id(x => x.Id); 
      mapping.HasMany(x => x.Addresses).KeyColumn("PersonId").Cascade.All();    
     } 
    } 

Aquí está el código modificado,

public class AddressOverrides : IAutoMappingOverride<Address> 
    { 
     public void Override(AutoMapping<Address> mapping) 
     { 
      mapping.Id(x => x.Id); 
      mapping.Map(x => x.PersonId).Column("PersonId"); 
     } 
    } 
+0

gracias voy a comprobar esto y volver – wal

+0

esto no funciona para mí. 'Address.PersonId' no se establece al recuperar una' Persona' de la base de datos. La adición que hice a mi código fue agregar el mapeo 'HasOne'. Vea mi proyecto de picos aquí http://wallaceturner.com/files/FluentNHibernateSubClassTest.zip, que puede ejecutar desde la consola. (asume la base de datos) – wal

+0

He actualizado la respuesta, agregando mapping.Map (x => x.PersonId) .Column ("PersonId"); debe hacer que las cosas funcionen, pero debe deshacerse de la clave externa en el DB para que funcione si tiene una. –

0

parece ser un problema de diseño

Si la intención es evitar la referencia de la persona dentro de la dirección de a/c de uso contextual de la dirección de

Entonces habría que introducir un "discriminador"

es decir

Address {AddressId, ...} 
PersonAddress : Address {Person, ...}, 
CustomerAddress : Address {Customer, ...}, 
VendorAddress : Address {Vendor, ...} 

Se podría inferir el discriminador vía fórmula así más bien especificando un valor discriminador duro

Ref: Discriminator based on joined property

modificar Alternativamente, la estructura db si se permite/posible

Persons [PersonId, ...] 
Addresses [AddressId] 
AddressDetails [AddressDetailId, AddressId, ...] 

Con Asignaciones de la siguiente manera (no hay idea de cómo se haría esto de forma fluida, pero es posible a través de xml) 1.) Persona + Direcciones a través de una tabla de combinación 2.) Dirección + AddressDetails través referencia

, sin duda prefiere la primera opción

aplausos ...

Cuestiones relacionadas