Pergunta

I've a simple category class. Each category can have zero or more children of type category. Therefore I have chosen to register the parent of a category, which can be null or the id of another category. When a category is deleted, all its children (of type category) must be deleted as well.

When the schema of the table is created by NHibernate, the foreign key exists, but On Delete: is set to RESTRICT. NOTE: When I manually change On Delete: to CASCADE all works as expected.

I've read that this is a wrong approach to relate the child to a parent. I do not see why. Hence, it works. But NHibernate doesn't configure the On Delete how I would like to.

I've read about inverse collections and bags, and using separate tables. But I am confused about what my approach should be.

How would one achieve the on delete cascade with this category class, and why?

My mapping looks like this:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="BackEnd"
                   namespace="BackEnd.Models">
  <class name="Category">
    <id name="Id">
      <generator class="native"/>
    </id>
    <property name="Name" />
    <many-to-one class="Category" name="Parent" cascade="delete"/>
    <property name="Description" />
  </class>
</hibernate-mapping>
Foi útil?

Solução

This is how its possible to map a parent child relationship in the same table, listing all childeren to your object, and deleting all childs when a parent is deleted.

I finally got this by trial and error. It took me 15 attempts.

    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                       assembly="BackEnd"
                       namespace="BackEnd.Models">
      <class name="Category">
        <id name="Id">
          <generator class="native"/>
        </id>
        <property name="Name" />
        <property name="ParentId" />
        <bag name="Children" table="Category" inverse="true" cascade="all-delete-orphan">
          <key column="ParentId" on-delete="cascade"/>
          <one-to-many class="Category"/>
        </bag>
        <property name="Description" />
      </class>
    </hibernate-mapping>

This is the code for the class I use.

/// <summary>
/// The ModelBase takes care of the CRUD (Create, Read, Update, Delete) functionalities for each derived class.
/// public functions here must be virtual, read this: http://thatextramile.be/blog/2009/03/must-everything-be-virtual-with-nhibernate/
/// This is an abstract class as it has no purpose on its own.
/// </summary>
public abstract class ModelBase<T>
{

    /// <summary>
    /// The id by which this transient object is related to a persistent object in database.
    /// </summary>
    public virtual ulong Id { get; set; }


    /// <summary>
    /// Many objects, like categories, issues, etc, can have a parent of the same type
    /// </summary>
    public virtual ulong ParentId { get; set; }

    /// <summary>
    /// The childeren of this object, if any
    /// </summary>
    public virtual IList<T> Children
    {
        get;
        set;
    }

    /// <summary>
    /// Constructor only available for derived classes.
    /// </summary>
    protected ModelBase()
    {
    }

    /// <summary>
    /// Creates or updates this object in database.
    /// </summary>
    public virtual void CreateOrUpdate()
    {
        using(ISession Session = Gate.SessionFactory.OpenSession())
        {
            Session.SaveOrUpdate(((T)((Object)this)));
            Session.Flush();
            Session.Close();
        }
    }

    /// <summary>
    /// Deletes this object from database
    /// </summary>
    public virtual void Delete()
    {
        using (ISession Session = Gate.SessionFactory.OpenSession())
        {
            Session.Delete(((T)((Object)this))); //Needs to be casted as the type of the top most class, or it will fail to get its properties.
            Session.Flush();
            Session.Close();
        }
    }

    /// <summary>
    /// Loads a persistent object (from database) in to a transienst object (variable).
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="Id">The Id of the object by which it is known in database.</param>
    /// <returns>A strong typed object.</returns>
    public static T Load(ulong Id)
    {
        /* TODO: Lazy loading only possible when sessinos remains open.
         * Solve this by adding some kind of OnDispose event that automatically closes the connection when the object is disposed.
         */
        //using (ISession Session = Gate.SessionFactory.OpenSession())
        //{
        //    ModelObject = Session.Load<T>(Id);
        //    Session.Close();
        //}
        ISession Session = Gate.SessionFactory.OpenSession();
        return Session.Load<T>(Id);
    }
}

And finally the category class that is derived from the base class

/// <summary>
/// A part can be categorized, under one or more, categories.
/// Each category is an instance of this class.
/// </summary>
public class Category : ModelBase<Category>
{
    /// <summary>
    /// The name of the category that is displayed to the user.
    /// </summary>
    public virtual String Name { get; set; }    

    /// <summary>
    /// A description of what this category is about.
    /// </summary>
    public virtual String Description { get; set; }

    /// <summary>
    /// The constructor creates a new category object.
    /// </summary>
    public Category()
    {
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top