Question

I created my own ContextProvider, sub classed from EFContextProvider. In BeforeSaveEntity I am running some business logic to validate the transaction. I need the updates to be "all or nothing", so if the 3rd entity in the collection fails the validation, the entire batch should be discarded, even though Ive already returned "true" for the first 2 entities.

I have a class level property thats getting set when any entity fails. In the final check in BeforeSaveEntities I can get the value of the flag.

I think this is where I can abort the update, but not sure how. Do I clear the map? Or throw an error?

Also, I will need to re-query the DB for my validation routines. I've read some posts that talk about creating a 2nd instance of the context to do the querying for the current values. Is there some docs on doing this, or gotchas I need to be aware of?

thanks

Was it helpful?

Solution

In your BeforeSaveEntities call you can throw an EntityErrorsException: Here is an example where we throw an exception if there is attempt to save any "Order" objects within a save bundle:

[HttpPost]
public SaveResult SaveWithEntityErrorsException(JObject saveBundle) {
  ContextProvider.BeforeSaveEntitiesDelegate = ThrowEntityErrorsException;
  return ContextProvider.SaveChanges(saveBundle);
}

private Dictionary<Type, List<EntityInfo>> ThrowEntityErrorsException(Dictionary<Type, List<EntityInfo>> saveMap) {
  List<EntityInfo> orderInfos;
  if (saveMap.TryGetValue(typeof(Order), out orderInfos)) {
    var errors = orderInfos.Select(oi => {
      return new EntityError() {
        EntityTypeName = typeof(Order).FullName,
        ErrorMessage = "Cannot save orders with this save method",
        ErrorName = "WrongMethod",
        KeyValues = new object[] { ((Order) oi.Entity).OrderID },
        PropertyName = "OrderID"
      };
   return new EFEntityError(oi, "WrongMethod", "Cannot save orders with this save method", "OrderID");
    });
    var ex =  new EntityErrorsException("test of custom exception message", errors);
    // if you want to see a different error status code use this.
    // ex.StatusCode = HttpStatusCode.Conflict; // Conflict = 409 ; default is Forbidden (403).
    throw ex;
  }
  return saveMap;
}

And you should use BeforeSaveEntities exclusively instead of BeforeSaveEntity as your save logic becomes more complicated.

OTHER TIPS

I had a requirement to perform server side calculations on entities that had been changed on the client - without saving - and get the results back to the client. The solution based on Breeze named saves that I came up with could be useful in this situation too.

I added the following method to the base class for my Breeze controllers.

protected SaveResult OverrideSaveChanges(JObject saveBundle, Action<List<object>> action, bool shouldSave = false)
{
    var saveChangesDelegate = new SaveChangesOverride(action, shouldSave);

    return saveChangesDelegate.Execute(saveBundle, ContextProvider);

This allows concrete controllers to implement named saves very simply. The saveBundle plus an Action<List<object>> are passed into the OverrideSaveChanges method. The action can make whatever modifications to the entities that are required and those changes will be propagated back to the client. The objects in the list are the entities that the client recognized as having changes and sent down to the server for the named save. Optionally, you could pass a shouldSave argument with a value of true to have the entities saved - the default is false.

OverrideChanges delegates to SaveChangesOverride for most of the heavy lifting.

public class SaveChangesOverride
{
    public SaveChangesOverride(Action<List<object>> action, bool shouldSave = false)
    {
        Action = action;
        ShouldSave = shouldSave;
    }

    private readonly Action<List<object>> Action;
    private readonly bool ShouldSave;

    public List<object> Entities;

   public SaveResult Execute(JObject saveBundle, ContextProvider contextProvider)
    {
        contextProvider.BeforeSaveEntitiesDelegate = OnBeforeSaveEntities;

        contextProvider.SaveChanges(saveBundle);

        return new SaveResult
        {
            Entities = Entities,
            KeyMappings = new List<KeyMapping>()
        };
    }

    private Dictionary<Type, List<EntityInfo>> OnBeforeSaveEntities(Dictionary<Type, List<EntityInfo>> arg)
    {
        Entities = arg.SelectMany(x => x.Value).Select(x => x.Entity).ToList();

        Action(Entities);

        if (!ShouldSave)
        {
            return new Dictionary<Type, List<EntityInfo>>();
        }

        return arg;
    }
}

Although we have access to all of the changed entities in the saveBundle actually performing the modifications in OnBeforeSaveChanges allows us to work with entities rather than a JObject.

Also, contextProvider.SaveChanges must be called regardless of whether we wish to have the entities saved. This is what triggers OnBeforeSaveEntities to be called. To ensure that the entities are not saved despite calling SaveChanges (if that is what is desired), rather than returning arg from OnBeforeSaveEntities, an empty dictionary is returned.

To ensure that the changes make it back to the client, a reference to the entities is saved in OnBeforeSaveEntities. This is used in Execute to prepare a SaveResult that is populated with the modified entities.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top