Search code examples
c#asp.net-mvcnhibernatenhibernate-mapping

Duplicate collection role mapping nHibernate - When trying to make one - many mapping


I am learning nHibernate and I was trying one to many mapping. Here are the two tables Product and Product Type.

namespace NHibernateSample.Models
{
    public class Product
    {
        public virtual Guid Id { get; set; }
        public virtual string Name { get; set; }
        public virtual string Category { get; set; }
        public virtual bool Discontinued { get; set; }
        public virtual IList<ProductType> ProductTypes { get; set; }
    }
}

Here are my mapping XMLs

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
               assembly="NHibernateSample"
               namespace="NHibernateSample.Models">

  <!-- more mapping info here -->
  <class name="Product">
    <id name="Id">
      <generator class="guid" />
    </id>
    <property name="Name" />
    <property name="Category" />
    <property name="Discontinued" />

    <bag name="ProductTypes">
      <key column="ProductID" />
      <one-to-many class="NHibernateSample.Models.ProductType,NHibernateSample" />
    </bag>
  </class>
</hibernate-mapping>

Product type xml:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
               assembly="NHibernateSample"
               namespace="NHibernateSample.Models">

  <!-- more mapping info here -->
  <class name="ProductType">
    <id name="ProductTypeID">
      <generator class="increment"/>
    </id>
    <property name="ProductType1" column="ProductType"/>
    <property name="ProductID" />
    <many-to-one name="Product" class="Product">
      <column name="ProductID" sql-type="int" not-null="true"/>
    </many-to-one>
  </class>
</hibernate-mapping>

The mappings are in the config

mapping assembly="NHibernateSample" /> 

When I try configuring and build session factory

var cfg = new Configuration();
cfg.Configure();
m_SessionFactory = cfg.BuildSessionFactory();

I get the error saying "Association references unmapped class: NHibernateSample.Models.ProductType" but I see the hbm file in the bin folder.

If I add the assembly explicitly,

Assembly thisAssembly = typeof(Product).Assembly;
cfg.AddAssembly(thisAssembly);

I get another error saying "Duplicate collection role mapping NHibernateSample.Models.Product.ProductTypes"

What am I doing wrong ? Is it not the way I should map the foreign keys ? Thanks in advance


Solution

  • The first issue seems to be related to wrong setting of the .hbm.xml file.. which always must have (see e.g. MappingException: No persister for - NHibernate - Persisting an Adapter)

    • xml mapping file is NOT makred as Embedded Resource
    • xml file is not part of .dll which is configured as the mapping source <mapping assembly="MyProject.Data" /> (see configuration)
    • xml file does not have the default suffix .hbm.xml

    The second question (in comment)

    Here I insert a product. I want to insert some ProductType to the database like prod.ProductTypes = new .... How can I do that

    Product prod = new Product(); 
    prod.Name = "Q3"; prod.Category = "Audi"; 
    prod.Discontinued = false; 
    session.Save(prod);  
    ...
    

    solution is to adjust the mapping of the collection to be using cascading:

    <class name="Product">
        <id name="Id">
          <generator class="guid" />
        </id>
        <property name="Name" />
        <property name="Category" />
        <property name="Discontinued" />
    
        <bag name="ProductTypes" 
          lazy="true" inverse="true" batch-size="25" cascade="all-delete-orphan"
         >
          <key column="ProductID" />
          <one-to-many class="NHibernateSample.Models.ProductType,NHibernateSample" />
        </bag>
    
    </class>
    

    (what are all these settings on the <bag> - check here)

    I would adjust this POCO defintion

    public class Product
    {
        public virtual Guid Id { get; set; }
        public virtual string Name { get; set; }
        public virtual string Category { get; set; }
        public virtual bool Discontinued { get; set; }
    
        //public virtual IList<ProductType> ProductTypes { get; set; }
        IList<ProductType> _productTypes;
        public virtual IList<ProductType> ProductTypes
        {
            get { return _productTypes ?? (_productTypes = new List<ProductType>()); }
            set { _productTypes = value; }
        }
    }
    

    (that is just to be sure that list is initiated either by NHibernate on load or by us in other cases)

    and then we just have to assign both sides

     // product
     Product prod = new Product(); 
     prod.Name = "Q3"; prod.Category = "Audi"; 
     prod.Discontinued = false; 
    
     // product type
     ProductType productType = new ProudctType();
     ...
    
     // both sides assigned
     prod.ProductTypes.Add(productType);
     productType.Product = prod;
    
     session.Save(prod);  
    

    And we also should adjust the mapping to be readonly for value type property

    <property name="ProductID" insert="false" update="false" />
    <many-to-one name="Product" class="Product">
      <column name="ProductID" sql-type="int" not-null="true"/>
    </many-to-one>
    

    To get more details I would not miss this: