nick
nick

Reputation: 57

NHibernate one-to-many could not insert null key

I'm trying to use NHibernate. I have these following tables:

    CREATE TABLE person(person_id INTEGER IDENTITY(1,1) NOT NULL,  
    person_first_name VARCHAR(55) NOT NULL,  
    person_last_name VARCHAR(55) NULL,   
    person_contacted_number INTEGER NOT NULL, 
    person_date_last_contacted DATETIME NOT NULL,
    person_date_added DATETIME NOT NULL,  
    CONSTRAINT PK_person PRIMARY KEY (person_id));

    CREATE TABLE person_order(order_id INTEGER IDENTITY(1,1) NOT NULL,  
    order_person_id INT NOT NULL,  
    CONSTRAINT PK_person PRIMARY KEY (order_id)),
    CONSTRAINT FK_person_order_person FOREIGN KEY(order_person_id)
    REFERENCES person (person_id);

Classes:

    public class Person
    {
        public virtual int person_id { get; set; }
        public virtual String person_first_name { get; set; }
        public virtual String person_last_name { get; set; }
        public virtual int person_contacted_number { get; set; }
        public virtual DateTime person_date_last_contacted { get; set; }
        public virtual DateTime person_date_added { get; set; }
        public virtual ISet<PersonOrder> person_orders { get; set; }
    }
    public class PersonOrder
    {
        public virtual int order_id { get; set; }
        public virtual int order_person_id { get; set; }
        public virtual Person Person { get; set; }
    }

hbm.xml:

  <class name="Person" table="`person`">
        <id name="person_id">
          <generator class="native"/>
        </id>
        <property name="person_first_name" />
        <property name="person_last_name" />
        <property name="person_contacted_number" />
        <property name="person_date_last_contacted" />
        <property name="person_date_added" type="LocalDateTime" />
        <set name="person_orders" table="`person_order`" cascade="all-delete-orphan" inverse="true">
          <key column="order_person_id"/>
          <one-to-many class="PersonOrder"/>
        </set>
  </class>

  <class name="PersonOrder" table="`person_order`">
     <id name="order_id">
      <generator class="native"/>
    </id>
    <many-to-one name="Person" column="order_person_id" cascade="save-update" />
  </class>

I want to save Person to database (cascade):

var newPerson = CreatePerson(); // return new Person
newPerson.person_orders = new HashedSet<PersonOrder> {new PersonOrder()};
session.Save(newPerson);

And have a error could not insert:

[TestConsoleApplication.PersonOrder][SQL: INSERT INTO [person_order] (order_person_id) VALUES (?); select SCOPE_IDENTITY()]

I think it's not correct one-to-many mapping for class. How to resolve this?

Upvotes: 3

Views: 2049

Answers (1)

Radim K&#246;hler
Radim K&#246;hler

Reputation: 123861

I would say, that the most suspected issue could be missing parent/Person setting on the Order itself.

Once we are not using cascading, NHibernate must know all the settings, independently. Other words, this is not enough *(simplified example, expecting IList instead of IEnumerable for person_orders)*:

var newPerson = CreatePerson(); // return new Person
newPerson.person_orders = new List<PersonOrder>();
// WRONG assignment
newPerson.person_orders.Add(new PersonOrder()); // first order
newPerson.person_orders.Add(new PersonOrder()); // second order

In this case, our new PersonOrder does not know about the Person, the reference. So we have to be sure, that it is assigned both ways (good practice anyhow):

// improve it
var order1 = new PersonOrder
{
    Person = newPerson,
};
var order2 = new PersonOrder
{
    Person = newPerson,
};
newPerson.person_orders.Add(order1); // first order
newPerson.person_orders.Add(order2); // second order

Once this is assured, then after session.Save(newPerson) and session save all orders ... the correct PersonId will be sent.

NOTE: Try to think about using the cascades, and marking the <set> with inverse="true" attribute. Honestly cannot remember scenario where not used this approach

EDIT: a bit different mapping I would suggest to change the mapping types. Instead of iesi ISet (which is really good if we need uniqueness) and the <set>, into standard IList<> and the bag mapping. I.e.:

Hbm like this :

<bag name="person_orders" table="`person_order`" 
     cascade="all-delete-orphan" inverse="true">
      <key column="order_person_id"/>
      <one-to-many class="PersonOrder"/>
</bag>

the C# like this

public virtual IList person_orders { get; set; }

And the insertion code like this:

var newPerson = CreatePerson(); // return new Person
var order = new PersonOrder
{
    Person = newPerson,
};
newPerson.person_orders = new List<PersonOrder> {order};
session.Save(newPerson);

This will work (I am almost sure, because I just reproduced as similar as possible)

Upvotes: 1

Related Questions