我模拟您的问题情况,插入时具有空父键的孩子,然后用正确的父键更新。
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();
}
...,因此我们可以说,在没有 Inverse 属性的情况下,我们的对象图的持久性只是侥幸;在具有良好设计的数据库上,我们的数据保持一致是至关重要的,也就是说,我们永远不应该在孩子的外键上指示可为空,尤其是当孩子与父母紧密耦合时。在上面的场景中,即使 ThePhoneNumber“1”表示 Paul McCartney 是其父级,John Lennon 稍后将拥有该 PhoneNumber,因为它包含在 John 的子实体中;这是不使用 Inverse 标记子实体的性质,即使子实体想要属于其他父实体,父实体也会积极拥有属于它的所有子实体。没有逆,孩子没有任何权利选择自己的父母:-)
看看上面的 SQL 日志,看看这个 Main 的输出
逆
然后在子实体上指示 Inverse 时,表示选择自己的父实体是子实体的责任;母公司永远不会干涉。
因此,给定上述 Main 方法的相同数据集,尽管子实体具有 Inverse 属性...
HasMany(x => x.PhoneNumbers).Inverse().Cascade.All ();
...,约翰列侬不会有任何孩子,ThePhoneNumber "1" 选择它自己的父母(保罗麦卡特尼),即使电话号码在约翰列侬的孩子实体中,它仍然会以保罗麦卡特尼作为它的父母被持久化到数据库中. 其他没有选择父母的电话号码将保持无父母状态。使用 Inverse,孩子可以自由选择自己的父母,没有攻击性的父母可以拥有任何人的孩子。
后端方面,这就是对象图的持久化方式:
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 成为非默认行为方面的疏忽。我们大多数人都非常谨慎地考虑数据一致性,永远不会让外键为空。因此,我们应该始终明确地将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