11

I have a very simple unidirectional mappings. see below:

    public ContactMap()
    {
        Id(x => x.Id).GeneratedBy.Assigned();
        Map(x => x.Name);
        References(x => x.Device);
        HasMany(x => x.Numbers)
            .Not.Inverse()
            .Not.KeyNullable()
            .Cascade.AllDeleteOrphan()
            .Not.LazyLoad()
            .Fetch.Subselect();
        Table("Contacts");
    }


    public PhoneNumberMap()
    {
        Id(x => x.Id).GeneratedBy.Native();
        Map(x => x.Number);
        Table("ContactNumbers");
    }

According to this post after nhibernate 3 and above, setting key as non-nullable should fix the insert-update issue (The issue when NHibernate issues an insert with foreign key set to null and then an update to update the foreign key to correct value), but this is not the case for me. When I set the key as not nullable, NHibernate issues a correct insert statement

INSERT INTO ContactNumbers
            (Number,
             ContactId)
VALUES      ('(212) 121-212' /* @p0 */,
             10 /* @p1 */);

As you can see, it inserts ContactId field, but after that, it still issues update statement

UPDATE ContactNumbers
SET    ContactId = 10 /* @p0 */
WHERE  Id = 34 /* @p1 */

So to clarify the problem. NHibernate inserts Contact row with foreign key assigned correctly and after that, it issues an update statement to update the foreign key (ContactId) which is redundant.

How can I get rid of this redundant update statement? Thanks.

BTW, I'm using latest version of NHibernate and Fluent NHibernate. The database is SQLite

Community
  • 1
  • 1
Davita
  • 8,928
  • 14
  • 67
  • 119

5 Answers5

25

You have to set "updatable"=false to your key to prevent update.

public ContactMap()
{
    Id(x => x.Id).GeneratedBy.Assigned();
    Map(x => x.Name);
    References(x => x.Device);
    HasMany(x => x.Numbers)
        .Not.Inverse()
        .Not.KeyNullable()
        .Not.KeyUpdate() // HERE IT IS
        .Cascade.AllDeleteOrphan()
        .Not.LazyLoad()
        .Fetch.Subselect();
    Table("Contacts");
}
hazzik
  • 13,019
  • 9
  • 47
  • 86
  • 2
    When all hopes were gone, you came :D. Simple, clear and works perfectly. It even replaced Update/Delete operation with single Delete. Damn, you don't know how grateful I am. This was an important issue that many is not aware of. Thanks hazzik. – Davita Jul 20 '12 at 09:03
  • 1
    Yea, we should update our documentation, but it is a huge and boring work:( – hazzik Jul 20 '12 at 09:16
  • I did not know about this and been using NHibernate for a while now! Nice one Hazzik – Rippo Jul 20 '12 at 10:42
4

You can't as of 3.2.0 BETA.

In v3.2.0 BETA an improvment to one-to-many introduced this anomaly to uni-directional one-to-many relationships (actually I am not sure if anormaly is what you would call this).

Before 3.2 you would need to set the foreign key to allow nulls for this type of relationship to work. So I would ignore the fact that this happens and just go with it. Otherwise you will need to change it to a fully bi-directional relationship.

  • [NH-941] - One-Many Requiring Nullable Foreign Keys

Release notes or JIRA issue

edit Also the answer to the post you point to is to fix save null-save-update rather than fixing the addtional update

Rippo
  • 22,117
  • 14
  • 78
  • 117
  • Damn, Nhibernate support for uni-directional associations sucks. I'll wait for other answers and accept yours if nothing good shows up. In the meantime I'll check EF + code first :( – Davita Jul 13 '12 at 15:40
3

I don't know if you really can get rid of it.

Try using another id generator as native. It forces NH to insert the record only to get the id. The id is used for every entity in the session, so it can't do the insert later. It may case subsequent updates. Use hi-lo or something similar.

Edit

Why aren't you using a component in this case? You don't need to map the phone number separately, if they consist only of a number. Something like this (I'm not a FNH user, so it may be wrong):

 public ContactMap()
 {
    Id(x => x.Id).GeneratedBy.Assigned();
    Map(x => x.Name);
    References(x => x.Device);
    HasMany(x => x.Numbers)
        .Not.Inverse()
        .Not.KeyNullable()
        .Cascade.AllDeleteOrphan()
        .Not.LazyLoad()
        .Fetch.Subselect()
        .Component(c =>
        {
          Map(x => x.Number);
        })
        .Table("ContactNumbers");
    Table("Contacts");
}
Stefan Steinegger
  • 63,782
  • 15
  • 129
  • 193
3

Try setting inverse to true on the mapping and assigning the relationship in code.

Inverse means that the child is responsible for holding the ID of the parent.

e.g.

var contact = new Contact();
var phoneNumber = new PhoneNumber();
phoneNumber.Contact = contact;

That way, when you do the insert for the PhoneNumber record, NH can insert the ContactId without having to do a separate update.

That's what I used to do in NH 2, I would assume the behaviour still works the same in 3.

Trevor Pilley
  • 16,156
  • 5
  • 44
  • 60
  • I don't want to do inverse, that's the whole idea. I want Contact to be responsible for numbers persistence. – Davita Jul 13 '12 at 15:26
1

It is what Trevor Pilley said. Use inverse="true". If you choose not to have inverse="true", this is the consequence of that choice. You can't have it both ways.

nhahtdh
  • 55,989
  • 15
  • 126
  • 162
Jeroen
  • 979
  • 1
  • 7
  • 16