Search code examples
c#nhibernatenullfluent-nhibernatefluent

NHibernate save children element FK going NULL


So, I have a list of children on my parent object, and I want to persist them on my SQL Server. When I run the application for the first time, all the children get their FK correctly, but when I run it again and no new parent is added, the new child(of an existing parent) doesn't get it's parent FK, just NULL. How can I map the parent FK on my child mapping for those situations?

I've tried the Inverse() method, but as I need the parent key to be generated all children gets null anyway. I need something like, if the parent is new, then the parent will update it's children FK, but when only the child is new I would need it to do the Inverse() method, is it possible?

Some more info: Every time I call the ParentPersist method, and it cascades as needed. I've added the AddChild() method to set the ParentId when a new child is added to the list, it's working as I debugged it, so the child is setting it's ParentId correctly.

The objects are like the following:

public class Parent
{
    public virtual int Id { get; set; }
    ...
    public virtual IList<Child> Children{ get; set; } 


 public virtual void AddChild(Child ch)
    {
        ch.IdParent = this.Id;
        Children.Add(ch);
    }
 }

 public class Child
 {
    public virtual int Id { get; set; }
    ...
    public virtual int IdParent {get;set;} 
 }

And my mapping:

public class ParentMapping : ClassMap<Parent>
{
    public ParentMapping ()
    {
        Id(cso => cso.Id).GeneratedBy.Identity();
        ...
        HasMany(cso => cso.Children).KeyColumn("IdParent").Cascade.SaveUpdate().Not.LazyLoad();
    }
}

public class ChildMapping : ClassMap<Child>
{
    public ChildMapping ()
    {
        Id(cso => cso.Id).GeneratedBy.Identity();
        ...
    }
}

Solution

  • Your logic (e.g. Add() method in Parent, Inverse() mapping) was OK. You were almost there. There is only one BUT...
    In general, the proper (if not only correct) solution is to use objects to express realtion and not just the ValueType/int values. That's why we call it ORM - Object-relational mapping

    Object in C# should look like this:

    public class Parent
    {
        ...
        // correct mapping of the children
        public virtual IList<Child> Children{ get; set; } 
    
        // this method uses the below updated Child version
        public virtual void AddChild(Child ch)
        {
            // this is replaced
            // ch.IdParent = this.Id;
            // with this essential assignment
            ch.Parent = this;
            Children.Add(ch);
        }
     }
    
    public class Child
    {
        ...
        // instead of this        
        // public virtual int IdParent {get;set;} 
        // we need the reference expressed as object
        public virtual Parent Parent { get; set; } 
    }
    

    So, now, once we have objects in place, we can adjust the mapping like this:

    // parent
    public ParentMapping ()
    {
        ...
        HasMany(cso => cso.Children)
           .KeyColumn("IdParent")
           .Inverse() // this is essential for optimized SQL Statements
           .Cascade.SaveUpdate() // All delete orphan would be better
           .Not.LazyLoad();
    }
    ...
    // Child
    public ChildMapping ()
    {
        ...
        References(x => x.Parent, "IdParent"); // it is a to use Inverse() 
    }
    

    With this Business Domain Model and the mapping (Inverse(), assigning bothe relation ends in Add() method...), NHibernat will have enough information to always (insert, update) issue proper SQL statements

    NOTE: One could ask why to map Parent Parent { get; set; } and not just the int IdParent { get; set; }... In fact, if we would have existing Parent (with NOT transient ID, i.e. > 0) - there won't be any difference. The trick/problems would appear on a new Parent insertion. Almost always, assignement of the children comes before the Parent is persiseted (flushed), and its ID is recieved from DB (sql server identity). And that could/would cause the child.IdParent == 0 ...
    We should remember, that in general - ORM is about objects, i.e. relation is represented by Reference types.