Parent-child cascade SaveOrUpdate failes

Dion's Avatar

Dion

02 Nov, 2010 12:13 PM via web

I have a parent-child relationship that I've put a test case together between Users and Groups. I did this to replicate a failure in
a Parent-Child relationship when trying to perform a cacade insert using thes relationship.

The two SQL tables are as follows:

CREATE TABLE [dbo].[User]
(

[Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[Name] [varchar](50) NOT NULL,

)

CREATE TABLE [dbo].[Group]
(

[Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[GroupName] [varchar](50) NOT NULL,
[UserId] [int] NOT NULL,

)

ALTER TABLE [dbo].[Group] WITH CHECK ADD CONSTRAINT [FK_Group_User] FOREIGN KEY([UserId])
REFERENCES [dbo].[User] ([Id])

The objects represetn these two tables with the following mappings:

public class UserMap : ClassMap<User>
{
    public UserMap()
    {
        Table("[User]");
        Id(x => x.Id).GeneratedBy.Identity();
        Map(x => x.Name).Not.Nullable();
        HasMany(x => x.Groups).KeyColumn("UserId").Cascade.SaveUpdate();
    }
}

public class GroupMap : ClassMap<Group>
{
    public GroupMap()
    {
        Table("[Group]");
        Id(x => x.Id).GeneratedBy.Identity();
        Map(x => x.GroupName).Not.Nullable();
        References(x => x.User).Column("UserId").Not.Nullable();
    }
}

The code to created the objects is simply:

        User u = new User() { Name = "test" };
        Group g = new Group() { GroupName = "Test Group" };
        u.Groups.Add(g);

        using (var session = factory.OpenSession())
        {
            session.SaveOrUpdate(u);
        }

However it fails with exception "Cannot insert the value NULL into column 'UserId', table 'test.dbo.Group'; column does not allow nulls. INSERT fails.
The statement has been terminated". I suspect that this is dude to the parent object's Id (an identity column) being passed through as NULL and not the new values. Is this a bug or is there a way to fix these mappings so that this cascade relationship succeeds?

  1. Support Staff 2 Posted by James Gregory on 03 Nov, 2010 03:02 PM

    James Gregory's Avatar

    I believe you're missing an Inverse on your Groups. You've got a bi-directional relationship but you haven't told NHibernate which side is the "owner".

    HasMany(x => x.Groups)
      .KeyColumn("UserId")
      .Cascade.SaveUpdate()
      .Inverse();

    Also, you should make sure all your writes are done within a transaction. This is a best practice with NHibernate.

    using (var session = factory.OpenSession())
    using (var transaction = session.BeginTransaction())
    {
      session.SaveOrUpdate(u);
      transaction.Commit();
    }
  2. Support Staff 3 Posted by Paul Batum on 03 Nov, 2010 03:41 PM

    Paul Batum's Avatar

    You have a bidirectional relationship but when you link the two objects you
    are not ensuring that both directions are set up correctly. You add the
    group to the users list of groups but you haven't set the user property on
    the group.

    You can get away with this if you make sure you specify that the Group.User
    many-to-one is mapped as inverse. (this tells NH that it should use
    User.Groups as the mechanism for detecting the relationship).

    Personally I think a better way is to use AddUser and RemoveUser methods and
    make sure those methods keep both sides of the relationship up to date, and
    then map the one-to-many side as inverse.

    On Tue, Nov 2, 2010 at 11:15 PM, Dion <
    ***@tenderapp.com<tender%***@tenderapp.com>
    > wrote:

Reply to this discussion

Preview Comments are parsed with Markdown. Help with syntax

Attached Files

    You can attach files up to 10MB

    Ten divided by two is what?