Implementing a soft-delete pattern

Sometimes you do not want to delete the information from the database, but instead to mark it as deleted. This technique is called soft-delete. In this recipe, we will show you how to implement a soft-delete pattern with NHibernate.

How to do it…

  1. Create a new class library project named SoftDeleteExample.
  2. Install the NHibernate package using the NuGet Package Manager Console by executing the following command:
    Install-Package NHibernate
    
  3. Add the ISoftDeletable interface using the following code:
    public interface ISoftDeletable
    {
      bool IsDeleted { get; }
      DateTime? DeletedAt { get; }
    }
  4. Add an App.config with a standard NHibernate configuration.
  5. Just before the end of the sessionfactory element, add the following three event elements:
    <event type="delete">
      <listener class=" SoftDeleteExample.SoftDeleteEventListener, 
                SoftDeleteExample" />
    </event>
  6. Add the following EventListener:
    public class SoftDeleteEventListener :
      DefaultDeleteEventListener
    {
      protected override void DeleteEntity(
        IEventSource session,
        object entity,
        EntityEntry entityEntry,
        bool isCascadeDeleteEnabled,
        IEntityPersister persister,
        ISet<object> transientEntities)
      {
        var deletable = entity as ISoftDeletable;
        if (deletable != null)
        {
          deletable.IsDeleted = true;
          deletable.DeletedAt = DateTime.UtcNow;
    
          CascadeBeforeDelete(
            session,
            persister,
            deletable,
            entityEntry,
            transientEntities);
    
          CascadeAfterDelete(
            session,
            persister,
            deletable,
            transientEntities);
        }
        else
        {
          base.DeleteEntity(
            session,
            entity,
            entityEntry,
            isCascadeDeleteEnabled,
            persister,
            transientEntities);
        }
      }
    }
  7. Add the Order and OrderLine classes using the following code:
    public class Order : ISoftDeletable
    {
      public Order()
      {
        OrderLines = new HashSet<OrderLine>();
      }
    
      Guid Id { get; set; }
    
      public virtual DateTime OrderDate { get; set; }
      public virtual ISet<OrderLine> OrderLines { get; set; }
    
      public virtual bool IsDeleted { get; set; }
      public virtual DateTime? DeletedAt { get; set; }
    }
    public class OrderLine : ISoftDeletable
    {
      Guid Id { get; set; }
    
      public virtual string ProductName { get; set; }
      public virtual int Amount { get; set; }
      public virtual bool IsDeleted { get; set; }
      public virtual DateTime? DeletedAt { get; set; }
    }
  8. Add a mapping document with the following XML. Don't forget to set Build Action to Embedded Resource:
    <?xml version="1.0" encoding="utf-8"?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                       assembly="SoftDeleteExample"
                       namespace="SoftDeleteExample">
      <class name="Order" table="`Order`" where="IsDeleted = 0">
        <id name="Id">
          <generator class="guid.comb" />
        </id>
        <property name="OrderDate" not-null="true" />
        <property name="IsDeleted" not-null="true"/>
        <property name="DeletedAt" />
        <set name="OrderLines" cascade="all-delete-orphan" where="IsDeleted = 0">
          <key column="OrderId"/>
          <one-to-many class="OrderLine"/>
        </set>
      </class>
      <class name="OrderLine" where="IsDeleted = 0">
        <id name="Id">
          <generator class="guid.comb" />
        </id>
        <property name="ProductName" not-null="true" />
        <property name="Amount" not-null="true" />
        <property name="IsDeleted" not-null="true"/>
        <property name="DeletedAt" />
      </class>
    </hibernate-mapping>

How it works…

The delete event listener works similarly to the event listeners in the Creation and change stamping of entities recipe. The code in the overriden DeleteEntity method first checks whether the entity implements the ISoftDeletable interface. If not, the call is just forwarded to the base class for the default execution (that is, the entity will be physically deleted from the system). However, if the entity implements the interface, we set its IsDeleted property to true and DeletedAt to the current UTC time, and call the CascadeBeforeDelete and CascadeAfterDelete methods of the base class.

We also use the where condition on the class and set definitions, to restrict loading of the deleted entities.

There's more…

Instead of using the where conditions, you can use the filters feature, which is more sophisticated and allows more control over the loading of the entities. You can enable or disable filters as necessary, for example to allow loading deleted entities, if needed.

See also

  • Creating an audit-event listener
  • Creation and change stamping of entities
  • Generating trigger-based auditing
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.62.122