Bi-directional relationship in nhibernate 4.0

2019-03-05 17:55发布

问题:

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

回答1:

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.



回答2:

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