Generating Business Logic “Hooks” for EF4 Entities

Once again a question in the EF MSDN Forum has prompted a blog post where I can give a more complete answer.  If I understand things correctly, the person asking the question wanted a simple way to add business logic hooks to their entities which would be called whenever they did SaveChanges.  In EF4 we made available the basic building blocks for this kind of thing, but unfortunately it’s not quite as easy and discoverable as it ought to be.

Since I’m stuck sitting at home with my knee elevated as I recover from minor knee surgery, I thought I’d take a little time today to create something which should make this much easier.  You can find the result here.  It’s a visual studio extension file (VSIX) which installs a new T4 item template called EntityHooks.  If you right click on the EF designer background and choose “Add Code Generation Item…”, you should see a new EF EntityHooks Code Generator in the list of templates.  If you choose that option, it will add a new TT file to your project that implements the hooks.

It’s important to realize that this complements whatever other code generation you have going on—it doesn’t replace it.  So if you have already added a code generation item, this will just add another one which generates partial classes that add functionality to your existing partial classes.  Unfortunately, if this is the first code gen artifact you have added to your project, the designer doesn’t realize it doesn’t generate the full entities so it turns off the default codegen, and you will either need to add another codegen artifact or in the properties for your EDMX file set the codegen strategy property from None back to default.

Once this is done you can write your own partial class for any of your entities and implement any or all of the OnAdded, OnModified and OnDeleted partial methods.  These methods will be called whenever an instance of that entity type is in the appropriate state at the time SaveChanges is called, and the call will happen before the framework does any other part of SaveChanges.  So, if your method throws an exception, no save will happen.  This also means that you can modify other entities if you want, or cancel an operation on the particular entity by calling AcceptChanges on its ObjectStateEntry (which is passed as an argument to the partial method).

Here’s a simple example I used to test out the extension.  It’s a hook which takes Customer entities marked for deletion and instead of deleting them just prepends “D:” to the front of the company name as a signal that they are deleted but you still want to keep them around for historical info or something like that:

 public partial class Customer
{
    partial void OnDeleted(System.Data.Objects.ObjectStateEntry entry)
    {
        entry.ChangeState(EntityState.Modified);
        CompanyName = "D:" + CompanyName;
    }
}

With this class in place, any attempt to delete a Customer will instead become just a modification.

The extension should work with whatever code generation strategy (default, self-tracking, poco, any custom strategy you create) as long as you generate partial classes both for your entities and your context.  You will notice, though, that it introduces a dependency on the EF since the partial methods receive an ObjectStateEntry argument.  if you want this kind of thing with a truly POCO experience, then you would need to create some other type to abstract away this dependency.  For this exercise I just took the shortcut of passing a state entry because it makes it easy to change the state of the entity or perform other actions in a general way.

The way I implemented this was to output an IEntityHooks interface which declares three methods OnAddedHook, OnModifiedHook and OnDeletedHook, plus a partial class for each entity which implements the interface by having those methods call the corresponding OnAdded, OnModified or OnDeleted partial method which the partial class also declares (can’t have a partial method implement an interface method because the compiler will completely optimize away the partial method if no one implements it), and finally a partial class for the context which overrides the SaveChanges virtual method.  The new SaveChanges method retrieves entries from the ObjectStateManager which represent entities in the various states, casts the entities to the interface and then executes the appropriate hook.

If you want more details, install the VSIX file linked above, add the hook template to one of your projects and have a look.  Pretty straight-forward really.  Let me know if you have any questions.

- Danny