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)
        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

Comments (12)

  1. JasonBSteele says:

    Thanks Danny, I’m sure I will be using this.

    Do you think this is the best way of setting





    Or is there a better way?

  2. simmdan says:

    For CreatedBy and LastModifiedBy this is probably the best option.  For CreatedDateTime and LastModifiedDateTime I would be tempted to use triggers in the database and mark the properties as storegenerated.  That way the times are always based on the database server’s clock rather than whatever client the code is running on.

    – Danny

  3. suntereo says:

    Wow! This is great!! I’m wondering if there’s a way to modify the .tt file so that it only creates the generated classes if it does not yet already exist.  The problem I see is that if I add code to the generated files to handle my logic, and then add some additional tables, I need to re-run the tool.  But when I re-run the tool, it generates all of the files again, wiping out any changes.  Is this easy to do?

  4. simmdan says:

    You shouldn’t need to modify the generated code at all to add your logic.  First off, keep in mind that these are partial classes so you can put your own code in a different file and have it be part of the same class.  Secondly, the generated code declares partial methods but doesn’t implement them.  So you just need to implement the partial methods in your own file, and the generated code will call it.  

    In the example I mention above with Customer.OnDeleted, in that project I actually had three separate files which all contribute to the Customer class.  One file was generated as part of the default codegen and had the main code for the customer object including its properties, etc.  Another file was generated using the EntityHooks code generator from this post, and it declares the partial methods and calls them, and the third file I wrote by hand to contain my business logic.  If I change the model, then the first two files will be regenerated, but my business logic won’t be touched at all.

    – Danny

  5. MrDaveM says:

    With regards to adding ModifiedBy, CreatedBy etc, whats the recommended way to get the currently logged in web user when the EF Model is in a separate project?

  6. simmdan says:

    This all depends on how you have authentication set up, and it’s really not related to the EF or to the question of how your projects are structured.  For many auth schemes, you can use System.Security.Principal.WindowsIdentity.GetCurrent().Name.ToString() to get the current identity for

    You might take a look at a post like this: or something on MSDN for more info.

    – Danny

  7. Dave says:

    works great with the default entityobject generator.  However,  when i swtich to self tracking entities, it doesn't seem to work.   ObjectStateManager.GetObjectStateEntries does not return any items.  Is there a differenty approach that could be used with self tracking entities?

  8. simmdan says:

    It really should work the same with self tracking entities as long as you reattach the entities / apply changes before you call SaveChanges.  The EF always uses the object state manager to figure out what changes it needs to send to the database, and these hooks use that same mechanism.  So if GetObjectStateEntries isn't returning any items, then saving won't have anything to work with either.  Hmmm…  I suppose the other possibility is that you might need to call DetectChanges first if you are using POCOs and the changes have been made while the entities were attached (not in self-tracking mode).

    – Danny

  9. Aaron says:

    Nevermind on that last post, I just figured it out.



    // Emit Entity Types

    foreach (EntityType entity in ItemCollection.GetItems<EntityType>().Where(e => e.BaseType == null).OrderBy(e => e.Name)) { … }


    I added the  .Where(e => e.BaseType == null) to the emitter.

    Hope this can help someone else as well!

  10. srinivas says:

    Hi Danny,

    I've installed the VSIX for EntitiyHooks but it doesn't show up in the list when try to code gen the model. Help!

    Thanks in advance.

  11. simmdan says:


    I'm not sure what's causing this difficulty.  Are you running VS2010?  Have you restarted visual studio?  Do you see other code gen items?  If so, which?

    – Danny

  12. Ben Cheng says:

    If you are using the June 2011 CTP update (I needed it to use ENUM in the entities — how can you possibly live without ENUMS????). Anyways, this will kill your beautiful TT file. The simple fix is to open up the file in the project and replace the include file with this file "EF.Utility.CS.OOB.ttinclude". Then it works again beautifully.

Skip to main content