views:

4112

answers:

3

I have to map two simple table with a foreign key relationship. One of the tables is Contact containing columns id (primary key of type int),name, address and guid (newly added and is not the primary key). The other one is phone__number containing columns id (primary key of type int), contact___id (foreign key of id in contact table) and phone__number.

The mapping file for Contact table is as below :

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="OfflineDbSyncWithNHibernate" default-lazy="true" namespace="OfflineDbSyncWithNHibernate.Models">
  <class name="Contact" table="Contact">
    <id name="Id" column="Id" type="int">
      <generator class="native" />
    </id>

    <property name="Name" column="name" type="string"/>
    <property name="Address" column="address" type="string"/>
    <property name="Guid" column="guid" type="string"/>

    <set lazy="true" batch-size="6" table="phone_number" name="PhoneNumbers" fetch="join" inverse="false" cascade="all" >
      <key foreign-key="FK_contact_phone_number" column="contact_id"/>
      <one-to-many class="PhoneNumber" />
    </set>

  </class>
</hibernate-mapping>

The mapping file for Phone_number table is :

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="OfflineDbSyncWithNHibernate" default-lazy="true" namespace="OfflineDbSyncWithNHibernate.Models">
  <class name="PhoneNumber" table="phone_number">
    <id name="Id" column="Id" type="int">
      <generator class="native" />
    </id>
    <property name="ContactId" column="contact_id" />
    <property name="Number" column="phone_number" />
  </class>
</hibernate-mapping>

The Contact and PhoneNumber classes are :

namespace OfflineDbSyncWithNHibernate.Models
{
    public class Contact
    {
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        public virtual string Address { get; set; }
        public virtual string Guid { get; set; }
        public virtual PhoneNumbers PhoneNumbers { get; set; }
    }
}

namespace OfflineDbSyncWithNHibernate.Models
{
    public class PhoneNumber
    {
        public virtual int Id { get; set; }
        public virtual int ContactId { get; set; }
        public virtual string Number { get; set; }
    }
}

namespace OfflineDbSyncWithNHibernate.Models
{
    public class PhoneNumbers : List<PhoneNumber>
    {
    }
}

When I load the contact and phone_numbers separately it works, but after adding the set element to get a one-to-many relationship nhibernate is giving an error :

NHibernate.MappingException: Invalid mapping information specified for type OfflineDbSyncWithNHibernate.Models.Contact, check your mapping file for property type mismatches

I am new to nHibernate so I am not sure if there is a mistake in the set element or I should not even be using it. Any help will be appreciated.

+2  A: 

I think your PhoneNumbers class needs to inherit from a subtype of Iesi.Collections.ISet. I don't think there is a "Set" type provided in .NET by default. See this page on the hibernate FAQ:

http://www.hibernate.org/359.html

The <set> maps to an Iesi.Collections.ISet. That interface is part of the Iesi.Collections assembly distributed with NHibernate.

Andy White
Thanks I did not know that. But I dont intend to use a Set here. I want a List<PhoneNumber>. In that case which element should I use for mapping.
Nazgul
I think the <list> mapping should work
Andy White
No, when you want a List, you'll have to use a <bag> mapping, since <list> represents an ordered collection if i'm not mistaken.However, a bag allows you to have multiple instances of the same type in your collection, and this can cause strange things that you won't expect
Frederik Gheysels
Ah yes, that's right. I forgot about the ordering with list. I usually just use bag I guess.
Andy White
Try changing your "set" to "bag" in the mapping and change your PhoneNumbers property to IList<PhoneNumber>. You may not need the PhoneNumbers class.
Andy White
+1  A: 

Your collection-type should be an interface, because NHibernate will provide it's own type that implements that interface when an object is retrieved from the DB.

If you define your collection as

public virtual ISet<PhoneNumber> Phonenumbers = new HashedSet<Phonenumber>();

Then I think it will work.

In order to better control the access to your collection, you can modify your Contact class like this:

public class Contact
{
    public virtual int Id {get;set;}
    ..

    private ISet<Phonenumber> _phoneNumbers = new HashedSet<PhoneNumber>();

    public ReadOnlyCollection<Phonenumber> PhoneNumbers
    {
        get 
        {
           return new List<Phonenumber>(_phoneNumbers).AsReadOnly();
        }
    }

    public void AddPhonenumber( Phonenumber n )  
    {
        n.Contact = this;
        _phoneNumbers.Add(n);
    }

    public void RemovePhoneNumber( PhoneNumber n )
    {
        ...
    }
}

Then, you have to make sure that in your mapping of the Contact class, you specify that NHibernate should access the field _phoneNumbers instead of the property PhoneNumber:

<set name="PhoneNumbers" access="field.camelcase-underscore" ... >
   ...
</set>
Frederik Gheysels
+3  A: 

Just remeber this

  • Bag is implemented using IList
  • Set is implemented using ISet
  • List is implemented using ArrayList or List
  • Map is implemented using HashedTable or IDictionary

If you want to use IList use first rule i.e change your hbm.xml to use Bag instead of Set also your Phonenumbers class should inherit from IList not List, if you want to use List you will need to change your mapping file to use List instead of Set.

Brijesh Mishra