0
votes

I've been searching the answer to my questions for a few days and I've tried most of tips/answers and even looked through hundred of lines of logs generated by nHibernate, but I can't find a way to get my mapping working.. nHiberante still refuses to generate any delete statement when it's asked to SaveOrUpdate.

I have three separate entities, Person, House and Room... in the real word, each Person will have one house and each house can have multiple rooms, the mapping between house and room is one-to-many as expected, and I wanted a one-to-one mapping between Person and House then I read on the fluent nHibernate wiki that says it's really a many(Person)-to-one(house) because there's nothing at the database level that stops multiple persons sharing the same house. I guess that makes sense so I have my database schema and class map designed as below:

CREATE TABLE [dbo].[Person](
    [PersonId] [int] IDENTITY(1,1) NOT NULL,
    [HouseId] [int] NOT NULL
)

CREATE TABLE [dbo].[House](
    [HouseId] [int] IDENTITY(1,1) NOT NULL,
    [HouseName] [varchar](500) NOT NULL
)

CREATE TABLE [dbo].[Room](
    [RoomId] [int] IDENTITY(1,1) NOT NULL,
    [HouseId] [int] NOT NULL
)


HouseId on [Person] and [Room] are both foreign keys referencing HouseId from [House] table

    public class PersonMap : ClassMap<Person>
    {
        public PersonMap()
        {
            Table("Person")
            Id (x=>x.Id).Column("PersonId").GeneratedBy.Identity().UnsavedValue(0);
            Reference(x=>x.House).Column("HouseId").ForeignKey("HouseId").Not.LazyLoad().Cascade.All();
        }
    }

    public class HouseMap : HouseMap<Room>
    {
        public RoomMap()
        {
            Table("House")
            Id (x=>x.Id).Column("HouseId").GeneratedBy.Identity().UnsavedValue(0);

            HasMany(x => x.Persons)
                .KeyColumn("HouseId")
                .Not.LazyLoad()
                .Cascade.AllDeleteOrphan().Inverse();

            HasMany(x => x.Rooms)
                .KeyColumn("HouseId")
                .Not.LazyLoad()
                .Cascade.AllDeleteOrphan().Inverse();
        }
    }

    public class RoomMap : ClassMap<Room>
    {
        public RoomMap()
        {
            Table("Room")
            Id (x=>x.Id).Column("RoomId").GeneratedBy.Identity().UnsavedValue(0);
            Reference(x=>x.House).Column("HouseId").ForeignKey("HouseId").Not.LazyLoad().Cascade.All();
        }
    }

The class House holds two lists of type Person and Room respectively, and the class Person and Room both holds a reference back to the House object.

So I created an instance of House and set its two child lists Person(one 1) and Room(multiple rooms), and also link the properties of Person/Rooms back to House..and use session.SaveOrUpdate to save the Person it all works well.

Then I tried retrieve a instance of Person, and remove a room from Person.House.List and set room.House to null to fully break the reference, then use session.SaveOrUpdate to save the Person again (let's call it Person A), , nHibernate correctly generated delete statement to delete the orphan room that's not linked to its parent House any more.. so far so good...

Here is where my problem starts...in my application, I need to convert the object Person to a PersonContract and then sent it to the client, the client has the ability to remove a roomContract from PersonContract.HouseContract and send a copy of PersonContract back. Note in PersonContract, there're no bilateral reference, i.e. PersonContract has one property of HouseContract and HouseContract only contains a list of RoomContract so HouseContract does not keep a list of PersonContract and RoomContract doesn't have a HouseContract property.

Then on the server side, I'll convert the PersonContract back to a Person object and the converter is responsible for creating all new instances of Person,Room and houses and setting their Ids and build all bilateral references between them.

The problem is... when I then use session.SaveOrUpdate on the Person object (after contract conversion), nHibernate seems to ignore the fact a room has been removed from the collection but it does the update or insert job well..

I've carefully checked my contract converter to make sure all properties/references/Ids are properly set, and it looks exactly the same copy as the Person A above...but I don't understand why nHibernate is not doing the job here.

I've overridden both Equals and GetHashCode on Person/Room/House to return its id.

Another fact I observed is when I use session.SaveOrUpdate on the after-conversion Person object, nHibernate is generating lots of Update statements even though the value hasn't really changed, it looks as if nHibernate is not aware of the state of the persisted Person object so choose to update everything by identifying it using Id...

I'm quite new to nHibernate...so please let me know if I'm doing something horribly wrong here :)

Update: If I remove Inverse from one-to-many mapping between House and Room, then nHibernate will set the house ID of removed Room to NULL...better than not removing room at all but I really want it to remove orphaned rooms from DB..

Update 2: session.Merge on Person is working (SaveOrUpdate doesn't)....though I don't understand know why..

1

1 Answers

1
votes

Try with wrapping your Save/Updates in a transaction:

using (ITransaction transaction = session.BeginTransaction())
{
    session.Save(SomeObject);
    transaction.Commit();
}