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
Dank bevorzugen, habe ich versucht, die zuvor ohne Wirkung, das heißt: 'mapping.HasMany (x => x .Addresses) .KeyColumn ("PersonId"). Inverse(). Cascade.All(); ' – wal
Haben Sie versucht,' KeyColumn' und 'Cascade' zu entfernen, um zu sehen, ob die grundlegenden' HasMany'-Maps in Ordnung sind? – R0MANARMY
@ R0MANARMY Ja, wenn ich das tue, wird die 'Adresse' überhaupt nicht gespeichert. – wal