R – NHibernate: Many to Many relationship not working

nhibernate

I have the following database schema:

http://lh4.ggpht.com/_SDci0Pf3tzU/SdM3XnAmmxI/AAAAAAAAEps/Ie3xW3ZVNfQ/s400/styleerror.png

And this is my mapping file:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="CodeSmithSampel.Generated.BusinessObjects" assembly="CodeSmithSampel">
    <class name="CodeSmithSampel.Generated.BusinessObjects.Store, CodeSmithSampel" table="store" lazy="true">
        <id name="Id" column="Id">
            <generator class="native" />
        </id>
        <property name="Name" column="Name" />
        <bag name="Employees" lazy="true" cascade="all-delete-orphan" inverse="true" >
            <key column="Store_id"></key>
            <one-to-many class="Employee"></one-to-many>
        </bag>
        <bag name="Products" table="storeproduct" lazy="true" cascade="all" inverse="true" >
            <key column="Store_id"></key>
            <many-to-many column="Product_id" class="Product" />
        </bag>
    </class>
</hibernate-mapping>

And ths is my Store entity class:

public partial class Store : BusinessBase<int>
{
    #region Declarations

        private string _name = String.Empty;


        private IList<Employee> _employees = new List<Employee>();
        private IList<Product> _products = new List<Product>();

        #endregion

    #region Constructors

    public Store() { }

    #endregion

    #region Methods

    public override int GetHashCode()
    {
        System.Text.StringBuilder sb = new System.Text.StringBuilder();

        sb.Append(this.GetType().FullName);
            sb.Append(_name);

        return sb.ToString().GetHashCode();
    }

    #endregion

    #region Properties

        public virtual string Name
    {
        get { return _name; }
            set
            {
                OnNameChanging();
                _name = value;
                OnNameChanged();
            }
    }
        partial void OnNameChanging();
        partial void OnNameChanged();

        public virtual IList<Employee> Employees
    {
        get { return _employees; }
        set
            {
                OnEmployeesChanging();
                _employees = value;
                OnEmployeesChanged();
            }
    }
        partial void OnEmployeesChanging();
        partial void OnEmployeesChanged();

        public virtual IList<Product> Products
    {
        get { return _products; }
        set
            {
                OnProductsChanging();
                _products = value;
                OnProductsChanged();
            }
    }
        partial void OnProductsChanging();
        partial void OnProductsChanged();

    #endregion
}

The product class:

   public partial class Product : BusinessBase<int>
    {
        #region Declarations

        private float _price = default(Single);
        private string _name = null;


        private IList<Store> _stores = new List<Store>();

        #endregion

        #region Constructors

        public Product() { }

        #endregion

        #region Methods

        public override int GetHashCode()
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();

            sb.Append(this.GetType().FullName);
            sb.Append(_price);
            sb.Append(_name);

            return sb.ToString().GetHashCode();
        }

        #endregion

        #region Properties

        public virtual float Price
        {
            get { return _price; }
            set
            {
                OnPriceChanging();
                _price = value;
                OnPriceChanged();
            }
        }
        partial void OnPriceChanging();
        partial void OnPriceChanged();

        public virtual string Name
        {
            get { return _name; }
            set
            {
                OnNameChanging();
                _name = value;
                OnNameChanged();
            }
        }
        partial void OnNameChanging();
        partial void OnNameChanged();

        public virtual IList<Store> Stores
        {
            get { return _stores; }
            set
            {
                OnStoresChanging();
                _stores = value;
                OnStoresChanged();
            }
        }
        partial void OnStoresChanging();
        partial void OnStoresChanged();

        #endregion
    }

The mapping for the Product class:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="CodeSmithSampel.Generated.BusinessObjects" assembly="CodeSmithSampel">
    <class name="CodeSmithSampel.Generated.BusinessObjects.Product, CodeSmithSampel" table="product" lazy="true">
        <id name="Id" column="Id">
            <generator class="native" />
        </id>
        <property name="Price" column="Price" />
        <property name="Name" column="Name" />
        <bag name="Stores" table="storeproduct" lazy="true" cascade="all" inverse="true" >
            <key column="Product_id"></key>
            <many-to-many column="Store_id" class="Store" />
        </bag>
    </class>
</hibernate-mapping>

What is particularly weird is that when I add a Store object to one of the product, the database record is not updated; the add doesn't seem to take place, although the new store object exists in the database:

        IManagerFactory managerFactory = new ManagerFactory();
        var productManager = managerFactory.GetProductManager();

        var myProduct= productManager.GetById(2);

        var myStore = new Store();
        myStore.Name = "new Store";  //a "new store" entry is created in the Store table
        myProduct.Stores.Add(myStore);  // but this "new store" is not linked to the myproduct, as it should.

       productManager.Session.CommitChanges();

Is there anything I miss?

Note: I generate the above code using CodeSmith.

Edit: The accepted answer works. The reason I got in this problem is because

  1. Only one entity class should have inverse = true, not two. So either Product or Store should set the inverse to false. The code generation tool didn't handle this properly.
  2. The correct way to Add Many to Many relationship is explained below. You must add two times.

Best Answer

Can this have anything to do with the fact that you have a surrogate key in the storeproducts table ?

What happens if you remove this surrogate key column Id, and put the primary key on the combination of the product_id and store_id columns ?

I believe that, if you want to have a surrogate key on the storeproducts table, you'll have to create yet another entity.

If you want to use the surrogate key, you'll have to use the idbag mapping.

How does your Product class and mapping look like ? I see that you specify the 'inverse' attribute in your mapping of the Products collection in the Store entity.

If you do this (and thus you have a bi-directional association), then you should add the Store to the Stores collection of the product as well. Since -from the NH documentation- :

Changes made only to the inverse end of the association are not persisted. This means that NHibernate has two representations in memory for every bidirectional association, one link from A to B and another link from B to A. This is easier to understand if you think about the .NET object model and how we create a many-to-many relationship in C#:

category.Items.Add(item);          // The category now "knows" about the relationship
item.Categories.Add(category);     // The item now "knows" about the relationship

session.Update(item);                     // No effect, nothing will be saved!
session.Update(category);                 // The relationship will be saved

The non-inverse side is used to save the in-memory representation to the database. We would get an unneccessary INSERT/UPDATE and probably even a foreign key violation if both would trigger changes! The same is of course also true for bidirectional one-to-many associations.

You may map a bidirectional one-to-many association by mapping a one-to-many association to the same table column(s) as a many-to-one association and declaring the many-valued end inverse="true".

This means, that only one of the ends should be inverse. Adding a Product to a store, should be done like this:

public class Store
{
   public void AddProduct( Product p )
   {
       if( _products.Contains (p) == false )
       {
             _products.Add (p);
             p.AddStore(this);
       }
   }
}
public class Product
{
    public void AddStore( Store s )
    {
       if( _stores.Contains (s) == false )
       {
          _stores.Add (s);
          s.AddProduct(this);
       }
    }
}

(Very important to check whether the collection already contains the item to be added; otherwise you'll end up in an infinite loop.

Related Topic