I think this is a bug. Basically if I use index-many-to-many or index-many-to-any to map an entity key of a dictionary this key is not reassociated with a new session through an update() even though I specify cascade = save-update on the collection. This then becomes a problem when the same object is retrieved in the new session because a lazy reference to it is resolved. It isn't in the PersistenceContext (because the cascade never occured on load of the dictionary) so a new instance is created. Should I report this to Jira?
Hibernate version: Local Build from nhibernate\src\NHibernate source at revision 3589. Update I have also just replicated this using 2.0.0GA. Same thing happens
Mapping documents:
Code:
<class name="Project" table="Project" lazy="false">
<id name="Name" type="String">
<column name="Name"/>
<generator class="assigned" />
</id>
<map name="nodeValue" table="NodeValue" access="field" cascade="save-update" lazy="false">
<key column="id"/>
<index-many-to-many column="Node" class="Node"/>
<element column="aNodeValue" type="double"/>
</map>
<many-to-one name="aScenario" column="Scenario" access="field" cascade="all"/>
</class>
<class name="Scenario" table="Scenario" lazy="true">
<id type="Int32" name="id">
<column name="ScenarioID"/>
<generator class="increment" />
</id>
<property name="Name">
<column name="Name"/>
</property>
<list name="Nodes" cascade="all">
<key column="NodeListID"/>
<index column ="NodeListIndex"/>
<one-to-many class="Node"/>
</list>
</class>
<class name="NHibernateSandbox.Node" table="Node" lazy="false">
<id type="Int32" name="id">
<column name="NodeIdentifier"/>
<generator class="increment" />
</id>
<property name="Name">
<column name="Name"/>
</property>
</class>
Code between sessionFactory.openSession() and session.close():Code:
//Open a session and transaction
ISession session = sf.OpenSession( cnn );
ITransaction tx = session.BeginTransaction( );
//Load the project
//The scenario is lazy loaded
//nodeA is loaded as part of the project dictionary nodeValue
project = session.Get<Project>( "Test" );
//Commit the transaction and cloase the session
tx.Commit( );
session.Close( );
//Open a new session and transaction
session = sf.OpenSession( cnn );
tx = session.BeginTransaction( );
//Cascade (as specified in xml mapping files) through the detached object
//reassociate objects and save any changes back to the database
session.Update( project );
//Get the key out of the dictionary
IEnumerator enumer;
enumer = project.nodeValue.Keys.GetEnumerator( );
enumer.MoveNext( );
//Compare the key which is nodeA to the first item in the scenario Nodes list
//This list is lazy load and loads when this comparison takes place.
//Even though the id's of nodeA are the same the object instances are different
//This appears to be because nodeA was not added to the PersistentContext during update
//If nodeA is stored in project as a property or in a list it is correctly added to
//the dictionary. This error only occurs when nodeA is a key in a dictionary mapped
//with index-many-to-many or index-many-to-any
if( enumer.Current != project.aScenario.Nodes[0] )
MessageBox.Show( "Key Node and scenario Node should be the same instance" );
tx.Commit( );
session.Close( );
Name and version of the database you are using: Firebird 2.1.0 The generated SQL:Code:
create table Scenario (ScenarioID INTEGER not null, Name VARCHAR(255), primary key (ScenarioID))
create table Project (Name VARCHAR(255) not null, Scenario INTEGER, primary key (Name))
create table NodeValue (id VARCHAR(255) not null, aNodeValue DOUBLE PRECISION, Node INTEGER not null, primary key (id, Node))
create table Node (NodeIdentifier INTEGER not null, Name VARCHAR(255), NodeListID INTEGER, NodeListIndex INTEGER, primary key (NodeIdentifier))
alter table Project add constraint FKCFC6D85A757B75B4 foreign key (Scenario) references Scenario
alter table NodeValue add constraint FK3085E2DC77DA1F6B foreign key (id) references Project
alter table NodeValue add constraint FK3085E2DC7F85E57B foreign key (Node) references Node
alter table Node add constraint FK76534CD8F176736F foreign key (NodeListID) references Scenario
NHibernate: INSERT INTO Scenario (Name, ScenarioID) VALUES (@p0, @p1); @p0 = '', @p1 = '1'
NHibernate: INSERT INTO Node (Name, NodeIdentifier) VALUES (@p0, @p1); @p0 = '', @p1 = '1'
NHibernate: INSERT INTO Project (Scenario, Name) VALUES (@p0, @p1); @p0 = '1', @p1 = 'Test'
NHibernate: UPDATE Node SET NodeListID = @p0, NodeListIndex = @p1 WHERE NodeIdentifier = @p2; @p0 = '1', @p1 = '0', @p2 = '1'
NHibernate: INSERT INTO NodeValue (id, Node, aNodeValue) VALUES (@p0, @p1, @p2); @p0 = 'Test', @p1 = '1', @p2 = '1'
NHibernate: SELECT project0_.Name as Name1_0_, project0_.Scenario as Scenario1_0_ FROM Project project0_ WHERE project0_.Name=@p0; @p0 = 'Test'
NHibernate: SELECT nodevalue0_.id as id0_, nodevalue0_.aNodeValue as aNodeValue0_, nodevalue0_.Node as Node0_ FROM NodeValue nodevalue0_ WHERE nodevalue0_.id=@p0; @p0 = 'Test'
NHibernate: SELECT node0_.NodeIdentifier as NodeIden1_3_0_, node0_.Name as Name3_0_ FROM Node node0_ WHERE node0_.NodeIdentifier=@p0; @p0 = '1'
NHibernate: SELECT scenario0_.ScenarioID as ScenarioID0_0_, scenario0_.Name as Name0_0_ FROM Scenario scenario0_ WHERE scenario0_.ScenarioID=@p0; @p0 = '1'
NHibernate: SELECT nodes0_.NodeListID as NodeListID1_, nodes0_.NodeIdentifier as NodeIden1_1_, nodes0_.NodeListIndex as NodeList4_1_, nodes0_.NodeIdentifier as NodeIden1_3_0_, nodes0_.Name as Name3_0_ FROM Node nodes0_ WHERE nodes0_.NodeListID=@p0; @p0 = '1'
NHibernate: UPDATE Project SET Scenario = @p0 WHERE Name = @p1; @p0 = '1', @p1 = 'Test'