Showing posts with label ORM. Show all posts
Showing posts with label ORM. Show all posts

2009-12-09

"Soft Delete" (aka "Logical Delete") in ORM

People often ask which ORM supports "Soft Delete" or "Logical Delete". Let's try to figure out what is it and whether ORM should internally support it.

"Soft Delete" is a way of removing business entities, which implies that we mark an entity as deleted instead of physical removing it from database. This approach is often used in Line of Business applications because of its several advantages:
  • It allows us to keep history for different  auditing sceneries. For example, somebody removed one document in the past from our workflow system, we surely want to be able to audit removing log and data removed document contained.
  • It allows to implement Recycle Bin approach in easy way. We'd like to be able to recycle any document removed in the past.
To implement "Soft Delete" feature in a simple case we should:
  • Create IsDeleted persistent property of bool type in all softly removable types.
  • Automatically filter all queries, i.e. automatically add Where(entity => !entity.IsDeleted) to each LINQ query.
In my example on DataObjects.Net I use single base class for all business objects, so I can add IsDeleted field to this class:

public class BusinessObject : Entity
{
  [Field]
  public bool IsDeleted { get; set;}

  public new void Remove()
  {
    IsDeleted = true;
  }
}
Then let's create DataContext class responsible for data access:

public static class DataContext
{
  public static IQueryable<T> GetAll<T>()
    where T : BusinessObject
  {
    return Query<T>.All
      .Where(entity => !entity.IsDeleted);
  }
}

Now we can softly remove our entities and query not removed ones. I've created small sample illustrating model of blog-publishing service, consisting of three classes: Blog, BlogPost and Comment. So I can query recent posts from my blog using such LINQ-query:

from post in DataContext.GetAll<BlogPost>()
where
  post.PublishDate > DateTime.Now-TimeSpan.FromDays(7) &&
  post.Blog == myBlog
select post;

This query will return all posts except softly deleted and I don't have to add appropriate check to every query in my application.

Generally, I am sure that "Soft Delete" shouldn't be internally implemented within an ORM framework, because it's easy to implement it yourself and your own implementation will be more flexible than built-in. Why flexibility is important? In my example I've chosen the simplest way of its implementation. In real-life scenarios there are many aspects connected with soft deletion in specific ways, for example:
  • Security: You may want to define who has a permission to access removed entities, etc...
  • Entities dependency: You may want to consider some entities as deleted when their dependency parent is deleted, so you need to specify more complex filter on queries.

2009-11-23

Auto-transactions API

Which auto-transactions API I'd like to have in ideal ORM?

In a typical business application we have a set of session-bound classes, for example our business classes, services, etc... Each method, property or constructor in these classes can be either transactional or not transactional. We should decide which of them will be transactional and inform ORM about this, using a set of attribute-based rules. I'd like these attributes to allow me to specify following rules:
  • All members of this class are not transactional by default
  • All public methods of this class and all its inheritors are transactional by default
  • This method requires new nested transaction even if some transaction is already open
  • This member doesn't require transaction
  • This method requires transaction with "ReadCommited" isolation level
Moreover I'd like API to be very simple and intuitive. It should allow to define default behavior and manage transactional behavior in flexible way.

When I starting to implement my model and business logic layer, I decide which policy to use. I can make all public members in all classes of my model transactional by marking base class as "Transactional", and then mark some members as not transactional by appropriate attribute. On other hand I can make everything not transactional, except public methods in some service classes, and open transactions manually each time I need them. I can use third approach, for example make methods transactional, but properties not. It depends on my solution architecture, and it's impossible to find universal right way.

So I think we need something like this:

[Transactional] attribute that can be applied to a session-bound class or its member. Attribute should have following options:
  • Should this attribute be applied to inheritors or not
  • Which members it should be applied to (methods, properties, constructors)
  • Visibility of members this attribute should be applied to (public, internal, protected)
  • Are members this attribute applied to transactional or not. Another alternative is to mark not transactional members by special [NotTransactional] attribute
  • Required isolation level for transaction
  • Whether new transaction is required or any existing one is enough.
There is a small example that illustrates how I'd like it to work. In this example I've made all entities not transactional and all public methods of DocumentService - transactional by default.

  1. [NotTransactional(InheritBehavior = true)]
  2. public class MyEntityBase : Entity
  3. {
  4. }
  5.  
  6. public class Document : Entity
  7. {
  8.   [Transactional]
  9.   public void BusinessMethod()
  10.   {      
  11.   }
  12.  
  13.   [Transactional(Options = TransactionOptions.New,
  14.     IsolationLevel = IsolationLevel.Serializable)]
  15.   public void AnotherBusinessMethod()
  16.   {      
  17.   }
  18. }
  19.  
  20. [Transactional(
  21.   MembersType = MemberTypes.Method,
  22.   MembersVisibility = Visibility.Public)]
  23. public class DocumentService : SessionBound
  24. {
  25.   public Document FindLastDocument()
  26.   {
  27.   }
  28.  
  29.   public void RemoveAllDocuments()
  30.   {
  31.   }
  32.  
  33.   [NotTransactional]
  34.   public override string ToString()
  35.   {
  36.   }
  37. }

As you can see MyEntityBase class marked by [NotTransactional] attribute, but if we assume that all classes are not transactional by default we must not mark it by this attribute.