0
votes

I have this error occurs when saving:
"object references an unsaved transient instance - save the transient instance before flushing or set cascade action for the property to something that would make it autosave."
Entities:

public class Division : EntityBase<int>
{
    public Division()
        : base()
    {
        DivisionType = new DivisionType();
        Employees = new List<Employee>();
        DivisionChildren = new List<Division>();
    }

    public virtual string DivisionName { get; set; }
    public virtual DivisionType DivisionType { get; set; }
    public virtual Division ParentDivision { get; set; }
    public virtual IList<Division> DivisionChildren { get; set; }
    public virtual IList<Employee> Employees { get; set; }
}

Mappings:

public class DivisionMap : ClassMap<Division>
{
    public DivisionMap()
    {
        Table("param.Division");

        Id(x => x.Id).Column("DivisionId");
        Map(x => x.DivisionName);
        References(x => x.DivisionType).Column("DivisionTypeId");
        References(x => x.ParentDivision).Column("ParentDivisionId");
        HasMany(x => x.DivisionChildren).KeyColumn("ParentDivisionId").Inverse().Cascade.AllDeleteOrphan();
        HasMany(x => x.Employees).KeyColumn("DivisionId").Inverse().Cascade.AllDeleteOrphan();
    }
}

As you can see, I have Divison with its parent and childs. When I call:

Session.SaveOrUpdate(entity);
NhUnitOfWork.Current.Commit();

The error exception above occurs!!
Where the problem is?

1

1 Answers

1
votes

In the constructor - there is new instance DivisionType created

 DivisionType = new DivisionType();

And the mapping does not handle its cascade anyhow:

References(x => x.DivisionType).Column("DivisionTypeId");

That is the cause of the exception. If you want to create the Type all-over-again... you can introduce the Cascade as well. But mostlikely you should bind it .. from list of existing types...

So, the not preferred solution (just in my thinking) would NOT be this:

References(x => x.DivisionType)
    .Column("DivisionTypeId");
    .Cascade.All();

But it will work.

The way I would suggest to go is to get the reference to existing DivisionType and assign it

entity.DivisionType = Session.Load<DivisionType>(typeId); // recieved from client
Session.SaveOrUpdate(entity);
NhUnitOfWork.Current.Commit();

NOTE: I would strongly suggest to change the initialization of your reference and collections - these are virtual properties and that means that they should not be created in constructor... convert the auto property into properties with private backing fields - to be initiated

see for example - CA2214: Do not call overridable methods in constructors

EXTEND: The exception says:

  1. object references ... (this is most likely the DivisionType)
  2. an unsaved transient instance .. (yes, the property DivisionType was created in constructor == it is transient instance)
  3. save the transient instance before flushing or set cascade action for the property to something that would make it autosave.
  4. Type: HR.Core.Data.Entities.Division ... (this is the type which does have the reference)

So, because the DivisionType is mapped, NHibernate does try to persist its id into column "DivisionTypeId".

But becuase the DivisionType does not have ID - it is a transient instance - the exception is thrown.

There are no issues with Parent/Child mapping

EXTEND MORE:

So now we know, that we also in some upper layer assign the entity.ParentDivision. The most suitable and appropriate way how to do that would be:

entity.ParentDivision = session.Load<Division>(parentDivisionId);

If the session is not part of the service (MVC controller) layer - we can create some DAO method e.g.:

public virtual Division GetById(id)
{
    var session = ... // ISessionFactory gets session
    return session.Get<Division>(id);
}

This is really very important when working with NHibernate. If we want NHibernate to do all the cascades, the references should be coming through NHibernate (Get or Load)...

Please do read this deep explanation:

NHibernate – The difference between Get, Load and querying by id