0

I got an exception "null id generated for AccountDetail" when mapping one-to-one relationship by using many-to-one with unique constraint.

Here's my SQL tables:

Account(Id, Name)
AccountDetail(AccountId, Remark)

AccountId is both primary and foreign key.

Here's my Domain Model (Account and AccountDetail):

public class Account
{
    public virtual int Id { get; set; }

    public virtual string Name { get; set; }

    public virtual AccountDetail Detail { get; set; }

    public Account()
    {
        Detail = new AccountDetail
        {
            Account = this
        };
    }
}

public class AccountDetail
{
    public virtual int AccountId { get; set; }

    public virtual Account Account { get; set; }

    public virtual string Remark { get; set; }
}

Mapping (NHibenrate 3.3 mapping by code):

class AccountMap : ClassMapping<Account>
{
    public AccountMap()
    {
        Table(typeof(Account).Name);

        Id(c => c.Id, m => m.Generator(Generators.Native));

        Property(c => c.Name);

        OneToOne(c => c.Detail, m =>
        {
            m.Constrained(true);
            m.Cascade(Cascade.All);
          m.PropertyReference(typeof(AccountDetail).GetPropertyOrFieldMatchingName("Account"));
        });
    }
}

class AccountDetailMap : ClassMapping<AccountDetail>
{
    public AccountDetailMap()
    {
        Table(typeof(AccountDetail).Name);

        Id(c => c.AccountId, m =>
        {
            m.Column("AccountId");
            m.Generator(Generators.Foreign<AccountDetail>(x => x.Account));
        });

        Property(c => c.Remark);

        ManyToOne(c => c.Account, m =>
        {
            m.Column("AccountId");
            m.Unique(true);
        });
    }
}

BTW: Can I remove the AccountId property in AccountDetail? That is, only use the Account property. Using both AccountId and Account properties in AccountDetail class looks not so object-oriented.

Thanks!

Mouhong Lin
  • 4,402
  • 4
  • 33
  • 48

1 Answers1

1

I can't say what's actually wrong, but comparing with my working one-to-one relation, I would map it like this:

class AccountMap : ClassMapping<Account>
{
    public AccountMap()
    {
        Table(typeof(Account).Name);

        // creates a auto-counter column "id"
        Id(c => c.Id, m => m.Generator(Generators.Native));

        // doesn't require a column, one-to-one always means to couple primary keys.
        OneToOne(c => c.Detail, m =>
        {
            // don't know if this has any effect
            m.Constrained(true);

            // cascade should be fine
            m.Cascade(Cascade.All);
        });
    }
}

class AccountDetailMap : ClassMapping<AccountDetail>
{
    public AccountDetailMap()
    {
        Id(c => c.AccountId, m =>
        {
            // creates an id column called "AccountId" with the value from
            // the Account property.
            m.Column("AccountId");
            m.Generator(Generators.Foreign(x => x.Account));
        });

        // should be one-to-one because you don't use another foreign-key.
        OneToOne(c => c.Account);
    }
}
Stefan Steinegger
  • 63,782
  • 15
  • 129
  • 193
  • But if map like this way, we are not able to do the lazy load. – Mouhong Lin May 14 '12 at 14:16
  • lazy load is not supported in one-to-one association. Please see this: http://stackoverflow.com/questions/1796574/lazy-load-nhibernate-one-to-one. I think I get the answer now. It requires to move the foreign key from child table to parent table. That is, add a AccountDetailId in Account table. But this does not look good from the database designers point of view. – Mouhong Lin May 15 '12 at 09:04
  • BTW: I think in your answer, the "constraint should be mapped in AccountDetailMap, not in the AccountMap. – Mouhong Lin May 15 '12 at 09:05