Tuesday 19 June 2012

Entity Framework Removing Failed Entities Saves From Object Context

Again another issue when I moved to Entity Framework 4, to be far this might have been there all along and I really got issue after more users were on the system and they got inventive at crashing it.

BackGround

Website using EF4.0 database first design which uses a standard Unit of Work and Repository pattern which allows me to developer db helper classes very quickly and I can move this from project to projects without having to reinvent the wheel. If any changes are done on the application (inserts, updates etc. ) then these are save by context.SaveChanges() as per normal patterns of this type.


Problem

The problem is that there is a single context for the whole application and the way how the context works. Also if a user (or code user calls really) calls context.SaveChanges() then it will save all changes in that context even if they are for other users or other parts of the application. This in theory is fine as you are encapsulating the data management to Entity Framework i.e. you don't care as long as it works.

There is a serious bug with this though; if you save data that will cause an exception that data (or entity) will be still be in the context and will try and save again on the next context.SaveChanges() attempt. So you may end up with domino effect on your web application as user after user starts to fall over as context.SaveChanges() tries to save the same bad data.


What you could Do

Have a different context for every user/session or dispose of contexts directly after making a change. This is very wasteful and missing the point of Entity Framework as it automatically load balances the data management and thing like connection pooling etc..

Ideally: Remove The Bad Entity and Throw that Exception 

If a user submits bad data or something falls over while save you really want to remove that data entity from the context and throw the original exception; this really should be the standard pattern in Entity Framework.

So I changed my Repository base class as follows:

FROM


public void SaveChanges()
 {
      context.SaveChanges();
  }

TO 


public void SaveChanges()

{

try  {
         context.SaveChanges();
      }

catch (Exception ex) 
{
      // Get All properties from the Exception
      var properties = ex.GetType().GetProperties();

      foreach (PropertyInfo p in properties)
     { 
         //Search Properties of Exception for StateEntries   
         if (p.Name == "StateEntries")
        {
            //Get the entities 
             var entities = (IEnumerable) p.GetValue(ex, null);
                        
             foreach (var objectStateEntry in entities)
             {
                 //Accept Changes (ie Abandon save of Entity Causing issues)
                  objectStateEntry.AcceptChanges();
              }
        }

      }
           //Throw the Exception so the upper level know it have a problem.
           throw;
}


How it works

Works by extracting the Entity Framework object which caused the exception. The object or "objectStateEntry" is embedded at run time within the exception, you just have to find it.

  1. Try and Save
  2. Get Properties of the Exception
  3. Find "StateEntries" in the exception properties which is the original object collection
  4. There will be only only one  "objectStateEntry" to get this via reflection
  5. Accept Changes on EF entity via  objectStateEntry.AcceptChanges(), this will tell EF that changes on this object are saved or ignore the save really. 
  6. Throw the exception back at calling code: this is very important you want to handle the error not ignore it.








No comments:

Post a Comment

Comments are welcome, but are moderated and may take a wee while before shown.