我有一个简单的模型,我试图用流利,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; }
}
一些样本数据:
var person = new Person() {Name = "Name1", Addresses = new[]
{
new Address { Street = "Street1"},
new Address { Street = "Street2"}
}};
当我打电话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);
}
}
请注意 ,我打算使用List<Address>
其他实体,我不想要添加Address.Person
财产。
更新1
我已经更换了这“工作” Address.PersonId
与Address.Person
,但我不希望Address
能有一个人的财产,我不希望这样的循环引用。 此外,将上述目的看日志时NHibernate的出现1)插入人2)插入地址与NULL PERSONID 3)更新地址与PERSONID真的要在2和3可以在同一时间内完成(冲洗时)? 这会导致另一个问题,如果NULL是在Address.PersonId禁止
更新2卸下财产Address.PersonId
的结果PersonId
变得填充到数据库中。 NHibernate的犯规像我提供我自己的PERSONID其明确内部使用,用于插入/检索记录。 所以我真的希望我的标志Address.PersonId
以“嘿,这心不是一个独立的领域的,你要使用走下赛场现场请做过对待它特别”标志。 此外,如上所述,的nHibernate似乎插入NULL到PERSONID柱(当Save
荷兰国际集团)和事后然后更新它(当Flush
ING)??
我模仿你的问题的情况下,与对插入null父键,然后用正确的父键更新后的孩子。
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();
}
}
......这是家长对自己孩子的实体责任。
这就是为什么即使你没有表明逆孩子(集合)和孩子没有任何预定义的父母,你的孩子似乎能正常坚持自己...
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();
}
......,因此,我们可以说,与没有逆属性,我们的对象图的persistability是侥幸; 有一个好的设计的数据库上,这是最重要的,我们的数据是一致的,那就是这是一个必须说,我们不应该上注明孩子的外键可为空的,尤其是如果孩子是紧耦合的父母。 而在上述情况下,即使ThePhoneNumber“1”表示保罗·麦卡特尼作为其母公司,约翰·列侬将在以后拥有该******中国,因为它是包含在约翰的孩子实体; 这不是标记孩子实体逆的性质,父母是积极的拥有属于它所有儿童实体,即使孩子想要属于其他家长。 由于没有逆,孩子没有任何权利来选择自己的父母:-)
看看上面的SQL日志看到这个主要的输出
逆
表明在子实体然后逆时,它的意思是孩子的责任,有权选择自己的父母; 母公司将永远不会染指。
因此,鉴于上述的方法主要,尽管在子实体逆属性相同的一组数据的...
HasMany(x => x.PhoneNumbers).Inverse().Cascade.All ();
......,约翰·列侬不会有任何孩子,ThePhoneNumber“1”选择自己的父(保罗·麦卡特尼)甚至认为电话号码是约翰·列侬的孩子的实体,它仍然会与保罗·麦卡特尼持久化到数据库作为其父。 其它电话号码,这并没有选择他们的父母,仍将父母双亡的孤儿。 随着逆,孩子可以自由选择自己的父母,有没有可以拥有任何人的孩子咄咄逼人的父母。
后端的角度来看,这是对象图是如何坚持:
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
那么,什么是坚持为根对象及其子实体好的做法呢?
首先,我们可以说,它是在休眠/ NHibernate的团队在做逆非默认行为的疏忽。 我们大多数人谁把极其谨慎数据的一致性,就永远不要让外键为空。 所以,我们应该始终明确地指出反作为默认行为。
其次,当我们添加一个子实体父母,父母通过的helper方法做到这一点。 因此,即使我们忘记注明孩子的父母,辅助方法可以明确地拥有该子实体。
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
另一个很好的类比反: https://stackoverflow.com/a/1067854
随着一点点的调整,以你原来的代码可以做到这一点,为了清楚我会后整个代码,
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();
}
}
这里是改变的代码,
public class AddressOverrides : IAutoMappingOverride<Address>
{
public void Override(AutoMapping<Address> mapping)
{
mapping.Id(x => x.Id);
mapping.Map(x => x.PersonId).Column("PersonId");
}
}
您需要使用参考。 我不熟悉IAutoMappingOverride,但这是我如何做到这一点的手动映射,注意到AnswerMap对问题参考资料:
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();
}
}
这里的模型:
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; }
}
请注意,我们没有使用public virtual int QuestionId { get; set; }
public virtual int QuestionId { get; set; }
public virtual int QuestionId { get; set; }
在答题类,我们应该用public virtual Question Question { get; set; }
public virtual Question Question { get; set; }
public virtual Question Question { get; set; }
来代替。 这样更OOP,它基本上是清楚你的域模型的样子,不受对象应该如何与对方克鲁夫特(不是INT,而不是字符串,等等;但对象的引用)
为了消除您的后顾之忧,正在加载的对象(通过session.Load
)不会导致数据库往返。
var answer = new Answer {
// session.Load does not make database request
Question = session.Load<Question>(primaryKeyValueHere),
TheAnswer = "42"
};
似乎是一个设计问题
如果目的是为了避免在上下文地址的使用的A / C类地址范围内的人参考
然后,我会介绍了“鉴别”
即
Address {AddressId, ...}
PersonAddress : Address {Person, ...},
CustomerAddress : Address {Customer, ...},
VendorAddress : Address {Vendor, ...}
你可以推断出通过上述公式识别器以及相当指定硬鉴别值
参考: 根据加盟性质鉴别
可替换地修改分贝结构如果允许/可能的
Persons [PersonId, ...]
Addresses [AddressId]
AddressDetails [AddressDetailId, AddressId, ...]
随着映射如下(不知道如何做到这一点的流畅进行,而是通过XML可能)1)通过一个人+地址通过加入参考表2)地址+ AddressDetails
我肯定会喜欢的第一个选项
欢呼声......