Wednesday, October 19, 2011

FluentNhibernate and Bidirectional Relations

Preface

My last position had me using FluentNHibernate to handle my object persistence. We were using Oracle as a store. Many of the tables were quite large (100+ columns), and often did not have a primary key. The result was I often didn't have a good opportunity to use FNH's (NH's?) relationship capabilities.

My new position has me introducing FNH to some developers. The project is using SQL Server 2008 as a store, with tables that have primary keys, and other little should-haves.

The Problem

When saving new parents with new children, the children would not have their foreign key updated. The parent would save correctly. When any child object would save the database would throw an exception, because the foreign key constraint (not null) would be violated.

Our Design

Names changed to protect the innocent.

Our design was simple: a record in the database representing a file, and records being held in another table representing records within the file. After a bit of research, we realized that we needed a bi-directional relationship.

public class MyFile
{
    public virtual int MyFileId { get; set; }
    public virtual string FileName { get; set; }
    public virtual IList<Record> Records  { get; set; }
}

public class Record
{
    public virtual int MyRecordId { get; set; }
    public virtual int MyFileId { get; set; }
    public virtual MyFile SourceFile { get; set; }
    public virtual int RecordType { get; set; }
}

Our mapping classes were pretty simple too:

public sealed class MyFileMap : ClassMap<MyFile>
{
    public MyFileMap()
    {
        Table("MyFile");

         Id(o => o.MyFileID, "MyFileID").GeneratedBy.Identity();

        Map(o => o.FileName).Column("FileName");

        HasMany(o => o.Records).KeyColumn("InFileID")
            .Inverse().Cascade.All().AsBag();
    }
}

public sealed class RecordMap : ClassMap<Record>
{
    public WEXTicketRequestDetailMap()
    {
        Table("Record");

        Id(o => o.RecordID, "RecordID").UnsavedValue(0);

        References(o => o.SourceFile)
            .Column("MyFileID").Cascade.All();

        Map(o => o.RecordType).Column("RecordType");
    }
}

We'd create the classes and attempt to save them to the database:

// ... some code
var record = new Record();
var file = new MyFile{ // set some file stuffs here };
file.Records = new List<Record>{ record };
// ... more stuff here, including creating a session and transaction
session.Save(file);

This would result in an exception being thrown, as the foreign key on the Record object wasn't being set.

The Solution

A little searching (okay, a lot of searching) finally led us to a Stack Overflow post. Therein an answer by James Gregory showed us what we were doing wrong: we needed to set the parent entity on the child.

// ... some code
var record = new Record();
var file = new MyFile{ // set some file stuffs here };

record.SourceFile = file; // <-- this is the important part

// ... more stuff here, including creating a session and transaction
session.Save(file);

Once that was done, everything was good.