0

I have a code that was working perfectly on NHibernate 3.1, but when it is not working on NHibernate 4.0

So, this is the class relations

public class Employee : BaseEntity
{
    ...
    public Department Dept { get; set; }
}

public class Department : BaseEntity
{
    ...
    public IList<Employee> Employees { get; set; }
}

and for the mapping we have this

DepartmentMap : ClassMap<Department>
{
     Table("....");
     HasMany(x => x.Employees).KeyColumn("DeptId").Not.KeyNullable();
}


EmployeeMap : ClassMap<Employee>
{
     Reference(x => x.Dept).Column("DeptId");
}

and when I am adding an employee like that

var dept = session.Load<Department>(deptId);
newEmployee.Dept = dept;
session.Save(newEmployee);

But it is throwing an error:

NHibernate.PropertyValueException: not-null property references a null or transient value

I read that I have to add the relation in two ways, so I modified it to this

var dept = session.Load<Department>(deptId);
newEmployee.Dept = dept;
dept.Employees.Add(newEmployee);
session.Save(newEmployee);

But now I have this error :

NHibernate.PropertyValueException: Error dehydrating property value for... System.IndexOutOfRangeException: Invalid index 7 for this SqlParameterCollection with Count=7.

So, I want to know how to fix it and where I can read about the changes in NHibernate about this with bi-direction

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
Ghassan Karwchan
  • 3,355
  • 7
  • 37
  • 65

2 Answers2

0

First issue - bi-directional mapping

We should always assign both sides of relation. Well, always?!? This is the perferred and good practice.

But in case, that we want to use .Load() to retrieve parent(Department) - we should not need assign parent.Chidren.Add().

The Load() is smart way how to get "fake" (proxy) instance, represented just by ID of that entity (engough to create proper INSERT statement)

So, in this case we should avoid dept.Employees.Add(newEmployee); - we do not need to load Department at all

// existing parent - but NHibernate just creates a proxy with expected ID
var dept = session.Load<Department>(deptId);
// child Employee is provided with parent reference - it is enough
newEmployee.Dept = dept;
// this will not help, just will execute SELECT - no benefit
// dept.Employees.Add(newEmployee);
// save and it should work
session.Save(newEmployee);

Second (real) issue - double mapping

While it is not visible in the question code snippets, I would bet, that Employee in fact has this def and mapping

public class Employee : BaseEntity
{
    ...
    // reference
    public virtual Department Dept { get; set; }
    // reference_ID also mapped as integer
    public virtual int? DeptId { get; set; }
}

EmployeeMap : ClassMap<Employee>
{
     Reference(x => x.Dept).Column("DeptId");
     Map(x => x.DeptId)
         // with .Not.Nullable() by code or convention
         ; 
}

So - we have two properties in C# which belongs to one SQL Column. I would say, it is absolutely ok, but we have to be sure that we adjust that a bit. The integer should be nullable and MUST be readonly

EmployeeMap : ClassMap<Employee>
{
     Reference(x => x.Dept).Column("DeptId");
     Map(x => x.DeptId)
        .Nullable()
        .Insert(false)
        .Update(false); // as far as I rememeber syntax to replicate
      // <property name="DeptId" insert="false" update="false" not-null="false />
}

So what happened? what are both exceptions experienced aboout?

NHibernate.PropertyValueException: not-null property references a null or transient value

the above issue means NHibernate expected property DeptId to be also provided

NHibernate.PropertyValueException: Error dehydrating property value for... System.IndexOutOfRangeException: Invalid index 7 for this SqlParameterCollection with Count=7.

this kind of issue is usually about doubled mapping - two properties to ONE column. Most often becuase of Reference and reference id being mapped to one column...

Even if the issue is not directly with int? DeptId - I hope that this give you enough information to reveal the real culprit.

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
  • Thanks a lot for your answer. It gives me some info about what is going on. but that was not the problem. I didn't have double column mapping and i didn't have that int? DeptId. My problem was fixed by adding Inverse keyword. Which I read that i was supposed to be there, but somehow in 3.1 was forgiving , but in 4.0 it errors out – Ghassan Karwchan May 08 '15 at 14:32
  • Just a note: none of the exceptions you've experienced is related to inverse mapping. And in fact, inverse mapping (on Department side) is not anyhow related to scenario when you save Employee with `many-to-one` reference to such Department. My explanation is really related to issues you've mentioned in the query. But I am glad that youv've found the way...Enjoy NHibernate – Radim Köhler May 10 '15 at 10:22
0

I found the fix for the problem: The fix for the problem is to add Inverse to the mapping of the parent file. So, the mapping for Department will be:

DepartmentMap : ClassMap<Department>
{
     Table("....");
     HasMany(x => x.Employees).KeyColumn("DeptId").Inverse().Not.KeyNullable();
}
Ghassan Karwchan
  • 3,355
  • 7
  • 37
  • 65