2012-04-27 3 views
5

У меня есть простая модель, я пытаюсь упорствовать с использованием свободно-NHibernate:NHibernate не сохранение внешнего ключа Id

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) оба объекта сохраняется, но внешний ключ не сохранен в адресной таблице:

enter image description here

Что я делаю неправильно? Мое Отображения переопределения следующим образом:

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 собственности.

UPDATE 1

У меня это «работа», заменив Address.PersonId с Address.Person, но я не хочу, чтобы Address иметь свойство Person, так как я не хочу, что циклическая ссылка. Кроме того, при вставке выше объекта, глядя на журналы NHibernate, как представляется, 1) вставить Person 2) вставить адрес с NULL PersonId 3) обновление адреса с помощью PersonId (при промывке) , когда на самом деле шаги 2 & 3 может быть сделано на в то же время? Это вызывает еще один вопрос, если NULL отвергается на Address.PersonId

UPDATE 2 вывоза имущества Address.PersonId результатов в PersonId становится заселена в базе данных. nHibernate не любит, когда я предоставляю свой собственный PersonId, что он явно использует внутренне для вставки/извлечения записей. Так что действительно хочу отметить мой Address.PersonId с «эй, это не отдельное поле, в поле, которое вы собираетесь использовать по треку, пожалуйста, обработайте его специально». Кроме того, как указано выше, nHibernate, кажется, вставляет NULL в столбец PersonId (когда Save ing), а THEN обновляет его после (когда Flush ing)?

+0

спасибо, я попробовал, что ранее без эффекта, то есть: 'mapping.HasMany (х => х .Addresses) .KeyColumn («PersonId») .Inverse() .Cascade.All(); ' – wal

+0

Вы пытались удалить« KeyColumn »и« Cascade », чтобы увидеть, нормально ли отображаются карты« HasMany »? – R0MANARMY

+0

@ R0MANARMY Да, если я это сделаю, то «Адрес» вообще не сохраняется – wal

ответ

0

Свойство PersonId, как представляется, является отображаемым и регулярным свойством, а не ссылкой на другой объект. Поэтому я хотел бы предложить вам попробовать две вещи:

  1. Попробуйте изменить PersonId свойство иметь тип лица и имя его лицо
  2. Убедитесь, что код выполняется в транзакцию (это может повлиять как NHibernate работы с ассоциации)
  3. Сохраните сгенерированные automappings в файлы XML и посмотреть, как на самом деле работает nhiberante с вашей моделью
+0

'Попробуйте изменить ваше свойство PersonId на тип Person и назовите его Person' Я специально сказал, что не хотел этого делать. – wal

0

Вы должны использовать ссылки.Я не знаком с 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; }     
} 

Обратите внимание, что мы Ждем» t используйте public virtual int QuestionId { get; set; } на классе Ответ, мы должны использовать вместо этого public virtual Question Question { get; set; }. Таким образом, больше ООП, в основном ясно, как выглядит модель вашего домена, свободная от того, как объекты должны относиться друг к другу (не по int, а не по строкам и т. Д., А по объектным ссылкам)

To allay ваши заботы, загрузка объекта (через session.Load) не берут на себя базу данных в оба конца.

var answer = new Answer { 
    // session.Load does not make database request 
    Question = session.Load<Question>(primaryKeyValueHere), 

    TheAnswer = "42" 
}; 
+0

'Обратите внимание, что я планирую использовать Список

в других объектах, и я не хочу добавлять адрес. Персонифицированное свойство – wal

+0

в качестве его сериализации. – wal

+0

Хм .. это странное требование, но ваш дизайн (на основе вашего скриншот) указывает, что таблица адресов тесно связана с таблицей Person –

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 

Об отсутствии обратных ...

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

..., поэтому мы можем сказать, что с отсутствием обратного атрибута постоянство нашего объектного графика - всего лишь случайность; в базе данных с хорошим дизайном важно, чтобы наши данные были согласованными, то есть необходимо, чтобы мы никогда не указывали на значение NULL для внешних ключей дочерних элементов, особенно если этот ребенок тесно связан с родителем. И в приведенном выше сценарии, даже если ThePhoneNumber «1» указывает на Пола Маккартни в качестве его родителя, Джон Леннон позже будет владеть этим PhoneNumber, так как он включен в детские сущности Джона; это характер не пометки дочерних объектов с помощью инверсии, родитель агрессивен, чтобы владеть всеми дочерними объектами, принадлежащими ему, даже если ребенок хочет принадлежать другому родителю. При отсутствии Inverse, дети не имеют права выбирать свой собственный родитель :-)

Посмотрите на журнал SQL выше, чтобы увидеть результат этой главной в


Inverse

Затем, указывая на обратные дочерние объекты, это означает, что ребенок обязан выбрать своего родителя; родительский объект никогда не вмешивается.

Поэтому, учитывая тот же набор данных о методе Main выше, хотя и с обратным атрибутом дочерних сущностей ...

HasMany(x => x.PhoneNumbers).Inverse().Cascade.All(); 

... Джон Леннон не будет иметь детей, ThePhoneNumber «1 «которые выбирают своего родителя (Пол Маккартни), даже если этот номер телефона находится в дочерних подразделениях Джона Леннона, он по-прежнему будет храниться в базе данных с Полом Маккартни в качестве его родителя.Другие номера телефонов, которые не выбрали родителя, останутся без родителей. С помощью Inverse ребенок может свободно выбирать своего родителя, нет агрессивного родителя, который может владеть чьим-либо ребенком.

Back-конец-накрест, это как упорствовал объекты графа:

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 как поведение по умолчанию.

Во-вторых, всякий раз, когда мы добавляем дочерний объект к родительскому объекту, выполняйте это с помощью вспомогательного метода родителя. Поэтому даже мы забыли указать родителя ребенка, вспомогательный метод может явно содержать этот дочерний объект.

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

+0

Я знаю, это хорошая политика StackExchange для написания комментариев, подобных этому, но это один из лучших и наиболее полных и полезных ответов, которые я когда-либо видел в StackOverflow. Спасибо, Майкл. –

1

С небольшой подстройкой к исходному коду, это может быть достигнуто, для ясности я буду размещать весь код,

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

спасибо, я проверю это и вернусь – wal

+0

, это не работает для меня. 'Address.PersonId' не задается при получении« Лица »из базы данных. Добавление к моему коду добавляло отображение «HasOne». См. Мой проект по шипу здесь http://wallaceturner.com/files/FluentNHibernateSubClassTest.zip, который вы можете просто запустить с консоли. (принимает базу данных) – wal

+0

Я обновил ответ, добавив map.Map (x => x.PersonId) .Column ("PersonId"); должен заставить работать, но вам нужно избавиться от внешнего ключа в db, чтобы это работало, если оно у вас есть. –

0

Кажется, проблема разработки

Если намерение состоит в том, чтобы избежать ссылок на человек в течение адреса на/с контекстуального использованием адреса

Тогда я бы ввести «дискриминатор»

т.е.

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

Вы можете сделать вывод, дискриминатор с помощью формулы, а скорее с указанием жесткого значение, дискриминатор

Ref: Discriminator based on joined property

В качестве альтернативы изменения структуры БД, если допускается/возможно

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

С Сопоставление следующим образом (не знаю, как это можно было бы сделать свободно, но возможно через xml) 1.) Лица + Адреса через таблицу соединений 2.) Адрес + AddressDetails через ссылку

Я определенно предпочитаю первый вариант

веселит ...

Смежные вопросы