When you use Entity Framework, you can perform Insert/Update/Delete operations on your entities, and you eventually call ObjectContext.SaveChanges() to actually make it all happen.  The call to SaveChanges() is either explicit, or can happen implicitly when you use the EntityDataSource (e.g. within a Dynamic Data application).

Before SaveChanges() actually performs the operations, it gives you a chance to look at the entities, letting you modify them, and possibly cancel certain operations.  Unfortunately, doing this requires using some pretty ugly code, because the API is a little too low level.  Instead of nicely handing you the changes one by one, it just gives you the raw change list and lets you deal with it.

For instance, suppose you want to do something special when a Product is being updated.  You have to:

  1. Register for the ObjectContext.SavingChanges event
  2. In your handler, call ObjectStateManager.GetObjectStateEntries(EntityState.Modified)
  3. Go through the whole list looking for those where stateEntry.Entity is a Product
  4. Finally they can do what they want with the Product

While certainly possible, it’s a lot more pain than it should be!  To make this easier, we’ll write a little helper which lets you trivially listen to the changes you care about.

Before we get to the implementation of the helper, let’s look at the end result with the helper.  It lets you write something like this:

public partial class NorthwindEntities {
    partial void OnContextCreated() {
        // This line hooks up the change notifications
        this.SetupChangeNotifications();
    }
}

public partial class Products : INotifyUpdating {
    public bool Updating() {
        // Make a small change to test that you can affect the entity
        ProductName += "!";
        return true;
    }
}

public partial class Categories : INotifyDeleting {
    public bool Deleting() {
        // Prevent the deletion
        return false;
    }
}

Notice how you just need to implement a simple interface on the partial class on the relevant entity type in order to get notified.  You don’t need to know anything about the change list, or other such low level things.

Now let’s take a look at how the help works.  First, we define the Insert/Update/Delete interfaces:

public interface INotifyInserting {
    /// <summary>
    /// Return false to cancel the Insert operation
    /// </summary>
    bool Inserting();
}

public interface INotifyUpdating {
    /// <summary>
    /// Return false to cancel the Update operation
    /// </summary>
    bool Updating();
}

public interface INotifyDeleting {
    /// <summary>
    /// Return false to cancel the Delete operation
    /// </summary>
    bool Deleting();
}

Pretty simple stuff.  And now, the core logic that makes it all happen:

public static class EntityFrameworkHelpers {
    public static void SetupChangeNotifications(this ObjectContext context) {
        new ObjectContextHelper(context);
    }

    private class ObjectContextHelper {
        ObjectContext _context;

        public ObjectContextHelper(ObjectContext context) {
            // Keep track of the context
            _context = context;

            // Register for the SavingChanges event
            _context.SavingChanges += new EventHandler(Context_SavingChanges);
        }

        void Context_SavingChanges(object sender, EventArgs e) {
            // Go through all the Insert/Update/Delete changes and notify the user code if needed
            ProcessObjectStateEntries(EntityState.Added);
            ProcessObjectStateEntries(EntityState.Modified);
            ProcessObjectStateEntries(EntityState.Deleted);
        }

        private void ProcessObjectStateEntries(EntityState entityState) {
            // Go through all the entries
            foreach (ObjectStateEntry entry in _context.ObjectStateManager.GetObjectStateEntries(entityState)) {

                if (entry.Entity == null)
                    continue;

                // If the entity implements the interface (Insert, Update or Delete), call the
                // method.  If it returns false, cancel the operation
                bool proceedWithChange = true;
                switch (entityState) {
                    case EntityState.Added:
                        var notifyInserting = entry.Entity as INotifyInserting;
                        if (notifyInserting != null)
                            proceedWithChange = notifyInserting.Inserting();
                        break;
                    case EntityState.Modified:
                        var notifyUpdating = entry.Entity as INotifyUpdating;
                        if (notifyUpdating != null)
                            proceedWithChange = notifyUpdating.Updating();
                        break;
                    case EntityState.Deleted:
                        var notifyDeleting = entry.Entity as INotifyDeleting;
                        if (notifyDeleting != null)
                            proceedWithChange = notifyDeleting.Deleting();
                        break;
                }

                // If the method returned false, cancel the change
                if (!proceedWithChange)
                    entry.AcceptChanges();
            }
        }
    }
}

I’ll let the comments speak for themselves here.  But in any case, you don’t really need to look at the details of this code if you just want to use the helpers.

I attached a complete Dynamic Data solution that includes and uses the helpers, so just download it and start from there!