views:

938

answers:

2

In my class Case I have an IDictionary with Entity (a class) as key and Roles (an enum) as value. When trying to save a new instance (non-persisted) of Case, where the IDictionary is filled with new instances of Entity I get the following error:

NHibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing. Type: Entity

These are the classes (Roles is an enum):

public class Case
{
    public Case { EntityCollection = new Dictionary<Entity, Roles>(); }
    public virtual int Id { get; set; }
    public virtual IDictionary<Entity, Roles> EntityCollection { get; set; }
}

and

public class Entity
{
    public virtual int Id { get; set; }
}

And mapping is as follows:

<class name="Case" table="[Case]">
    <id name="Id" column="Id" type="Int32" unsaved-value="any">
        <generator class="hilo"/>
    </id>
    <map name="EntityCollection" table="CaseEntityRoles" 
     cascade="save-update" lazy="false" inverse="false">
        <key column="CaseId" />
        <index-many-to-many class="Entity" 
         column="EntityId" />
        <element column="Roles" type="Roles" not-null="true" />
    </map>
</class>

and

<class name="Entity" table="[Entity]">
    <id name="Id" column="Id" type="Int32" unsaved-value="0">
        <generator class="hilo"/>
    </id>
</class>

Sample of the test code:

[Test]
public void Can_add_new_case()
{
    var newCase = new Case();
    newCase.EntityCollection.Add(new Entity(), Roles.Role1);
    newCase.EntityCollection.Add(new Entity(), Roles.Role2);

    /* At which point I try to persist newCase and get an exception */
}

In the testcode the newCase-instance is persisted, but the new entities are not. I've tried a lot of different things, like adding a <version> tag to Entity and messing around with unsaved-value, but nothing seems to help. And as you can see from the mapping I do have cascade="save-update". Any ideas?

A: 

What happens if you set inverse to true ?

I dont know whether this wil solve your problem ...

What you could do, is make use of the Repository pattern, and create a 'CaseRepository' class. In that repository, you will have a Save method which would Save a given Case. In that save method, you could loop over all the entities for the given Case and explicitly call 'SaveOrUpdate' for each Entity.

I also wonder why you are using a Dictionary for this issue ?

Frederik Gheysels
If I set inverse to true I get no exception, but the entities still do not get saved. I actually do use a generic Repository class with a Save method, but I didn't want to write extra code for this if NHibernate was able to do it for me if I mapped it right. If I have no other choice, this will probably be what I have to do.I use Dictionary because I wanted only to save the same Entity once for each Case (which is why I use them as key). And the idea was that if one Entity has many roles I can just use a Flags-enum. If this becomes problematic I'll probably have to revise my solution.
Dodo
A: 

I think you need to persist the Entity objects that are referenced by the Case first, before you try to persist the Case. I had a similar issue that I solved this way. For example, using the Rhino NHRepository:

[Test]
public void Can_add_new_case()
{
    var newCase = new Case();
    var entity1 = new Entity();
    var entity2 = new Entity();
    newCase.EntityCollection.Add(entity1, Roles.Role1);
    newCase.EntityCollection.Add(entity2, Roles.Role2);

    Rhino.Commons.NHRepository<Entity> entityRepository = new NHRepository<Entity>();
    Rhino.Commons.NHRepository<Case> caseRepository = new NHRepository<Case>();

    using (UnitOfWork.Start())
    {
        entityRepository.SaveOrUpdate(entity1);
        entityRepository.SaveOrUpdate(entity2);
        caseRepository.SaveOrUpdate(newCase);
    }
}
Ogre Psalm33
Yes this is pretty much what I ended up doing as well. It's a shame that it is not automatically handled by NHibernate. If anyone know any other solutions (or know when this bug, if it is a bug, will be fixed) please do tell, but until then I guess I have to accept this as the necessary solution.
Dodo