2012-04-27 5 views
5

ich ein einfaches Modell fließend-nhibernate bestehen bleiben mit Ich bin versucht haben:nhibernate nicht Speichern Fremdschlüssel Id

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; }   
} 

Einige Beispieldaten:

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

Wenn ich rufe session.SaveOrUpdate(person) beide Objekte sind blieb aber der Fremdschlüssel in der Adresstabelle nicht gespeichert:

enter image description here

Was mache ich falsch? Meine Zuordnungen überschreibt, sind wie folgt:

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);    
    } 
} 

Bitte beachten Sie, ich plane List<Address> in anderen Einheiten zur Verwendung und ich möchte nicht eine Address.Person Eigenschaft hinzufügen.

UPDATE 1

Ich habe dies habe zu ‚Arbeit‘ durch Address.PersonId mit Address.Person ersetzen, aber ich will nicht die Address eine Person Eigentum haben, da ich nicht, dass die kreisförmige Referenz will. nHibernate erscheint 1) einlegen Person 2) Schreiben Sie Ihre Adresse mit NULL PersonId in den Protokollen auch auf der Suche, wenn das oben genannte Ziel Einfügen 3) Update-Adresse mit PersonId (beim Spülen) wenn wirklich die Schritte 2 & 3 kann auf die getan werden gleiche Zeit? Dies führt zu einem weiteren Problem, wenn NULL auf Address.PersonId nicht zulässig ist

UPDATE 2 die Eigenschaft Address.PersonId Ergebnisse in PersonId Entfernen in der Datenbank gefüllt werden. nHibernate mag es nicht, dass ich meine eigene PersonId zur Verfügung stelle, die sie intern zum Einfügen/Abrufen von Datensätzen verwendet. Also ich möchte meine Address.PersonId mit einem 'hey das ist nicht ein eigenständiges Feld, das das Feld, das Sie verwenden werden, den Track bitte getan behandeln es speziell' flag. Wie oben erwähnt, scheint nHibernate NULL in die PersonId-Spalte einzufügen (wenn Save ing) und DANN danach zu aktualisieren (wenn Flush ing) ??

+0

Dank bevorzugen, habe ich versucht, die zuvor ohne Wirkung, das heißt: 'mapping.HasMany (x => x .Addresses) .KeyColumn ("PersonId"). Inverse(). Cascade.All(); ' – wal

+0

Haben Sie versucht,' KeyColumn' und 'Cascade' zu ​​entfernen, um zu sehen, ob die grundlegenden' HasMany'-Maps in Ordnung sind? – R0MANARMY

+0

@ R0MANARMY Ja, wenn ich das tue, wird die 'Adresse' überhaupt nicht gespeichert. – wal

Antwort

0

Ihre PersonId Eigenschaft scheint kartiert und regelmäßige Eigenschaft, nicht als Hinweis auf ein anderes Objekt. So würde ich Ihnen vorschlagen, zwei Dinge versuchen:

  1. Versuchen Sie, Ihre PersonId Eigenschaft zu ändern zu sein, der Art Person und Name es Person
  2. Stellen Sie sicher, dass Ihr Code innerhalb transation ausgeführt wird (es kann beeinflussen, wie nhibernate Arbeiten mit Verbände)
  3. Ihre generierten automappings in XML-Dateien speichern und sehen, wie nhiberante tatsächlich mit Ihrem Modell
+0

'Versuchen Sie, Ihre PersonId-Eigenschaft zu ändern, um vom Typ Person zu sein, und nennen Sie es Person' Ich sagte speziell, dass ich das nicht tun wollte. – wal

0

Sie benötigen arbeitet Referenzen zu verwenden.Ich bin nicht vertraut mit IAutoMappingOverride, aber das ist, wie ich es bei der manuellen Zuordnung zu tun, bemerken die AnswerMap Referenzen auf Anfrage:

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(); 
    } 
} 


Modell hier:

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; }     
} 

Bitte beachte, dass wir didn‘ t verwenden public virtual int QuestionId { get; set; } auf Antwortklasse, sollten wir stattdessen public virtual Question Question { get; set; } verwenden. Auf diese Weise mehr OOP ist, es ist im Grunde klar, wie Sie Ihr Domain-Modell aussieht, frei von der cruft, wie Objekte miteinander in Beziehung stehen sollen (nicht von int, nicht durch Zeichenfolge, etc., aber durch Objektreferenzen)

auszuzuräumen Ihre Sorgen, das Laden des Objekts (über session.Load) verursacht keine Datenbank Roundtrip.

+0

'Bitte beachten Sie, ich plane die Verwendung von Liste

in anderen Entitäten und ich möchte keine Address.Person Eigenschaft hinzufügen ' – wal

+0

wie es wird serialisiert werden – wal

+0

Hmm .. das ist ungerade Anforderung, noch Ihr Design (basierend auf Ihrer screenshot) zeigt an, dass Adresstabelle eng mit Person Tabelle –

10

Ich simuliere Ihre Problemsituation, das Kind mit Null Elternschlüssel beim Einfügen, die später mit dem richtigen Elternschlüssel aktualisiert wird.

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 

auf die Abwesenheit der 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 ist die Verantwortung der Eltern die Kinder Einheiten zu besitzen.

Aus diesem Grund auch nicht Inverse für das Kind (Sammlung) angegeben hat und das Kind haben keine vordefinierten Eltern, Ihr Kind scheinbar Lage ist, sich richtig zu bestehen ...

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();    
} 

..., daher können wir sagen, dass die Persistenz des Objektgraphen ohne die Inverse-Eigenschaft nur ein Zufall ist; Bei einer Datenbank mit gutem Design ist es von größter Wichtigkeit, dass unsere Daten konsistent sind. Das heißt, es ist ein Muss, dass wir keine Nullwerte für die Fremdschlüssel eines Kindes angeben dürfen, insbesondere wenn das Kind eng an die Eltern gekoppelt ist. Und in dem obigen Szenario, auch wenn ThePhoneNumber "1" Paul McCartney als sein Elternteil angibt, wird John Lennon später diese PhoneNumber besitzen, da sie in Johns Kinderentitäten enthalten ist; Dies ist die Art, die untergeordneten Elemente nicht mit Inverse zu kennzeichnen, ein Elternteil ist aggressiv, wenn alle untergeordneten Kindelemente zu ihm gehören, selbst wenn das Kind einem anderen Elternteil angehören möchte. Ohne Inverse, haben die Kinder haben keine Rechte, ihre eigenen Eltern wählen :-)

einen Blick auf das Protokoll über SQL Nehmen Sie diese Mains Ausgabe


Inverse zu sehen

Wenn Sie Inverse auf untergeordnete Entitäten angeben, bedeutet dies, dass das Kind selbst für die Auswahl seines eigenen Elternteils verantwortlich ist. die übergeordnete Entität wird sich niemals einmischen.

So die gleiche Menge von Daten über die Methode Haupt oben angegebenen, wenn auch mit Invers-Attribut auf untergeordnete Entitäten ...

HasMany(x => x.PhoneNumbers).Inverse().Cascade.All(); 

... wird John Lennon keine Kinder haben „, ThePhoneNumber 1 "Die Wahl des eigenen Elternteils (Paul McCartney), auch wenn diese Telefonnummer in John Lennons Kinderentitäten enthalten ist, wird weiterhin in der Datenbank mit Paul McCartney als Elternteil bestehen bleiben.Andere Telefonnummern, die ihre Eltern nicht gewählt haben, bleiben ohne Eltern. Mit Inverse kann ein Kind seinen eigenen Elternteil frei wählen, es gibt keinen aggressiven Elternteil, der jemandes Kind besitzen kann.

Back-end-weise, das ist, wie die Objekte Graph beibehalten wird:

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 

Also, was ist eine gute Praxis, Wurzelobjekt und seine Kinder Einrichtungen für persistierende?

Erstens können wir sagen, dass es ein Fehler im Team von Hibernate/NHibernate ist, das Inverse zu einem nicht standardmäßigen Verhalten zu machen. Die meisten von uns, die Datenkonsistenz mit äußerster Sorgfalt betrachteten, würden niemals machen, um Fremdschlüssel nullbar zu machen. Daher sollten wir immer das Inverse als Standardverhalten explizit angeben.

Zweitens, wenn wir eine untergeordnete Entität zu einem Eltern hinzufügen, tun Sie dies über Eltern-Helper-Methode. Selbst wenn wir vergessen haben, das Elternelement des Kindes anzugeben, kann die Hilfsmethode diese untergeordnete Entität explizit besitzen. Diese

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); 
    } 
} 

ist, wie unser Objekt-Persistenz Routine wird wie folgt aussehen:

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();    
} 

Dies ist, wie unsere Objekte werden beibehalten:

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 

Eine weitere gute Analogie für Inverse: https://stackoverflow.com/a/1067854

+0

Ich weiß, Es ist keine gute StackExchange-Richtlinie, Kommentare wie diesen zu schreiben, aber dies ist eine der besten und gründlichsten und nützlichsten Antworten, die ich jemals auf StackOverflow gesehen habe. Danke, Michael. –

1

Mit einem kleinen Kniff zu Ihrem ursprünglichen Code dies erreicht werden kann, für Klarheit ich den gesamten Code wird Post,

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();    
     } 
    } 

Hier ist der geänderte Code,

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

danke ich werde das überprüfen und wieder kommen – wal

+0

das funktioniert nicht für mich. 'Address.PersonId' wird nicht festgelegt, wenn eine' Person' aus der Datenbank abgerufen wird. Der Zusatz, den ich zu meinem Code gemacht habe, war das 'HasOne'-Mapping. Sehen Sie mein Spike-Projekt hier http://wallaceturner.com/files/FluentNHibernateSubClassTest.zip, das Sie einfach von der Konsole aus ausführen können. (nimmt Datenbank an) – wal

+0

Ich habe die Antwort aktualisiert, Mapping.Map (x => x.PersonId) .Column ("PersonId"); sollte Dinge zur Arbeit bringen, aber Sie müssen den Fremdschlüssel in der DB loswerden dies zu funktionieren, wenn Sie einen haben. –

0

Scheint ein Design-Problem zu sein

Wenn die Absicht Bezug von Person innerhalb Adresse auf einem/c kontextueller Verwendung der Adresse zu vermeiden, ist

Dann würde ich einen „Scheidungs“ einführen

dh

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

Sie den Diskriminator über Formel ableiten könnte auch eher ein harter Diskriminatorwert Angabe

Ref: Discriminator based on joined property

Alternativ können Sie die DB-Struktur ändern, wenn zulässig/möglich

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

Mit Mappings wie folgt (keine Ahnung, wie das flüssig gemacht werden würde, aber über xml möglich ist) 1.) Person + Adressen über eine Join-Tabelle 2.) Adresse + AddressDetails über Referenz

ich auf jeden Fall die erste Option

prost ...