Deleting unreferenced child records with nhibernate
- by Chev
Hi There
I am working on a mvc app using nhibernate as the orm (ncommon framework) 
I have parent/child entities: Product, Vendor & ProductVendors and a one to many relationship between them with Product having a ProductVendors collection Product.ProductVendors.
I currently am retrieving a Product object and eager loading the children and sending these down the wire to my asp.net mvc client.
A user will then modify the list of Vendors and post the updated Product back. I am using a custom model binder to generate the modified Product entity. I am able to update the Product fine and insert new ProductVendors.
My problem is that dereferenced ProductVendors are not cascade deleted when specifying Product.ProductVendors.Clear() and calling _productRepository.Save(product).
The problem seems to be with attaching the detached instance. Here are my mapping files:
Product
<?xml version="1.0" encoding="utf-8" ?>
  
<id name="Id">
  <generator class="guid.comb" />
</id>
<version name="LastModified"
                unsaved-value="0"
                column="LastModified"
                 />
<property name="Name" type="String" length="250" />
ProductVendors
    
      
      
    
<?xml version="1.0" encoding="utf-8" ?>
  
<id name="Id">
  <generator class="guid.comb" />
</id>
<version name="LastModified"
                unsaved-value="0"
                column="LastModified"
                 />
<property name="Price" />
<many-to-one
  name="Product"
  class="Product"
  column="ProductId"
  lazy="false"
  not-null="true"
   />
<many-to-one
 name="Vendor"
 class="Vendor"
 column="VendorId"
 lazy="false"
 not-null="true"
   />
Custom Model Binder:
using System;
using Test.Web.Mvc;
using Test.Domain;
namespace Spoked.MVC
{
    public class ProductUpdateModelBinder : DefaultModelBinder
    {
        private readonly ProductSystem ProductSystem;
    public ProductUpdateModelBinder(ProductSystem productSystem)
    {
        ProductSystem = productSystem;
    }
    protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var product = bindingContext.Model as Product;
        if (product != null)
        {
            product.Category = ProductSystem.GetCategory(new Guid(bindingContext.ValueProvider["Category"].AttemptedValue));
            product.Brand = ProductSystem.GetBrand(new Guid(bindingContext.ValueProvider["Brand"].AttemptedValue));
            product.ProductVendors.Clear();
            if (bindingContext.ValueProvider["ProductVendors"] != null)
            {
                string[] productVendorIds = bindingContext.ValueProvider["ProductVendors"].AttemptedValue.Split(',');
                foreach (string id in productVendorIds)
                {
                    product.AddProductVendor(ProductSystem.GetVendor(new Guid(id)), 90m);
                }
            }
        }
    } 
}
}
Controller:
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Update(Product product)
    {
        using (var scope = new UnitOfWorkScope())
        {
            //product.ProductVendors.Clear();
            _productRepository.Save(product);
            scope.Commit();
        }
        using (new UnitOfWorkScope())
        {
            IList<Vendor> availableVendors = _productSystem.GetAvailableVendors(product);
            productDetailEditViewModel = new ProductDetailEditViewModel(product,
                                                                        _categoryRepository.Select(x => x).ToList(),
                                                                        _brandRepository.Select(x => x).ToList(),
                                                                        availableVendors);
        }
        return RedirectToAction("Edit", "Products", new {id = product.Id.ToString()});
    }
The following test does pass though:
 [Test]
    [NUnit.Framework.Category("ProductTests")]
    public void Can_Delete_Product_Vendors_By_Dereferencing()
    {
        Product product;
        using(UnitOfWorkScope scope = new UnitOfWorkScope())
        {
            Console.Out.WriteLine("Selecting...");
            product = _productRepository.First();
            Console.Out.WriteLine("Adding Product Vendor...");
            product.AddProductVendor(_vendorRepository.First(), 0m);
            scope.Commit();
        }
        Console.Out.WriteLine("About to delete Product Vendors...");
        using (UnitOfWorkScope scope = new UnitOfWorkScope())
        {
            Console.Out.WriteLine("Clearing Product Vendor...");
            _productRepository.Save(product); // seems to be needed to attach entity to the persistance manager
            product.ProductVendors.Clear();
            scope.Commit();
        }
    }
Going nuts here as I almost have a very nice solution between mvc, custom model binders and nhibernate. Just not seeing my deletes cascaded. Any help greatly appreciated.
Chev