11

I am sure this is a straightforward question but consider the following: I have a reference between company and sector as follows:

public class Company {
    public Guid ID { get; set; }
    public Sector Sector { get; set; }
    public Guid SectorID { get; set; }
}

public class Sector {
    public Guid ID { get; set; }
    public string Name { get; set; }
}

Ok. What I want is the SectorID of the Company object to be populated after I go:

(new Company()).Sector = new Sector() { Name="asdf" }

and do a flush.

The mapping I am using kindly creates an additional column in the database called Sector_Id in the Company table, but this is not available as a property on Company. I want the SectorID property to be filled.

The mapping I'm currently using in the CompanyMap is

References(c => c.Sector).Cascade.All();

Does anyone have any ideas?


Thanks for your response. Sadly if I do the second option (set the column name of the column to be the same as the property, or set Map(x => x.SectorID, "Sector_Id") then I get the error:

System.IndexOutOfRangeException: Invalid index 7 for this SqlParameterCollection with Count=7.

I may have to do the first option but I am concerned that an additional query will be fired when you call the SectorID get as it gets the Sector itself out of the db (unless it is eager loaded which is a bit of a hassle).

I am surprised there is not an easy answer to this.


WOW! If I use

public virtual Guid SectorID
{
    get { return Sector.ID;
}   

then nhibernate is clever enough to know that the Sector_id column in the Organisation query is actually the same thing as Sector.ID and it returns this under the hoods. It does not send off an additional query even if you lazy load. I am impressed!


As a follow up... It seems that hibernate is not really written to be able to map the foreign key column in the objects. Although this can be a bit of a pain in web front ends it makes sense as this is really a persistence concern not really an object concern. I am using asp.net MVC and have written a custom model binder that will take a input box of name Contact (rather than ContactID), new up a new Contact with the ID of what is in the texbox, and then apply this to the property of the Model. This gets around the issue with dropdown lists in web front ends. Will post code if anyone is interested.

3 Answers3

13

This is easily done with a formula property.

public class Company {
  public virtual Guid Id { get; set; }
  public virtual Guid? SectorId { get; set; }
  public virtual Sector Sector { get; set; }
}

public class CompanyMap : ClassMap<Company> {
  public CompanyMap() {
    Id(x => x.Id); // Maps to a column named "Id"
    References(x => x.Sector); // Maps to a column named "Sector_id", unless you change the NHibernate default mapping rules.
    Map(x => x.SectorId).Formula("[Sector_id]");
  }    
}

This should act exactly how you want. When the Company is new, SectorId will be null; when Company is fetched from the DB, SectorId will be populated with the given formula value. The SectorId is exposed as a property, which makes it really nice for dealing with web drop downs, etc. When you save, you'll still need to bind the "real" association. Assuming that SectorId was selected in a form...

using (var txn = session.BeginTransaction()) {
  // Set the FK reference.
  company.Sector = session.Load<Sector>(company.SectorId);
  // Save the new record.
  session.Save(company);
  // Commit the transaction.
  txn.Commit();
}
Jarrett Meyer
  • 19,333
  • 6
  • 58
  • 52
  • I give you points for solving the error I was getting, but this seems completely non-intuitive. It seems like a design problem in NHibernate if it cant handle such a common scenario as having both the FK ID and the associated object-instance as properties. – Lance Hunt Feb 21 '11 at 21:02
3

Steve you don't need ForeignKey property in POCO class.

For example if you will try to get id of article author no join select will be performed.

var authorID = Article.Author.ID

Alexey Zakharov
  • 24,694
  • 42
  • 126
  • 197
2

Two thoughts: First of all, wouldn't something like this accomplish what you want?

public class Company {
    public Guid ID { get; set; }
    public Sector Sector { get; set; }
    public Guid SectorID {
        get { return Section.ID; }
        // Really not sure what behavior your setter should have here; Maybe it shouldn't even have one?
        set { Sector = new Sector { ID = value }; }
    }
}

Second, when you say that the mapping created a column in the DB called Sector_Id, is that in addition to a column that you created named SectorID? If so, you can change the column name so it uses the correct name (here's the documentation for mappings, see a few headings down "Specifying the column name").

Also, are you mapping the SectorID property (eg. "Map(x => x.SectorID, "Sector_Id")")?

Chris Shaffer
  • 32,199
  • 5
  • 49
  • 61