2

Using

.NET Core SDK (reflecting any global.json): Version: 2.2.300 Commit: 73efd5bd87

and

Nhibernate 5.2.5

Have the following entities

public class Customer : Entity {
    public Customer() {
        Initialize();
    }
    public virtual string Name { get; set; }
    public virtual string LegalName { get; set; }
    public virtual string VATCode { get; set; }
    public virtual ICollection<Site> Sites { get; set; }
    public virtual DateTime Created { get; set; } = DateTime.UtcNow;

    private void Initialize() {
        Sites = new List<Site>();
    }
}

public class Site : Entity {
    public virtual string Address { get; set; }
    public virtual string City { get; set; }
    public virtual string Country { get; set; }
    public virtual Customer Customer { get; set; }
}

with the following mappers

internal class CustomerConfiguration : ClassMapping<Customer> {
    public CustomerConfiguration() {
        Table( TableNames.CustomersTable );

        Id( x => x.EntityID, im => {
            im.Column( "CustomerID" );
            im.Generator( Generators.Identity );
        } );
        [... code omitted for brevity ...]
        Set<Site>( property => property.Sites,
            collection => {
                collection.Fetch( CollectionFetchMode.Join );
                collection.Lazy( CollectionLazy.Lazy );
                collection.Cascade( Cascade.Persist.Include( Cascade.DeleteOrphans ) );
                collection.Inverse( true );
                collection.Key( keyMapping => {
                    keyMapping.Column( "CustomerID" );
                } );
            },
            mapping => {
                mapping.OneToMany( relationalMapping => {
                    relationalMapping.Class( typeof( Site ) );
                } );
            } );
    }
}

internal class SiteConfiguration : ClassMapping<Site> {
    public SiteConfiguration() {
        Table( TableNames.SitesTable );

        Id( x => x.EntityID, im => {
            im.Column( "SiteID" );
            im.Generator( Generators.Identity );
        } );
        [... code omitted for brevity ...]
        ManyToOne( x => x.Customer, mm => {
            mm.Column( "CustomerID" );
            mm.NotNullable( true );
        } );
    }
}

I guess this mapping is not correct because if I do something like

using ( var session = sessionFactory.OpenSession() ) {
    var customer = new Customer() {
        Name = $"Customer 1",
        LegalName = $"Customer 1 LLC",
        VATCode = "xxxxxxxxx",
        Created = DateTime.UtcNow
    };
    customer.Sites.Add( new Site() {
        Address = $"Address Customer 1",
        City = $"City Customer 1",
        Country = $"Country Customer 1",
        Customer = customer
    } );
    session.Save( customer );
}

I get the following exception

Unhandled Exception: NHibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing or set cascade action for the property to something that would make it autosave. Type: Nhibernate.ConsoleApp.Entities.Site, Entity: [Site 0]

Any suggestion?

EDIT

Actually the problem was on a different area. it was because of the presence of two registered Listeners (IDeleteEventListener and IUpdateEventListener). Whenever I add those Update and delete does not work. However I was able to discover the problem thanks to Roman Artiukhin comment

Lorenzo
  • 29,081
  • 49
  • 125
  • 222
  • Possible duplicate https://stackoverflow.com/questions/20502986/transientobjectexception-when-using-cascade-all – Alexander Jul 08 '19 at 11:13
  • @Alexander I did read all the referenced post but unless I did not got the difference, In my case there is no composite ID in place. Both tables are using surrogate keys as database identities columns. I have updated the question to add the mapping IDs. Could you please elaborate? – Lorenzo Jul 08 '19 at 15:53

2 Answers2

1

Exception is telling that the root entity Customer is holding the reference to other (Site) entity which is transient entity. That is why, root entity cannot be persisted.

Scenario 1: No cascade options were configured.
In this case the child or referenced objects must be saved first.

using ( var session = sessionFactory.OpenSession() ) {
    var customer = new Customer() {
        Name = $"Customer 1",
        LegalName = $"Customer 1 LLC",
        VATCode = "xxxxxxxxx",
        Created = DateTime.UtcNow
    };
    Site site = new Site() {
        Address = $"Address Customer 1",
        City = $"City Customer 1",
        Country = $"Country Customer 1",
        Customer = customer
    }
    customer.Sites.Add(site);
    session.Save( customer );//<--Save the master
    session.Save( site );//<--Save the site
    ...
    ...
    ...
    session.Flush();//<--You are calling this somewhere in your code
}

This should work.

Scenario 2: Cascade options were not configured for all INPUT; UPDATE or DELETE operations.
In this case the configuration must be changed or child or referenced objects must be saved first.

You have already:

so this is not a problem.

Scenario3: Related transient objects where instantiated and associated to the persitent object but no save operation is to be performed on those objects.
In this case the trainsient objects must be detached from the current session through the command: ISession.Evict(obj)

Instead of Evict, you can call the Save to attach those objects explicitly as mentioned above.

For more insight, have a look at documentation which explains various strategies.

Amit Joshi
  • 15,448
  • 21
  • 77
  • 141
  • You are completely right on your suggestion. The problem was related to a listener registration. Please have a look at my edit. – Lorenzo Jul 16 '19 at 16:54
1

My guess is that collection is not your problem. It seems you have Site entity somewhere else exposed (for instance many-to-one customer.LastSite property or something like this). And cascade logic stumbles upon this not yet saved Site property before collection is saved. So you have to make sure that other Site properties are also have Cascade.Persist mapping

Roman Artiukhin
  • 2,200
  • 1
  • 9
  • 19
  • Actually this is not the right answer but thanks to this I was able to find the error which was exactly in a listener registration. Whenever I add a listener for `PostInsert` events, the listener is called when I do `Session.Create`. But if I add a listener for `PostUpdate` or `PostDelete` something strange happens: (1) the listener is never called when I do `Session.Update` or `Session.Delete` and (2) `Update` and `Delete` methods of `ISession` stops working. Please have a look at my edit. – Lorenzo Jul 16 '19 at 16:59