C# – Mapping references to companion objects with fluent-nhibernate

cnhibernate

I've got the following basic domain model for my MVC website accounts:

public class Account
{
    public Account()
    { 
        Details = new AccountDetails( this ); 
        Logon = new LogonDetails(this); 
    }
    public virtual int Id { get; private set; }
    public virtual AccountDetails Details { get; set; }
    public virtual LogonDetails Logon { get; set; }
    ...
}

public class AccountDetails
{
    // Primary Key
    public virtual Account Account { get; set; }
    public virtual DateTime Created { get; set; }
    ...
}

public class LogonDetails
{
    // Primary Key
    public virtual Account Account { get; set; }
    public virtual DateTime? LastLogon { get; set; }
    ...
}

Both AccountDetails and LogonDetails use a mapping like this:

public class AccountDetailsOverride : IAutoMappingOverride<AccountDetails>
{
    public void Override( AutoMap<AccountDetails> mapping )
    {
        mapping
            .UseCompositeId()
            .WithKeyReference( x => x.Account, "AccountId" );

        mapping.IgnoreProperty( x => x.Account );
    }
}       

I've split the account details and logon details into separate models since I rarely need that information, whereas I need the userid and name for many site operations and authorization. I want the Details and Logon properties to be lazy-loaded only when needed. With my current mapping attempts I can get one of two behaviors:

# 1 Create table and load successfully, cannot save

Using this mapping:

public class AutoOverride : IAutoMappingOverride<Account>
{
    public void Override( AutoMap<Account> mapping )
    {
        mapping.LazyLoad();
        mapping
            .References( x => x.Details )
            .WithColumns( x => x.Account.Id )
            .Cascade.All();
        mapping
            .References( x => x.Logon  )
            .WithColumns( x => x.Account.Id )
            .Cascade.All();
    }
}

The tables are generated as expected. Existing data loads correctly into the model, but I can't save. Instead I get an index out of range exception. Presumably because Account.Details and Account.Logon are both trying to use the same db field for their reference (The Account.Id itself).

#2 Table includes extra fields, does not save properly

Using this mapping:

public class AutoOverride : IAutoMappingOverride<Account>
{
    public void Override( AutoMap<Account> mapping )
    {
        mapping.LazyLoad();
        mapping
            .References( x => x.Details )
            .Cascade.All();
        mapping
            .References( x => x.Logon  )
            .Cascade.All();
    }
}

I get a table with a separate field for Details_id and Logon_id but they are null since the value of Details.Account.Id is null when the Account is persisted. So, attempting to Session.Get the account results in Details and Logon being null. If I save Account twice, the table is updated correctly and I can load it.

Help…

There must be a way of mapping this hierarchy and I'm missing something simple. Is there a way to help nhibernate pick the proper field (to solve #1) or to have it update dependent fields automatically after save (to solve#2)?

Thanks for any insight you folks can provide.

Best Answer

If I'm understanding your model and desired behavior, what you have is actually a one-to-one relationship between Account and AccountDetails and between Account and LogonDetails. References creates a many-to-one relationship, so that could be your problem; try HasOne instead.

That said, for this and other reasons, I avoid one-to-ones unless absolutely necessary. There may be more than what you're showing, but is it worth the headache and ugly model to avoid loading two DateTime fields?

Finally, and this is somewhat speculation since I have not tested this functionality, NHibernate 2.1's (which FNH has switched to as supported version) mapping XML schema defines a lazy attribute for property elements. The 1.0 release of FNH (should be in the next week or two) will support setting this attribute. As I said, I have not tested it but it would seem that this would allow you to lazy load individual properties which is exactly what you want.

Related Topic