dragonfly
dragonfly

Reputation: 17773

Cannot delete child entities in one-to-many relationship in NHibernate

I have a test code:

        using (var session = factory.OpenSession())
        {
            var animalsCategory = session.Query<Category>().Where(c => c.Name == "Animals").Single();
            Assert.AreEqual(3, animalsCategory.Products.Count());
            animalsCategory.Products.ForEach(x => session.Delete(x));
            session.Flush();
        }

and mappings for Category & Product classes:

public class ProductMap : ClassMap<Product>
{
    public ProductMap()
    {
        Id(x => x.ID);
        Map(x => x.Name).Not.Nullable().Length(50);
        Map(x => x.Description).Length(4000);
        Map(x => x.UnitPrice).Not.Nullable();
        Map(x => x.ReorderLevel).Not.Nullable();
        Map(x => x.Discontinued);

        References(x => x.Category).Not.Nullable();
    }
}

public class CategoryMap : ClassMap<Category>
{
    public CategoryMap()
    {
        Id(x => x.ID);
        Map(x => x.Name);
        Map(x => x.Description);
        HasMany<Product>(x => x.Products).Inverse().Cascade.AllDeleteOrphan();
    }
}

Also, here are classes:

public class Product : Entity<Product>
{
    public virtual string Name { get; set; }
    public virtual string Description { get; set; }
    public virtual Category Category { get; set; }
    public virtual decimal UnitPrice { get; set; }
    public virtual int ReorderLevel { get; set; }
    public virtual bool Discontinued { get; set; }
}

public class Category : Entity<Category>
{
    private List<Product> products = null;

    public virtual string Name { get; set; }
    public virtual string Description { get; set; }
    public virtual IEnumerable<Product> Products { get { return products; } }
}

And now, when I run test code I get an exception:

N*Hibernate.ObjectDeletedException : deleted object would be re-saved by cascade (remove deleted object from associations)[Model.Product#1]*

which is fine, and I think I get it :)

So I have created a method inside Category class:

    public virtual void ClearProducts()
    {
        products.Clear();
    }

and I've changed my test code to the following:

        using (var session = factory.OpenSession())
        {
            var animalsCategory = session.Query<Category>().Where(c => c.Name == "Animals").Single();
            Assert.AreEqual(3, animalsCategory.Products.Count());
            //animalsCategory.Products.ForEach(x => session.Delete(x));
            animalsCategory.ClearProducts();
            session.Flush();
        }

I've replaced ForEach with ClearProducts call. And now I get exception:

System.IndexOutOfRangeException : Index was outside the bounds of the array at System.Array.Clear(Array array, Int32 index, Int32 length) at System.Collections.Generic.List`1.Clear()

I've also used while(products.Count >0) loop to remove all products from collection, but I also got an exception. Can somebody tell me how can I remove child objects if I cannot remove them from collection?

Thanks

EDIT 1: I'm stunned... I've just changed type of collection (and field definition) from:

    private List<Product> products = null;

to:

    private IList<Product> products = null;

and code works. Could somebody please tell my why?

Upvotes: 2

Views: 2228

Answers (1)

Miroslav Popovic
Miroslav Popovic

Reputation: 12128

Problem #1 - you can't just directly delete objects from the child collection. In order to delete them, you would remove an item from Products, and then save the parent Category object. So in DDD terms, you should only perform delete on aggregate root object, but you have already figured that out.

Problem #2 - if you are using lazy loading, you need to have virtual keyword on your public instance members and use interfaces for collection, so that NHibernate can replace your properties with proxies that know how to lazy load when first accessed. Here's a similar question with a good answer.

Are you sure your Category and CategoryMap classes now look like that? Not sure how would the mapping like you have there work, since you don't have a setter on your public property, nor you have defined different access strategy in mapping.

Upvotes: 2

Related Questions