A helper to easily set up change notifications in Entity Framework
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:
- Register for the ObjectContext.SavingChanges event
- In your handler, call ObjectStateManager.GetObjectStateEntries(EntityState.Modified)
- Go through the whole list looking for those where stateEntry.Entity is a Product
- 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!
Comments
Anonymous
January 09, 2009
Thanks for this David but it does'nt seem to work in a situation where the only change is to a foreign key (drop-down). What I'm trying to achieve is to check for a foreign key change and then validate other properties of the same entity. Any thoughts appreciated !Anonymous
January 10, 2009
Code Camps First New Hampshire Code Camp on February 28th [Via: cbowen ] Link Collections Interesting...Anonymous
January 10, 2009
The comment has been removedAnonymous
January 10, 2009
I'm one step ahead of you, David :-) If you check the 'IsRelationship' property of the ObjectStateEntry and it's true you can get the key and hence the type of the object from objectStateEntry.CurrentValues[1] and retrieve the object from a new instance of the Data Entities by using the GetObjectByKey method. The problem I'm now having is that the foreign key references in the NorthwindEntities.Inserting() method of the partial classes seem to contain the original values. Getting very frustrated now !Anonymous
January 10, 2009
The comment has been removedAnonymous
January 10, 2009
Hi Michael, I checked with someone from the Entity Framework team, and they confirm that getting this helper to work well on FKs would be tricky. In their words: "There is no simple way to drill into the relationship state entry (or, more likely, pair of relationship state entries, one deleted and one added) and turn it into an update. It is possible, but quite awkward. You need to match the entity key in the relationship state entry to a particular entity. In the EDM, the relationship is not part of the entity. That’s why changes to the relationship are not reflected in the entity state entry." At this point, this becomes more of an EF question than something specific to this helper, and I'd suggest following up where the experts are: http://social.msdn.microsoft.com/forums/en-US/adodotnetentityframework/threads/Anonymous
February 18, 2009
Please post corrections/new submissions to the Dynamic Data Forum . Put FAQ Submission/Correction in