wal*_*wal 5 c# nhibernate fluent-nhibernate
我有一个简单的模型,我试图使用流畅的nhibernate持久化:
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; }
}
Run Code Online (Sandbox Code Playgroud)
一些样本数据:
var person = new Person() {Name = "Name1", Addresses = new[]
{
new Address { Street = "Street1"},
new Address { Street = "Street2"}
}};
Run Code Online (Sandbox Code Playgroud)
当我调用session.SaveOrUpdate(person)两个对象都是持久的但外键未保存在地址表中:

我究竟做错了什么?我的映射覆盖如下:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,我打算List<Address>在其他实体中使用,我不想添加Address.Person属性.
更新1
我已经通过替换来"工作" Address.PersonId,Address.Person但我不希望Address有一个Person属性,因为我不想要那个循环引用.此外,当插入上面的对象查看日志nHibernate似乎1)插入Person 2)插入地址与NULL PersonId 3)更新地址与PersonId(当刷新时)真正的步骤2和3可以同时完成?如果在Address.PersonId上不允许NULL,则会导致另一个问题
更新2
删除属性会Address.PersonId导致在PersonId数据库中填充.nHibernate不喜欢我提供我自己的PersonId,它在内部清楚地用于插入/检索记录.所以我真的想Address.PersonId用'嘿,这不是一个独立的领域标志着你将要在赛道上使用的领域,请特别对待它'旗帜.另外,如上所述,nHibernate似乎在PersonId列中插入NULL(当Saveing时),然后THEN更新它(当Flushing时)?
Mic*_*uen 10
我模拟了您的问题情况,插入时具有空父键的子项,然后使用右父键更新.
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
Run Code Online (Sandbox Code Playgroud)
没有逆...
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();
}
}
Run Code Online (Sandbox Code Playgroud)
......父母有责任拥有子实体.
这就是为什么即使你没有向孩子(集合)指出反向而且孩子没有任何预定义的父母,你的孩子似乎能够正确地坚持自己......
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();
}
Run Code Online (Sandbox Code Playgroud)
...因此我们可以说,由于没有Inverse属性,我们的对象图的持久性只是一个侥幸; 在具有良好设计的数据库上,最重要的是我们的数据是一致的,这是必须的,我们永远不应该在子项的外键上指明可为空,特别是如果该子项与父项紧密耦合.在上面的场景中,即使ThePhoneNumber"1"表示保罗·麦卡特尼为其父母,约翰·列侬稍后将拥有该电话号码,因为它包含在约翰的儿童实体中; 这是不使用Inverse标记子实体的本质,即使孩子想要属于其他父级,父级也会主动拥有属于它的所有子实体.没有Inverse,孩子们没有权利选择自己的父母:-)
看一下上面的SQL日志,看看这个Main的输出
逆
然后,当指示对子实体的反向时,这意味着孩子有责任选择自己的父母; 父实体永远不会干涉.
因此,给定上面方法Main的相同数据集,尽管在子实体上有Inverse属性...
HasMany(x => x.PhoneNumbers).Inverse().Cascade.All ();
Run Code Online (Sandbox Code Playgroud)
......,John Lennon将不会有任何孩子,ThePhoneNumber"1"选择自己的父母(Paul McCartney)即使那个电话号码在John Lennon的子实体中,它仍然会被保留到数据库中,Paul McCartney作为其父母.未选择其父母的其他电话号码将保持无父母.使用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
Run Code Online (Sandbox Code Playgroud)
那么持久化根对象及其子实体有什么好的做法呢?
首先,我们可以说这是对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);
}
}
Run Code Online (Sandbox Code Playgroud)
这是我们的对象持久化例程的样子:
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();
}
Run Code Online (Sandbox Code Playgroud)
这是我们的对象持久化的方式:
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
Run Code Online (Sandbox Code Playgroud)
Inverse的另一个很好的类比:https://stackoverflow.com/a/1067854
您的 PersonId 属性似乎是映射的常规属性,而不是对其他对象的引用。所以我建议你尝试两件事: