Я имитирую вашу проблемную ситуацию, ребенок с нулевым родительским ключом при вставке, который затем обновляется позже с помощью правильного родительского ключа.
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
Об отсутствии обратных ...
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();
}
}
... это ответственность родителя владеть дочерних объектов.
Вот почему даже вы не указал Inverse к ребенку (сбору) и ребенок не имеет предопределенные родителей, ваш ребенок , казалось бы, способны персистировать себя правильно ...
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();
}
..., поэтому мы можем сказать, что с отсутствием обратного атрибута постоянство нашего объектного графика - всего лишь случайность; в базе данных с хорошим дизайном важно, чтобы наши данные были согласованными, то есть необходимо, чтобы мы никогда не указывали на значение NULL для внешних ключей дочерних элементов, особенно если этот ребенок тесно связан с родителем. И в приведенном выше сценарии, даже если ThePhoneNumber «1» указывает на Пола Маккартни в качестве его родителя, Джон Леннон позже будет владеть этим PhoneNumber, так как он включен в детские сущности Джона; это характер не пометки дочерних объектов с помощью инверсии, родитель агрессивен, чтобы владеть всеми дочерними объектами, принадлежащими ему, даже если ребенок хочет принадлежать другому родителю. При отсутствии Inverse, дети не имеют права выбирать свой собственный родитель :-)
Посмотрите на журнал SQL выше, чтобы увидеть результат этой главной в
Inverse
Затем, указывая на обратные дочерние объекты, это означает, что ребенок обязан выбрать своего родителя; родительский объект никогда не вмешивается.
Поэтому, учитывая тот же набор данных о методе Main выше, хотя и с обратным атрибутом дочерних сущностей ...
HasMany(x => x.PhoneNumbers).Inverse().Cascade.All();
... Джон Леннон не будет иметь детей, ThePhoneNumber «1 «которые выбирают своего родителя (Пол Маккартни), даже если этот номер телефона находится в дочерних подразделениях Джона Леннона, он по-прежнему будет храниться в базе данных с Полом Маккартни в качестве его родителя.Другие номера телефонов, которые не выбрали родителя, останутся без родителей. С помощью Inverse ребенок может свободно выбирать своего родителя, нет агрессивного родителя, который может владеть чьим-либо ребенком.
Back-конец-накрест, это как упорствовал объекты графа:
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
Так что это хорошая практика для сохраняющихся корневого объекта и его дочерние организации?
Во-первых, мы могли бы сказать, что это надзор над частью команды Hibernate/NHibernate, чтобы сделать обратное поведение, отличное от умолчания. Большинство из нас, которые рассматривали целостность данных с предельной осторожностью, никогда не делают внешние ключи обнуляемыми. Поэтому мы всегда должны указывать Inverse как поведение по умолчанию.
Во-вторых, всякий раз, когда мы добавляем дочерний объект к родительскому объекту, выполняйте это с помощью вспомогательного метода родителя. Поэтому даже мы забыли указать родителя ребенка, вспомогательный метод может явно содержать этот дочерний объект.
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);
}
}
Это как наш объект рутина настойчивость должен выглядеть следующим образом:
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();
}
Это, как, сохраняются наши объекты:
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
Еще одна хорошая аналогия для Inverse: https://stackoverflow.com/a/1067854
спасибо, я попробовал, что ранее без эффекта, то есть: 'mapping.HasMany (х => х .Addresses) .KeyColumn («PersonId») .Inverse() .Cascade.All(); ' – wal
Вы пытались удалить« KeyColumn »и« Cascade », чтобы увидеть, нормально ли отображаются карты« HasMany »? – R0MANARMY
@ R0MANARMY Да, если я это сделаю, то «Адрес» вообще не сохраняется – wal