How do I perform logical/soft delete with Breeze JS?
Obviously it would depend on my data model in how I implement my logical delete, for me (Status=2), so would I detect that my entity is marked for deletion in the Breeze Controller somehow and convert it into an Update with my column change, or what is the preferred practice?
This explains how to perform a physical delete I believe, but no logical delete.
http://www.breezejs.com/documentation/inside-entity#DeleteEntity
Your suggestion would likely work, but my first pass at this would simply be to add a softDelete
method to your client side class that simply sets the status to 2
. I would also add a 'where Status!=2' condition to each query on the server so that by default no deleted
entities get returned, something like:
[HttpGet]
public IQueryable<Customer> Customers(bool includedDeleted) {
if (includeDeleted) {
return ContextProvider.Context.Customers;
} else {
return ContextProvider.Context.Customers.Where(c = c.Status != 2);
}
}
A further refinement would be to automatically detach any soft deleted
entities on the client after a SaveChanges
call. i.e. iterate over the 'saved' entities and detach any with a status == 2
Hope this makes sense.
One current way it works, is not very elegant because it uses reflection, but better than not having solution.
Follows a very simplified way that I use with using 'ExclusionDate' property:
// Model -----------------------------------
public interface ISoftDelete
{
DateTime? ExclusionDate { get; set; }
}
public class Order : ISoftDelete
{
// Props...
public DateTime? ExclusionDate { get; set; }
}
// ------------------------------------------
// Data -------------------------------------
public interface IBreezeRepository<out T>
{
IQueryable<T> All();
}
public class SoftRepository<T> :
IBreezeRepository<T> where T : class, ISoftDelete
{
public SoftRepository(DbContext context)
{
Context = context;
}
protected DbContext Context { get; private set; }
public IQueryable<T> All()
{
return Context.Set<T>().Where(p=> !p.ExclusionDate.HasValue);
}
}
public class UnitOfWork
{
private readonly EFContextProvider<EcmContext> _contextProvider;
public UnitOfWork()
{
_contextProvider = new EFContextProvider<EcmContext>
{
BeforeSaveEntityDelegate = BeforeSaveEntity
};
var context = _contextProvider.Context;
Orders = new SoftRepository<Order>(context);
}
public IBreezeRepository<Order> Orders { get; private set; }
private bool BeforeSaveEntity(EntityInfo entityInfo)
{
var entityType = entityInfo.Entity.GetType();
// a little reflection magic
if (typeof(ISoftDelete).IsAssignableFrom(entityType) &&
entityInfo.EntityState == EntityState.Deleted)
{
entityInfo.GetType().GetProperty("EntityState")
.SetValue(entityInfo, EntityState.Modified, null);
var entity = entityInfo.Entity as ISoftDelete;
if (entity != null)
{
entity.ExclusionDate = DateTime.Now;
entityInfo.ForceUpdate = true;
}
}
return true;
}
}
// -------------------------------------------