Mapping by code - two-way relation

234 views Asked by At

I have a Parent-Children relation and currently, only a reference from each child to the parent exists (there is a foreign key column on the child table). I would like to add a readonly IEnumerable property to the parent, but I am failing to do so.

public class ProjectData : Entity
{
    public virtual long Id {get;set;}
    private readonly IList<BranchData> _branches = new List<BranchData>();
    public virtual IEnumerable<BranchData> Branches
    {
        get { return _branches.AsReadOnly(); }
    }
}

public class BranchData : Entity
{ 
    puplic virtual long Id {get;set;}
    public virtual ProjectData ProjectData { get; set; }
}

 public class BranchDataMapping : ClassMapping<BranchData>
{
    public BranchDataMapping()
    {
       ManyToOne(x => x.ProjectData, mapper => { });
    }
}

  public class ProjectDataMapping : ClassMapping<ProjectData>
{
    public ProjectDataMapping()
    {
        Bag(data => data.Branches,
            mapper =>
                {
                    mapper.Access(Accessor.ReadOnly);
                    mapper.Inverse(false);                       
                }, relation => relation.OneToMany());

    }
}

Table- and column-names are created by conventions. When I create and save a new BranchDate with parent-property set, it does not appear in the Branches collection of the parent.

What setting am I missing?

I have read Bi-directional NHibernate mapping by code and http://notherdev.blogspot.cz/2012/02/nhibernates-mapping-by-code-summary.html and I am still failing to set it properly.

Edit: Code that verifies this behaviour

        [Test]
    public void CanSaveAndLoadBranch()
    {
        var session = InMemoryDatabase.GetSession();

        var project = new ProjectData {Name = "ratata"};
        var branch = new BranchData {ProjectData = project};

        using (var tx = session.BeginTransaction())
        {
            session.Save(project);
            session.Save(branch);

            tx.Commit();
        }
        var branches = project.Branches;
        //branches are empty

        var freshProject = session.Query<ProjectData>().Where(x => x.Id == project.Id).First();
        var freshBranches = freshProject.Branches;
        //branches are  empty here, too

        var fetched = session.Query<ProjectData>().Where(x => x.Id == project.Id).FetchMany(x => x.Branches).First();
        var fetchedBranches = fetched.Branches;
        //branches are  empty here, too


        var queriedOn = session.Query<ProjectData>().Where(x => x.Branches.Any()).ToList();
        //this does return the project, but Branches property is still empty


    }
1

There are 1 answers

1
Daniel Schilling On BEST ANSWER

When manipulating the data in-memory, you are responsible for managing both sides of the relationship. You can do this easily with an Add method in ProjectData:

public virtual void Add(BranchData branch)
{
    branch.ProjectData = this;
    Branches.Add(branch);
}

On the other side, NHibernate is responsible for properly hydrating and dehydrating your objects. However, you aren't seeing this because your queries are all occurring within the same session. Consider this code:

var project = new ProjectData();
session.Save(project);
session.Flush();
var freshProject = session.Get<ProjectData>(project.Id);
if (ReferenceEquals(project, freshProject))
    Console.WriteLine("They're the same instance.");

This code will display "They're the same instance." That instance will still have the relationships set up exactly as you initialized them, correct or not.

If you neglect to set the relationship on the inverse side, project.Branches, NHibernate will still be able to save it fine, but it will still continue to look inconsistent for the duration of that session. If you close the session and open a new one (this might be problematic for your in-memory test), or just call session.Clear() or session.Evict(project), then you will see data in project.Branches when you load it again.