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