Search code examples
c#nhibernatefluent-nhibernatenhibernate-mappingfluent-nhibernate-mapping

fluent nhibernate mappings that save in both directions


I want to be able to do the following:

var child = new Child {
  Name = "foo",
  Parent = new Parent { Name = "bar" }
};

session.Save(child);

OR

var parent = new Parent {
  Name = "foo",
  Children = new List<Child> {
    new Child { Name = "bar" },
    new Child { Name = "baz" },
  },
};

session.Save(parent);

and have it in both cases save all of the created objects when Save() is called. Here are the classes. I do not know how to do the mapping of the one to many relationship between Parent and Child that will facilitate saving all of the created objects in one call to Save(). I expect that either version can be used, but only one will be used at any given time. The rationale behind this is that I'm creating test data builders and in some cases it will be easier to wire it up from the Child side and in other cases it will be easier to wire up from the Parent side. Hence why I want to be able to cascade inserts and deletes in both directions given either object. I don't care about updates. This will only be used to insert data, run my tests against the app, then delete the data.

Here are the classes involved:

public class Child {
  public virtual int Id { get; set; }
  public virtual string Name { get; set; }
  public virtual Parent Parent { get; set; }
}

public class Parent {
  public virtual int Id { get; set; }
  public virtual string Name { get; set; }
  public virtual IEnumerable<Child> Children { get; set; }
}

public ChildMap : ClassMap<Child> {
  public ChildMap() {
    Id(x => x.Id);
    Map(x => x.Name);

    // How should I set this up?
    Reference(x => x.Parent);
  }
}

public ParentMap : ClassMap<Parent> {
  public ParentMap() {
    Id(x => x.Id);
    Map(x => x.Name);

    // How should I set this up ?
    HasMany(x => x.Children);
  }
}

Solution

  • To be able save cascades from both sides, we can just use cascade setting

    So, this mapping would be enough:

    public ChildMap() 
    {
        ...
        BatchSize(100);  // this is the way how to solve 1 + N (good practice, mine at least)
    
        // How should I set this up?
        References(x => x.Parent) // References, not Reference
            .Cascade.SaveUpdate()
            ;
    }
    
    public ParentMap() 
    {
        ...
        BatchSize(100);  // this is the way how to solve 1 + N
    
        // How should I set this up ?
        HasMany(x => x.Children)
           .BatchSize(100)  // no 1 + N
           .Cascade.AllDeleteOrphan()
           .Inverse()
           ;
    

    With these settings, the above would work, with one important change - children MUST know about its parent. Because we use inverse...

    This will work:

    var child = new Child
    {
        Name = "foo",
        Parent = new Parent {Name = "bar"}
    };
    
    session.Save(child);
    session.Flush();
    session.Clear();
    
    var persistedChild = session.Get<Child>(child.ID);
    

    And this will as well (but with setting child.Parent reference)

    var parent = new Parent
    {
        Name = "foo",
        Children = new List<Child>
        {
            new Child {Name = "bar"},
            new Child {Name = "baz"},
        },
    };
    
    foreach (var ch in parent.Children)
    {
        ch.Parent = parent; // we MUST do this, there is .Inverse()
    }
    
    session.Save(parent);
    session.Flush();
    session.Clear();
    
    var persistedParent = session.Get<Parent>(parent.ID);
    

    Read more here about the batch size (not part of a question, but I do use it always)

    Some other details about inverse mapping and reference assignment

    And also, why we cannot use the same cascade on .Rferences() (many-to-one) NHibernate Many-to-one cascade