5

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

いくつかのサンプルデータ:

var person = new Person() {Name = "Name1", Addresses = new[]
    {
        new Address { Street = "Street1"},
        new Address { Street = "Street2"}
    }};

session.SaveOrUpdate(person)両方のオブジェクトを呼び出すと、永続化されますが、外部キーは Address テーブルに保存されません。

ここに画像の説明を入力

私は何を間違っていますか?私のマッピングのオーバーライドは次のとおりです。

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、循環参照が必要ないため、 Person プロパティを持たせたくありません。また、ログを参照して上記のオブジェクトを挿入すると、nHibernate は 1) Person を挿入 2) NULL PersonId で Address を挿入 3) PersonId で Address を更新 (フラッシュ時) のように見えますが、実際にはステップ 2 と 3 を同時に実行できますか? Address.PersonId で NULL が許可されていない場合、これにより別の問題が発生します。

UPDATE 2Address.PersonIdプロパティを 削除するPersonIdと、データベースに入力されます。nHibernate は、レコードの挿入/取得のために内部で明確に使用する独自の PersonId を提供することを好みません。Address.PersonIdですから、「ねえ、これはスタンドアローンのフィールドではなく、トラックで使用するフィールドなので、特別に扱ってください」というフラグを付けたいと思います。また、上記のように、nHibernate は PersonId 列に NULL を挿入し ( Saveing の場合)、後で更新するように見えます (ing の場合Flush) ??

4

5 に答える 5

10

私はあなたの問題の状況をシミュレートします。挿入時に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();            
}

...したがって、逆属性がない場合、オブジェクトグラフの永続性は単なるまぐれであると言えます。優れた設計のデータベースでは、データに一貫性を持たせることが最も重要です。つまり、特にその子が親に緊密に結合されている場合は、子の外部キーでnull許容を示さないようにする必要があります。上記のシナリオでは、ThePhoneNumber "1"がPaulMcCartneyを親として示している場合でも、John Lennonは、Johnの子エンティティに含まれているため、後でそのPhoneNumberを所有します。これは、子エンティティにInverseのタグを付けないという性質であり、子が他の親に属したい場合でも、親はそれに属するすべての子エンティティを積極的に所有します。インバースがない場合、子供には自分の親を選択する権利がありません:-)

上記のSQLログを見て、このメインの出力を確認してください


次に、子エンティティで逆を示す場合、それは自分の親を選択するのは子の責任であることを意味します。親エンティティが干渉することはありません。

したがって、子エンティティにInverse属性がありますが、上記のメソッドMainで同じデータセットが与えられます。

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

...、John Lennonには子がなく、電話番号がJohn Lennonの子エンティティにある場合でも、自身の親(Paul McCartney)を選択するThePhoneNumber "1"は、PaulMcCartneyを親としてデータベースに保持されます。 。親を選択しなかった他の電話番号は、親がないままになります。インバースを使用すると、子供は自分の親を自由に選択できます。誰の子供も所有できる攻撃的な親はありません。

バックエンドに関しては、これはオブジェクトグラフが永続化される方法です。

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

では、ルートオブジェクトとその子エンティティを永続化するための良い習慣は何ですか?

まず、Inverseをデフォルト以外の動作にすることに関するHibernate/NHibernateチームの見落としであると言えます。データの整合性を細心の注意を払って考えた私たちのほとんどは、外部キーをnull許容にすることは決してありません。したがって、デフォルトの動作として常に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

インバースのもう1つの良い例え:https ://stackoverflow.com/a/1067854

于 2012-04-29T17:04:58.273 に答える
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");
        }
    }
于 2012-05-01T10:46:43.650 に答える
0

あなたの PersonId プロパティは、他のオブジェクトへの参照としてではなく、マップされた通常のプロパティのようです。そのため、次の 2 つのことを試してみることをお勧めします。

  1. PersonId プロパティを Person タイプに変更し、Person という名前を付けてみてください
  2. コードがトランザクション内で実行されることを確認します (nhibernate が関連付けでどのように動作するかに影響する可能性があります)。
  3. 生成された自動マッピングを XML ファイルに保存し、nhiberante が実際にモデルでどのように機能するかを確認します
于 2012-04-27T07:14:13.013 に答える
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; }                
}

public virtual int QuestionId { get; set; }Answer クラスで使用しなかったことに注意してください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"
};
于 2012-04-29T04:43:06.790 に答える
-1

設計上の問題のようです

意図が、アドレスのコンテキスト使用の a/c でアドレス内の人物の参照を避けることである場合

次に、「差別者」を紹介します

すなわち

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

ハード識別子の値を指定するのではなく、式を介して識別子を推測することもできます

参照 :結合されたプロパティに基づく識別器

または、可能であればデータベース構造を変更します

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

次のようなマッピングを使用します (これがどのように流暢に行われるかはわかりませんが、xml を介して可能です) 1.) 結合テーブルを介した Person + Addresses 2.) 参照を介した Address + AddressDetails

私は間違いなく最初のオプションを好むでしょう

乾杯 ...

于 2012-05-05T11:26:57.483 に答える