Hi,

In our Application, rows are seldomly hard removed. Instead we set the DeletedBy = UserId and DeletedOn = DateTime.Now to signal the Entity isn't valid anymore. This way we can always tell when an Entity was removed and by whom. And it gives us the ability to restore data that was removed inadvertently.

Can we override the OnRemoving() and OnRemove() to do our soft deletes?

Regards Paul Sinnema Diartis AG

protected override void OnRemoving()
        {
            // base.OnRemoving();

            Stamp(EntityStamp.Deletion);
        }

        protected override void OnRemove()
        {
            // base.OnRemove();
        }

Updated at 22.06.2010 8:13:21

IMHO, the best approach is to add your own SoftRemove method (possibly - extension method) with all the infrastructure you need for this (e.g. your own event-like methods), and use our own Remove() method only for "hard" removes.

We're currently brainstorming about the following solution:

  • In the OnRemoving() we gather all the data being deleted into a Dictionary (key, value)

  • We serialize the Dictionary to XML and store that in a separate Entity with the stamp information

  • The base.OnRemoving() is called normally

This means we are adding data to the DB during the OnRemoving(). Is this a valid construction?

Regards Paul Sinnema Diartis AG


Updated at 22.06.2010 9:34:37

Hi Paul, We also want soft removes in our application. All our entities inherit from this class: public abstract class DefaultEntity<t> : Entity where T : DefaultEntity<t>, new() { /// <summary> /// The identifier of the entity. /// </summary> [Key, Field] public Int32 Id { get; private set; } /// <summary> /// Created by user. /// </summary> [Field(Nullable = true)] [Association()] public ApplicationUser CreatedBy { get; set; } /// <summary> /// Created on date. /// </summary> [Field(Nullable = false)] public DateTime Created { get; set; } /// <summary> /// Last modified by user. /// </summary> [Field(Nullable = true)] [Association()] public ApplicationUser ModifiedBy { get; set; } /// <summary> /// Last modified on date. /// </summary> [Field(Nullable = true)] public DateTime? Modified { get; set; } //To Paul: When you do this you allways have these fields with your entities. [b] /// <summary> /// Deleted by user. /// </summary> [Field(Nullable = true)] [Association()] public ApplicationUser DeletedBy { get; set; } /// <summary> /// Deleted on date (if not null). /// </summary> [Field(Nullable = true)] public DateTime? Deleted { get; set; }[/b] /// <summary> /// Logicaldelete of the current entity. /// </summary> public new void Remove() { this.Deleted = DateTime.Now; this.DeletedBy = DomainInfo.CurrentUser; } /// <summary> /// Deletes the current entity /// </summary> /// <param name="RemovePermanently">True if you want to delete the record permanently!</param> public void Remove(bool RemovePermanently) { if (RemovePermanently) { base.Remove(); } else { this.Remove(); } } It's easy to specify this method so that deleted values are automatically excluded: /// <summary> /// Gets all the (not deleted) entities of this type. /// </summary> /// <returns>IQueryable resultlist</returns> public static IQueryable<t> GetAll() { return Query.All<t>().Where(p => p.Deleted == null); } The class that you want to create looks like this: [Serializable] [HierarchyRoot(InheritanceSchema = InheritanceSchema.SingleTable)] public class AppointmentCore : DefaultEntity<appointmentcore> { //All your classspecific properties go here //All the properties and methods specified in the DefaultEntity class are accessible here. } I also agree with Alex; reference cleanup isn't performed, so you should be aware of this.

You've got to experience the same problems we have with 'soft deletes'. With every query we make we have to realize that we have to exclude the deleted rows. As Alex wrote, there is the possibility of automatic filtering that makes life easier. Problem with that is when you need to access the deletes afterwards you'll have to write an application without this automatic filtering. Saying that, we also need an application for resurecting data for our solution.

The most beautiful solution is the undo/redo and operation logging mechanism Alex mentions. I do see however problems with this solutions that will come when DB Structure changes are done in between. DO4 will then possibly not be able to restore data because of changed types. For short notice undo's this will how ever not be a problem, I guess.

Regards Paul Sinnema Diartis AG

This thread was imported from our support forum. The original discussion may contain more detailed answer.

asked Jun 22 '10 at 07:26

Paul%20Sinnema's gravatar image

Paul Sinnema
261888896


2 Answers:

Alex (Xtensive) wrote:

I think it's a bad idea: soft removes normally don't imply any reference cleanup; business rules there can be quite different.

IMHO, the best approach is to add your own SoftRemove method (possibly - extension method) with all the infrastructure you need for this (e.g. your own event-like methods), and use our own Remove() method only for "hard" removes.


stefmen wrote:

Hi Paul,

We also want soft removes in our application.

All our entities inherit from this class:

public abstract class DefaultEntity<T> : Entity where T : DefaultEntity<T>, new()
{
/// <summary>
        /// The identifier of the entity.
        /// </summary>
        [Key, Field]
        public Int32 Id { get; private set; }

        /// <summary>
        /// Created by user.
        /// </summary>
        [Field(Nullable = true)]
        [Association()]
        public ApplicationUser CreatedBy { get; set; }

        /// <summary>
        /// Created on date.
        /// </summary>
        [Field(Nullable = false)]
        public DateTime Created { get; set; }

        /// <summary>
        /// Last modified by user.
        /// </summary>
        [Field(Nullable = true)]
        [Association()]
        public ApplicationUser ModifiedBy { get; set; }

        /// <summary>
        /// Last modified on date.
        /// </summary>
        [Field(Nullable = true)]
        public DateTime? Modified { get; set; }

        //To Paul: When you do this you allways have these fields with your entities.
[b]        /// <summary>
        /// Deleted by user.
        /// </summary>
        [Field(Nullable = true)]
        [Association()]
        public ApplicationUser DeletedBy { get; set; }

        /// <summary>
        /// Deleted on date (if not null).
        /// </summary>
        [Field(Nullable = true)]
        public DateTime? Deleted { get; set; }[/b]

        /// <summary>
        /// Logicaldelete of the current entity.
        /// </summary>
        public new void Remove()
        {
            this.Deleted = DateTime.Now;
            this.DeletedBy = DomainInfo.CurrentUser;
        }

        /// <summary>
        /// Deletes the current entity
        /// </summary>
        /// <param name="RemovePermanently">True if you want to delete the record permanently!</param>
        public void Remove(bool RemovePermanently)
        {
            if (RemovePermanently)
            {
                base.Remove();
            }
            else
            {
                this.Remove();
            }
        }

It's easy to specify this method so that deleted values are automatically excluded:

/// <summary>
        /// Gets all the (not deleted) entities of this type.
        /// </summary>
        /// <returns>IQueryable resultlist</returns>
        public static IQueryable<T> GetAll()
        {
            return Query.All<T>().Where(p => p.Deleted == null);
        }

The class that you want to create looks like this:

[Serializable]
[HierarchyRoot(InheritanceSchema = InheritanceSchema.SingleTable)]
public class AppointmentCore : DefaultEntity<AppointmentCore>
{
       //All your classspecific properties go here
       //All the properties and methods specified in the DefaultEntity class are accessible here.
}

I also agree with Alex; reference cleanup isn't performed, so you should be aware of this.


Alex (Xtensive) wrote:

It seems you're trying to implement operation logging + undo for removals - the original entity gets deleted anyway, so this isn't what normally called "soft removal".

If you really need this feature (undo + operation logging), may be it's a good idea to acquire a license and push with this on us ;) Actually, it's one of long promised features; a lot of work necessary to implement it is already done - there is Operations framework used by DisconnectedState, that already implements "do" part; + we tested our approach for "undo" in Integrity.Atomicity, although we already decided to use much simpler approach in Xtensive.Storage.

So in short, I think there are two good ways:

  • Implement soft removals w/o significant integration with DO4.

  • Push on us with operation logging & undo/redo. This will require some time (may be few months), but when it's done, it will work everywhere, i.e. not just for removal. And in this case you won't need to implement anything except UI part for this.


Alex (Xtensive) wrote:

Btw, about the approach: being on your place, I'd seriously think about adding ISupportsSoftRemove interface + few extension methods to it, as well as some service allowing to e.g. find all of such objects and actually delete them.


Alex (Xtensive) wrote:

Clear. I'd also prefer operation logging instead of soft deletes, if purpose is just logging & possibility to do a kind of rollback - at least to keep the working set (and indexes) smaller.

You're right about undo and upgrade. We'll think about providing custom serializer + operation processor to handle this case.

answered Jun 22 '10 at 07:31

Editor's gravatar image

Editor
46156156157

We've decided not to do any soft-deletes anymore. Instead we'll be doing hard-deletes all the time and log the deletes in a separate table.

(Jun 22 '10 at 07:31) Paul Sinnema Paul%20Sinnema's gravatar image

Alex Kofman wrote: You can get some ideas here.

(Jun 22 '10 at 07:31) Editor Editor's gravatar image

Alex,

Is there any progress on the 'logging undo/redo' feature to report?

We would like to implement a kind of pull-mechanism to pull changes in the DB to the clients. The idea is to register all changes made in the DB in a table with a time-stamp. The clients can then see which changes were made since the last 'pull' from the DB. Your idea of audit logging would help us there a lot. We wouldn't need to write the code to store the changes and simply use your log-table to pull changes to the client.

(Jan 05 '11 at 06:44) Paul Sinnema Paul%20Sinnema's gravatar image

Hi Alex,

Any news here?

Regards Paul

(Jan 10 '11 at 10:42) Paul Sinnema Paul%20Sinnema's gravatar image

As Alex mentioned, you also want to think about 1) soft-removing referenced entities in the aggregate root and 2) preventing an entity from being soft-removed if it is referenced by another entity that requires it.

It would be great if DO could implement a standardized solution... but I'm sure they're busy with 100 other things.

answered Jan 06 '11 at 02:03

ara's gravatar image

ara
395878791

Your answer
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!
toggle preview

powered by OSQA