Search code examples
nhibernatefluent-nhibernates#arp-architectureautomapping

One-to-one mapping in S#arp Architecture


There's a distinct smell of burned out circuits coming from my head, so forgive my ignorance.

I'm trying to setup a one-to-one relationship (well, let Automapper do it) in S#arp Architecture.

I have

public class User : Entity
{
    public virtual Profile Profile { get; set; }
    public virtual Basket Basket { get; set; }
    public virtual IList<Order> Orders { get; set; }
    public virtual IList<Role> Roles { get; set; }  
    ...
}

public class Basket : Entity
{
    public virtual User User { get; set; }  
    ...
}

public class Profile : Entity
{
    public virtual User User { get; set; }
    ...
}

And my db schema is

CREATE TABLE [dbo].[Users](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    ...

CREATE TABLE [dbo].[Profiles](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [UserFk] [int] NULL,
    ...

CREATE TABLE [dbo].[Baskets](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [UserFk] [int] NULL,
    ...

When I run the unit test CanConfirmDatabaseMatchesMappings in MappingIntegrationTests I get the following error

NHibernate.ADOException : could not execute query ... System.Data.SqlClient.SqlException : Invalid column name 'ProfileFk'. Invalid column name 'BasketFk'.

and the sql it's trying to execute is

SELECT TOP 0
    this_.Id AS Id6_1_ ,
    ..
    user2_.ProfileFk AS ProfileFk9_0_ ,
    user2_.BasketFk AS BasketFk9_0_
FROM
    Profiles this_
    LEFT OUTER JOIN Users user2_
        ON this_.UserFk = user2_.Id

So it's looking for a ProfileFk and BasketFk field in the Users table. I haven't setup any customer override mappings and as far as I can see I've followed the default conventions setup in S#.

The two other mappings for IList Orders and Roles seem to map fine. So I'm guessing that it've missed something for setting up a one-to-one relationship.

What am I missing?


Solution

  • Got it. This is really an NHibernate problem to solve with Fluent NHibernate syntax, but it happens to be relevant for S#.

    Background reading: NHibernate Mapping and Fluent NHibernate HasOne

    What you do is override the mapping for User and give it two .HasOne mappings. Then set a unique reference to the user on the Profile and Basket class:

    public class UserMap : IAutoMappingOverride<User>
        {
            #region Implementation of IAutoMappingOverride<User>
            /// <summary>
            /// Alter the automapping for this type
            /// </summary>
            /// <param name="mapping">Automapping</param>
            public void Override(AutoMapping<User> mapping)
            {
                mapping.HasOne(u => u.Profile);
                mapping.HasOne(u => u.Basket);
            }
            #endregion
        }
    
    public class ProfileMap : IAutoMappingOverride<Profile>
        {
            #region Implementation of IAutoMappingOverride<Profile>
            /// <summary>
            /// Alter the automapping for this type
            /// </summary>
            /// <param name="mapping">Automapping</param>
            public void Override(AutoMapping<Profile> mapping)
            {
                mapping.References(p => p.User).Unique().Column("UserFk");
            }
            #endregion
        }
    
    public class BasketMap : IAutoMappingOverride<Basket>
        {
            #region Implementation of IAutoMappingOverride<Basket>
            /// <summary>
            /// Alter the automapping for this type
            /// </summary>
            /// <param name="mapping">Automapping</param>
            public void Override(AutoMapping<Basket> mapping)
            {
                mapping.References(b => b.User).Unique().Column("UserFk");
            }
            #endregion
        }
    

    As a side note, at the time of writing this, NHibernate 3 has just been released. There's a great book out called NHibernate 3.0 Cookbook which I've just bought and it looks extremely useful for working with S#.