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
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.
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();
}