NHibernate的不节能外键标识(nhibernate not saving foreign k

2019-07-29 04:17发布

我有一个简单的模型,我试图用流利,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.PersonIdAddress.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)??

Answer 1:

我模仿你的问题的情况下,与对插入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



Answer 2:

随着一点点的调整,以你原来的代码可以做到这一点,为了清楚我会后整个代码,

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");
        }
    }


Answer 3:

你PERSONID属性似乎映射和普通的属性,不能作为参考其他对象。 所以我建议你要做两件事情:

  1. 试着改变你的PERSONID属性为类型的人,并将其命名人
  2. 验证您的代码里面TRANSATION执行(它会影响如何NHibernate的与各协会的作品)
  3. 保存生成的automappings成XML文件,看看如何nhiberante实际上与你的样板工程


Answer 4:

您需要使用参考。 我不熟悉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"
};


Answer 5:

似乎是一个设计问题

如果目的是为了避免在上下文地址的使用的A / C类地址范围内的人参考

然后,我会介绍了“鉴别”

Address {AddressId, ...}
PersonAddress : Address {Person, ...}, 
CustomerAddress : Address {Customer, ...}, 
VendorAddress : Address {Vendor, ...}

你可以推断出通过上述公式识别器以及相当指定硬鉴别值

参考: 根据加盟性质鉴别

可替换地修改分贝结构如果允许/可能的

Persons [PersonId, ...]
Addresses [AddressId]
AddressDetails [AddressDetailId, AddressId, ...]

随着映射如下(不知道如何做到这一点的流畅进行,而是通过XML可能)1)通过一个人+地址通过加入参考表2)地址+ AddressDetails

我肯定会喜欢的第一个选项

欢呼声......



文章来源: nhibernate not saving foreign key Id