4
votes

I am trying to achieve a HasMany mapping with a non-nullable key column.

My mapping looks like this:

public void Override(AutoMapping<DataExportTable> mapping)
{
    mapping.Table("data_export_tables");
    mapping.Id(x => x.Id).Column("data_export_tables_id")
           .GeneratedBy.Sequence("SQ_DATA_EXPORT_TABLES_ID");
    mapping.HasMany(x => x.Columns)
           .KeyColumn("table_id")
           .Not.KeyNullable()
           .Not.KeyUpdate()
           .Cascade.AllDeleteOrphan();
}

The code to save the entity looks like this:

var table = new DataExportTable("table_name");
table.Columns.Add(new DataExportColumn("column_name"));
session.Save(table);
transaction.Commit();

This throws an exception:

NHibernate.Exceptions.GenericADOException: could not execute batch command.[SQL: SQL not available] ---> Oracle.DataAccess.Client.Orac leException: ORA-01400: cannot insert NULL into ("MYSCHEMA"."DATA_EXPORT_COLUMNS"."TABLE_ID")

Now, I read quite a few posts about this topic and the generally accepted solution seems to be to make the reference bi-directional, i.e. adding a Table property to DataExportColumn and setting that.
Another "hack" is to make the foreign key column nullable. That's not what I want. I want NHibernate to insert the ID directly in the INSERT statement.
According to the log file, NHibernate certainly knows the ID at the time it executes the INSERT statement:

NHibernate: select SQ_DATA_EXPORT_TABLES_ID.nextval from dual
NHibernate: Batch commands:
command 0:INSERT INTO data_export_tables (NAME, data_export_tables_id) VALUES (:p0, :p1);:p0 = 'table_name' [Type: String (0)], :p1 = 93 [Type: Int32 (0)]

NHibernate: Batch commands:
command 0:INSERT INTO data_export_columns (NAME, data_export_columns_id) VALUES (:p0, :p1);:p0 = 'column_name' [Type: String (0)], :p1 = 1228 [Type: Int32 (0)]

As you can see, NHibernate simply ommits the table_id column, although the ID is well known - NHibernate passed it as a parameter to the first insert statement...

2

2 Answers

2
votes

So close, you need to set Not.Inverse() as shown in this answer.

mapping.HasMany(x => x.Columns)
       .KeyColumn("table_id")
       .Not.Inverse()
       .Not.KeyNullable()
       .Not.KeyUpdate()
       .Cascade.AllDeleteOrphan();

I don't consider making the relationship bidirectional to be a hack and it's usually necessary for querying.

2
votes

You did not mention which version of NHibernate you're using - but I'll note that this appears to have been fixed in version 3.2.0 according to this jira issue.

If you're stuck with another version of NHibernate, I'd suggest making the relationship bidirectional, as it is a MUCH better alternative to using a nullable FK in my opinion.