NHibernate with mapping by code and SQLite database: saving one-two-one descendant objects-descendants, the child element gets a foreign foreign key

Variations of this question have been asked and answered many times, and the answers have many overlapping details. I tried so many different things suggested by these answers, but none of them worked in my case.

I have a SQLite database with a parent table and a child table. This is a really easy setup. I am using NHibernate 4.0.4 with code mapping, and not free, as I was suggested, so that the former is newer and better than the latter .

ENTITIES:

public class BillingItem { public virtual int ID { get; set; } public virtual string Name { get; set; } // ... other properties public virtual ICollection<PaymentItem> PaymentItems { get; set; } public BillingItem() { PaymentItems = new List<PaymentItem>(); } } public class PaymentItem { public virtual int ID { get; set; } public virtual BillingItem OwningBillingItem { get; set; } // ... other properties } 

BillingItem MAPPING:

 public class BillingItemMapping : ClassMapping<BillingItem> { public BillingItemMapping() { Table("BillingItems"); Lazy(true); Id(x => x.ID, map => map.Generator(Generators.Identity)); Set(x => x.PaymentItems, c => { c.Key(k => { k.Column("ID"); k.ForeignKey("BillingItemID"); }); c.Inverse(true); c.Cascade(Cascade.None); }, r => r.OneToMany(o => { })); Property(x => x.Name); // ... other properties } } 

PaymentItem mapping:

 public class PaymentItemMapping : ClassMapping<PaymentItem> { public PaymentItemMapping() { Table("PaymentItems"); Lazy(true); Id(x => x.ID, map => map.Generator(Generators.Identity)); ManyToOne(x => x.OwningBillingItem, m => { m.Column("ID"); m.Update(false); m.Insert(false); m.Cascade(Cascade.None); m.Fetch(FetchKind.Join); m.NotFound(NotFoundMode.Exception); m.Lazy(LazyRelation.Proxy); m.ForeignKey("BillingItemID"); }); Property(x => x.DueDate, map => map.NotNullable(true)); // ... other properties. } } 

WAREHOUSE:

 public void Add(BillingItem toAdd) { using (ISession session = Helpers.NHibernateHelper.OpenSession()) using (ITransaction tran = session.BeginTransaction()) { session.Save(toAdd); foreach (var pi in toAdd.PaymentItems) { session.Save(pi); } tran.Commit(); } } 

BUSINESS LOGIC:

 var bi = new BillingItem() { Name = Guid.NewGuid().ToString(), // ... others.. }; var pi = new PaymentItem() { OwningBillingItem = bi, DueDate = DateTime.Now.AddDays(3) // ... others.. }; bi.PaymentItems.Add(pi); var repo = new Repository(); repo.Add(bi); 

As suggested by this answer (and this and this and many others), I set Inverse(true) in my Set ( BillingItemMapping collection) to BillingItemMapping . I also set my bidirectional links in the PaymentItem object:

 OwningBillingItem = bi 

and BillingItem object:

 bi.PaymentItems.Add(pi); 

I feel like I'm setting up everything else as it should, and I was busy with a lot of display settings based on suggestions from other sources. However, I just cannot understand why it is not working.

The problem is that I cannot get the foreign key column in the PaymentItem record to store the identifier from BillingItem . If I set the column to not allow NULL (as it should be), I get an exception with a null constraint. If I set it to allow null (for testing), it just gets null (obviously).

What am I doing wrong?

+5
source share
2 answers

Hehe, something is wrong with your PaymentItemMapping , the correct display should be like this:

 public class PaymentItemMapping : ClassMapping<PaymentItem> { public PaymentItemMapping() { Table("PaymentItems"); Lazy(true); Id(x => x.ID, map => map.Generator(Generators.Identity)); ManyToOne(x => x.OwningBillingItem, m => { //Do not map to m.Column("ID"); m.Column("BillingItemID"); // BillingItemID can be insert and update m.Update(true); m.Insert(true); m.Cascade(Cascade.None); m.Fetch(FetchKind.Join); m.NotFound(NotFoundMode.Exception); m.Lazy(LazyRelation.Proxy); m.ForeignKey("BillingItemID"); }); Property(x => x.DueDate, map => map.NotNullable(true)); // ... other properties. } } 
+2
source

One part of the display that seems wrong is column set

 // BillingItemMapping() Set(x => x.PaymentItems, c => { // this should refer to column where parent id will be found c.Key(k => { k.Column("ID"); k.ForeignKey("BillingItemID"); }); 

therefore he must be

  c.Key(k => { k.Column("BillingItemID"); }); 

The foreign key is for sql generators only .. it may be skipped now

In addition, for each collection I use cascading

 Set(x => x.PaymentItems, c => { c.Key(k => { k.Column("BillingItemID"); }); c.Inverse(true); //c.Cascade(Cascade.None); c.Cascade(Cascade.All); }, 

With this, we can simplify the challenge to keep that all

 using (ISession session = Helpers.NHibernateHelper.OpenSession()) using (ITransaction tran = session.BeginTransaction()) { session.Save(toAdd); //foreach (var pi in toAdd.PaymentItems) //{ // session.Save(pi); //} tran.Commit(); } 

(we still need to keep both parent-parent-parent-parent-parent)

And finally, when we have a cascade in place, we MUST let NHibernate do its work - for INSERT and UPDATE, which is the ratio

 ManyToOne(x => x.OwningBillingItem, m => { m.Column("ID"); // this is stopper - DO NOT use it //m.Update(false); //m.Insert(false); 

So, do not install Update(false) and Insert(false) if we want to be updated and inserted. That should solve everything ..

This may also get some idea:

Minimal and Correct One-to-Many Mapping with NHibernate

+1
source

All Articles