views:

57

answers:

3

Each user has a list of roles:

  <class name="User" lazy="false" table="Users">
    <id name="Id" type="int">
      <generator class="native"/>
    </id>

    <property name="Name" />

    <bag name="RoleList" table="User_Role" inverse="true" lazy="false" collection-type="Roles">
      <key column="UserId" foreign-key="Id"/>
      <many-to-many class="Role" column="RoleId"/>
    </bag>

  </class>

I am tracking all the queries to the SQL server. When I select user, I also see the SQL SELECT statement (which is Ok). The problem is when I am trying to update an existing user (that has roles): I see only an update to User table, but not to User_Role (which is bad).

To clarify: I don't expect inserts/updates in Role table (but in User_Role, since the roles are "permanent" and can be attached and detached freely.

Why select works correctly, but not update. please? If anymore info is needed - just ask - I'll try to answer all the secondary questions.

Update: the Role mapping:

  <class name="Role" lazy="false" table="Roles">
    <id name="Id" type="int">
      <generator class="native"/>
    </id>

    <property name="Name" />
    <property name="Description" />
  </class>

Update N2: this is how the tables are defined:

Role table:

CREATE TABLE [Roles] (
[Id] INTEGER NOT NULL PRIMARY KEY,
[Name] text  NOT NULL,
[LastChanged] INT NOT NULL DEFAULT(0)
);

CREATE UNIQUE INDEX uidxUserName ON Roles (Name COLLATE NOCASE);

User table:

CREATE TABLE [Users] (
[Id] INTEGER NOT NULL PRIMARY KEY,
[Name] text NOT NULL,
[LastChanged] INT NOT NULL DEFAULT(0)
);

CREATE UNIQUE INDEX uidxRoleName ON Users (Name COLLATE NOCASE);

User_Role relatioin table (foreign keys):

CREATE TABLE [User_Role] (
[UserId] INTEGER NOT NULL,
[RoleId] INTEGER NOT NULL,
[LastChanged] INT NOT NULL DEFAULT(0),
PRIMARY KEY (UserId, RoleId),
FOREIGN KEY (UserId) REFERENCES Users(Id),
FOREIGN KEY (RoleId) REFERENCES Roles(Id)
);
+1  A: 

I've ran into a similar problem myself.

Try to remove the inverse="true" on both the User collection and the Role collection and see if there's any difference. That worked for me.

There seems some unexpected behavior with many-to-many relationships and the inverse attribute. Perhaps someone else can explain why that occurs better than me. :)

Update: If I'm not mistaken here, you'll need to map a many-to-many bag under the Role mapping. Try to keep both inverse="false" or without the inverse attribute at all.
Are you certain about the foreign-key value used there?

Also, I think when mapping the class attribute, you need to provide the fully qualified name, i.e: Namespace.Class, Assembly.

Besides that, it looks mostly like my own mapping files.

GeReV
tried to remove and got another exception, saying " object references an unsaved transient instance - save the transient instance before flushing"
BreakPhreak
Hmm... Can you give any more information? The other class' mapping, a source code sample?
GeReV
Added Role mapping - please see the update.
BreakPhreak
Thanks for following! The [Class, Assembly] names are properly given in the production code. --- I've removed the "inverse" attribute (as said in my previous comment and then got that long error message, appointed above). --- What do you mean by "are you certain?" in the foreign-key-related question? The ROLE table has ID field, just like USER table that has an ID of its own; the USER_ROLE table keeps pairs of those IDs. Does it answer your question? --- Not sure I need to map a bag on the ROLE side - this is something which is neither required, nor necessary at this time. Is it a real must?
BreakPhreak
What I'm asking is, are you certain that using the `foreign-key` attribute is something you need? The value you've entered, at least the one you've posted here doesn't seem like a standard foreign key.Could you provide some source code of a part where you save a user and its roles?
GeReV
The source would be way too long (not kidding) and mixed with Lhotka business objects. But I've just updated the question with the SQL where I define tables (in sqlite for example). Does it help to figure out anything?
BreakPhreak
Also, just as said below, I've debugged the long message about transient object and realized that it's related to the *LastChanged* field. The system checks the property value and thinks that the **Role** is transient.
BreakPhreak
A: 
<bag name="RoleList" table="User_Role" cascade="save-update">
  <key column="UserId"/>
  <many-to-many class="Role" column="RoleId"/>
</bag>

Also: lazy="false" is almost always a bad idea.

Diego Mijelshon
Just tried it. By turning the cascade on the NHibernate tries to INSERT into USER table (success), then tries to INSERT into ROLE table (fail: role already exists, roles are unique). What I miss in the story is the INSERT into USER_ROLE table. Any advice will be blessed.
BreakPhreak
PS: there are only a very-very few roles, so lazy="false" is on purpose.
BreakPhreak
If you get an insert, it's because you are not retrieving the roles correctly (i.e. you are probably creating new ones and assigning the same id). Also, that's not what lazy="false" is used for. Look into caching and class-level `batch-size`
Diego Mijelshon
I've just validated in the debugger: the ID is correct and belongs to the role that resides in the table. Still there is a question why the relationship (into the USER_ROLE table) is not stored. Maybe it is scheduled to happen right after INSERT into ROLE but it fails from the obvious reasons. As about your advice regarding the batch-size - I'll happily perform the optimization (promise :) when this blocking issue will be resolved.
BreakPhreak
Again, how are you retrieving the Role? Creating a new instance an assigning the Id will **not** work.
Diego Mijelshon
BreakPhreak
... And if I use the same mapping with User/Role (just as you've recommended, but without cascade) then I am getting the message "object references an unsaved transient instance - save the transient instance before flushing" (when trying to save User instance). Debugged it and received that Roles are versioned and NHibernate thinks that the Role is transient and must be saved first.
BreakPhreak
If you have a list of roles that come from another session, use session.Load<Role>(roleId) to reference them in the current session (it won't go to the DB)
Diego Mijelshon
Fetched the role by role ID through the correct session: (A) with "save-update" still tries to save the role. (B) without the "save-update" tells that the role is transient and should be saved first. Thanks a lot for your advices, I think that it's some piece of info I need to figure out (soon :). I'll continue my own research and thanks again.
BreakPhreak
A: 

please see my answer here

BreakPhreak