问题描述
public class Person
我有一个简单的模型,我试图用fluent-nhibernate持久化{
public int Id {get;组; }
public string Name {get;组; }
public IList< Address>地址{get;组; }
}
公共类地址
{
public int Id {get;组; }
public int PersonId {get;组; }
public string Street {get;组;一些示例数据:
var person = new Person(){Name =Name1,Addresses = new []
{
new Address {Street =Street1} ,
新地址{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);
请注意,I计划在其他实体中使用 List< Address>
,并且我不想添加 Address.Person
属性。
更新1
Address.PersonId
与 Address.Person
但我不希望地址
有一个Person属性,因为我不想要那个循环引用。插入上面的对象来查看日志nHibernate出现
1)插入Person
2)插入带有空的PersonId
的地址3)用PersonId更新地址(刷新时)
当真正的步骤2& 3可以同时完成吗?如果在Address.PersonId上禁止NULL,则会导致另一个问题。
更新2
删除属性地址.PersonId
导致 PersonId
被填充到数据库中。 nHibernate不喜欢我提供我自己的PersonId,它清楚地使用内部插入/检索记录。所以我真的想标记我的 Address.PersonId
这个'嘿,这不是一个独立的领域,你将要使用的领域,请专门处理它'旗。同样,如上所述,nHibernate似乎在PersonId列中插入NULL(当 Save
ing),然后在之后更新它(当 Flush $ c
解决方案我模拟你的问题情况,稍后用正确的父键。
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' )(((E'Lennon'):: text),((E'John'):: text),((109):: int4))
INSERT INTO person(lastname,firstname,person_id)VALUES (((E'9'):: text),((NULL):: int4)($($($')')}
INSERT INTO phone_number(the_phone_number,person_id,phone_number_id)VALUES ,((306):: int4))
INSERT INTO phone_number(the_phone_number,person_id,phone_number_id)VALUES(((E'8'):: text),((NULL):: int4),((307 )(((E'6'):: text),((NULL):: int4),((308))int b)b
INSERT INTO phone_number(the_phone_number,person_id,phone_number_id)VALUES (((E'1'):: text),((109):: int4),((309):: int4))
INSERT INTO phone_number(the_phone_number,person_id,phone_number_id)VALUES )
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();
//否
HasMany(x => x.PhoneNumbers).Cascade.All();
}
}
公共类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();
$ b $ p $ ...这是父母有责任拥有孩子实体。
这就是为什么即使你没有向孩子指出逆向(集合),孩子也没有任何预定义的父母,你的孩子看起来像
em>能够坚持自己正确的... public static void Main(string [] args)
{
var sess = Mapper.GetSessionFactory()。OpenSession();
var tx = sess.BeginTransaction();
$ b $ var jl = new Person {Firstname =John,Lastname =Lennon,PhoneNumbers = new List< PhoneNumber>()};
var pm = new Person {Firstname =Paul,Lastname =McCartney,PhoneNumbers = new List< PhoneNumber>()};
//注意,我们没有为ThePhoneNumber 9指示父键(例如Person = jl)。
//如果我们没有反转,则取决于父实体拥有子实体
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();
$ ...因此我们可以说,在缺少逆元素的情况下,我们的对象图的持久性只是一个侥幸;在一个设计好的数据库上,我们的数据是一致的,也就是说,我们绝对不能在孩子的外键上表示空,尤其是如果这个孩子与父母紧密相连的话。在上面的情况中,即使ThePhoneNumber1表示Paul McCartney作为其父母,John Lennon也将拥有该PhoneNumber,因为它包含在John的子实体中;这是不使用反向标记子实体的本质,即使孩子想要属于其他父母,父母也很积极拥有属于它的所有子实体。没有反过来,孩子没有任何权利选择自己的父母: - )
看看上面的SQL日志,看看这个主要的输出
逆
然后,当指示子实体的逆时,意味着选择自己的父亲是孩子的责任;父元素不会干预。
因此,如果在上面的Main方法中使用了相同的数据集,尽管在子实体上有反向属性...
HasMany(x => x.PhoneNumbers).Inverse()。Cascade.All();
...,John Lennon将不会有任何孩子,ThePhoneNumber1父母(保罗·麦卡特尼)即使那个电话号码是在约翰列侬的儿童实体,仍然会坚持以保罗·麦卡特尼为母亲的数据库。其他电话号码没有选择他们的父母,将保持无父母。反过来,一个孩子可以自由选择自己的父母,没有可以拥有任何人的孩子的积极的父母。
后端明智,这是对象图持续:
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' )((E'Lennon'):: text),((E'John'):: text),((111):: int4))
INSERT INTO person(lastname,firstname,person_id)VALUES (((E'9'):: text),((NULL):: int4)($($')');
INSERT INTO phone_number(the_phone_number,person_id,phone_number_id)VALUES ,((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) (((E'1'):: text),((111):: int4),((313):: int4):
INSERT INTO phone_number(the_phone_number,person_id,phone_number_id)VALUES )
COMMIT
对于坚持根对象和它的子实体的良好实践?首先,我们可以说,这是对Hibernate / NHibernate团队的一个监督,使得反向是一个非默认的行为。我们大多数人都非常关心数据一致性,绝不会使外键为空。所以,我们应该总是明确指出逆作为默认行为。
第二,每当我们添加一个子实体到父项,做这个通过父母的帮手方法。因此,即使我们忘记指明孩子的父母,辅助方法也可以明确地拥有该孩子的实体。
public class Person
{
public virtual int PersonId {get;组; }
公共虚拟字符串姓氏{get;组; }
公共虚拟字符串名字{get;组; }
公共虚拟IList< PhoneNumber> PhoneNumbers {get;组; }
public virtual void AddToPhoneNumbers(PhoneNumber pn)
{
pn.Person = this;
PhoneNumbers.Add(pn);
$ b $ p
$ b $ p这就是我们的对象持久化例程应该是这样的: public static void Main(string [] args)
{
var sess = Mapper.GetSessionFactory ).OpenSession();
var tx = sess.BeginTransaction();
$ b $ 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(新的PhoneNumber {ThePhoneNumber =9});
jl.AddToPhoneNumbers(新的PhoneNumber {ThePhoneNumber =8});
jl.AddToPhoneNumbers(new PhoneNumber {ThePhoneNumber =6});
pm.AddToPhoneNumbers(new PhoneNumber {ThePhoneNumber =1});
sess.Save(pm);
sess.Save(jl);
tx.Commit();
$ b $ p
$ p 这就是我们的对象被持久化的方式:
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' )(((E'1'):: text),((113):: int4):((113):: int4))
INSERT INTO phone_number(the_phone_number,person_id,phone_number_id)VALUES ),((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):: i nt4))
INSERT INTO phone_number(the_phone_number,person_id,phone_number_id)VALUES(((E'6'):: text),((114):: int4),((317):: int4))
COMMIT
:
I have a simple model I'm attempting to persist using fluent-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; }
}
Some sample data:
var person = new Person() {Name = "Name1", Addresses = new[]
{
new Address { Street = "Street1"},
new Address { Street = "Street2"}
}};
When I call session.SaveOrUpdate(person)
both objects are persisted but the foreign key is not saved in the Address table:
What am I doing wrong? My mappings overrides are as follows:
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);
}
}
Please note, I plan on using List<Address>
in other entities and I don't want to add an Address.Person
property.
UPDATE 1
I've got this to 'work' by replacing Address.PersonId
with Address.Person
but I don't want the Address
to have a Person property, as I don't want that circular reference. Also, when inserting the above object looking at the logs nHibernate appears to 1) insert Person2) insert Address with NULL PersonId3) update Address with PersonId (when flushing)when really steps 2 & 3 can be done at the same time? This causes another issue if NULL is disallowed on Address.PersonId
UPDATE 2Removing the property Address.PersonId
results in PersonId
becoming populated in the database. nHibernate doesnt like me providing my own PersonId that its clearly using internally for inserting/retrieving records. So really I want to flag my Address.PersonId
with a 'hey this isnt a standalone field its the field you're going to use down the track please done treat it specially' flag. Also, as above, nHibernate appears to insert NULL into the PersonId column (when Save
ing) and THEN update it afterwards (when Flush
ing) ??
解决方案 I simulate your problem situation, the child with null parent key upon insert which then is updated later with the right parent key.
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
On absence of 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();
}
}
...it's the parent's responsibility to own the child entities.
That's why even you didn't indicate Inverse to the child (collection) and the child don't have any predefined parent, your child is seemingly able to persist itself properly...
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();
}
...,hence we can say that with the absence of Inverse attribute, our object graph's persistability is just a fluke; on a database with a good design, it's paramount that our data is consistent, that is it's a must that we should never indicate nullable on child's foreign keys, especially if that child is tightly coupled to parent. And in the scenario above, even if ThePhoneNumber "1" indicates Paul McCartney as its parent, John Lennon will later own that PhoneNumber, as it is included in John's children entities; this is the nature of not tagging the children entities with Inverse, a parent is aggressive to own all children entities that belong to it, even if the child wanted to belong to other parent. With no Inverse, the children don't have any rights to choose their own parent :-)
Take a look at the SQL log above to see this Main's output
Inverse
Then when indicating Inverse on child entities, it mean it's the child's responsibility to choose its own parent; parent entity will never meddle.
So given the same set of data on the method Main above, albeit with Inverse attribute on child entities...
HasMany(x => x.PhoneNumbers).Inverse().Cascade.All ();
..., John Lennon won't have any children, ThePhoneNumber "1" which choose its own parent(Paul McCartney) even that phone number is in John Lennon's children entities, it will still be persisted to database with Paul McCartney as its parent. Other phone numbers which didn't choose their parent, will remain parentless. With Inverse, a child can freely choose its own parent, there's no aggressive parent that can own anyone's child.
Back-end-wise, this is how the objects graph is persisted:
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
So what's a good practice for persisting root object and its children entities?
First, we could say that it is an oversight on Hibernate/NHibernate team's part on making the Inverse a non-default behavior. Most of us who regarded data consistency with utmost care, would never make foreign keys nullable. So, we should always explicitly indicate the Inverse as the default behavior.
Second, whenever we add a child entity to a parent, do this via parent's helper method. So even we forgot to indicate the child's parent, the helper method can explicitly own that child entity.
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);
}
}
This is how our object persistence routine shall look like:
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();
}
This is how our objects are persisted:
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
Another good analogy for Inverse: https://stackoverflow.com/a/1067854
这篇关于nhibernate不保存外键标识的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!