breezejs cancel saveChanges due to validation failure in BeforeSaveEntity - breeze

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

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.

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.

Related

Exclude property from updating when SaveChanges() is called

There appears to be two ways to update a disconnected Entity Framework entity using the "attach" method.
Method One is to simply set the disconnected entity's state as modified:
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.SaveChanges();
This will save all fields on the "dog" object. But say you are doing this from an mvc web page where you only allow editing of Dog.Name, and the only Dog property contained on the page is Name. Then one could do Method Two:
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).Property(o => o.Name).CurrentValue = dog.Name;
myDbContext.Entry(dog).Property(o => o.Name).IsModified = true;
myDbContext.SaveChanges();
Method Two could get quite verbose when there are a lot of properties to update. This prompted me to attempt Method Three, setting IsModified = false on the properties I don't want to change. This does not work, throwing the runtime error "Setting IsModified to false for a modified property is not supported":
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
myDbContext.Entry(dog).Property(o => o.Owner).IsModified = false;
myDbContext.SaveChanges();
I'd much prefer to use Method One everywhere, but there are many instances where my asp.net mvc view does not contain every scalar property of the Dog class.
My questions are:
Are there any attributes I could use on the POCO class that would tell Entity Framework that I never want the property to up updated? Eg, [NeverUpdate]. I am aware of the [NotMapped] attribute, but that is not what I need.
Failing that, is there any way I can use Method One above (myDbContext.Entry(dog).State = EntityState.Modified;
) and exclude fields that I don't want updated?
P.S. I am aware of another way, to not use "attach" and simply fetch a fresh object from the database, update the desired properties, and save. That is what I am doing, but I'm curious if there is a way to use "attach," thus avoiding that extra trip to the database, but do it in a way that is not so verbose as Method Two above. By "fetch a fresh object" I mean:
Dog dbDog = myDbContext.Dogs.FirstOrDefault(d => d.ID = dog.ID);
dbDog.Name = dog.Name;
myDbContext.SaveChanges();
The following may work works.
myDbContext.Dogs.Attach(dog);
myDbContext.Entry(dog).State = EntityState.Modified;
var objectContext = ((IObjectContextAdapter) myDbContext).ObjectContext;
foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(entity => entity.Entity.GetType() == typeof(Dogs)))
{
// You need to give Foreign Key Property name
// instead of Navigation Property name
entry.RejectPropertyChanges("OwnerID");
}
myDbContext.SaveChanges();
If you want to do it in a single line, use the following extension method:
public static void DontUpdateProperty<TEntity>(this DbContext context, string propertyName)
{
var objectContext = ((IObjectContextAdapter) context).ObjectContext;
foreach (var entry in objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified).Where(entity => entity.Entity.GetType() == typeof(TEntity)))
{
entry.RejectPropertyChanges(propertyName);
}
}
And use it like this
// After you modify some POCOs
myDbContext.DontUpdateProperty<Dogs>("OwnerID");
myDbContext.SaveChanges();
As you can see, you can modify this solution to fit your needs, e.g. use string[] properties instead of string propertyName as the argument.
Suggested Approach
A better solution would be to use an Attribute as you suggested ([NeverUpdate]). To make it work, you need to use SavingChanges event (check my blog):
void ObjectContext_SavingChanges(object sender, System.Data.Objects.SavingChangesEventArgs e)
{
ObjectContext context = sender as ObjectContext;
if(context != null)
{
foreach(ObjectStateEntry entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
{
var type = typeof(entry.Entity);
var properties = type.GetProperties();
foreach( var property in properties )
{
var attributes = property.GetCustomAttributes(typeof(NeverUpdateAttribute), false);
if(attributes.Length > 0)
entry.RejectPropertyChanges(property.Name);
}
}
}
}
// Check Microsoft documentation on how to create custom attributes:
// http://msdn.microsoft.com/en-us/library/sw480ze8(v=vs.80).aspx
public class NeverUpdateAttribute: SystemAttribute
{
}
//In your POCO
public class Dogs
{
[NeverUpdate]
public int OwnerID { get; set; }
}
Warning: I did not compile this code. I'm not at home :/
Warning 2: I have just read the MSDN documentation and it says:
ObjectStateEntry.RejectPropertyChanges Method
Rejects any changes made to the property with the given name since the
property was last loaded, attached, saved, or changes were accepted.
The orginal value of the property is stored and the property will no
longer be marked as modified.
I am not sure what its behavior would be in the case of attaching a modified entity. I will try this tomorrow.
Warning 3: I have tried it now. This solution works. Property that is rejected with RejectPropertyChanges() method are not updated in the persistence unit (database).
HOWEVER, if the entity that is updated is attached by calling Attach(), the current context remains dirty after SaveChanges(). Assume that the following row exists in the database:
Dogs
ID: 1
Name: Max
OwnerID: 1
Consider the following code:
var myDog = new Dogs();
myDog.ID = 1;
myDog.Name = Achilles;
myDog.OwnerID = 2;
myDbContext.Dogs.Attach(myDog);
myDbContext.Entry(myDog).State = EntityState.Modified;
myDbContext.SaveChanges();
The current state of database after SaveChanges():
Dogs:
ID: 1
Name: Achilles
OwnerID: 1
The current state of myDbContext after SaveChanges():
var ownerId = myDog.OwnerID; // it is 2
var status = myDbContext.Entry(myDog).State; // it is Unchanged
So what you should do? Detach it after SaveChanges():
Dogs myDog = new Dogs();
//Set properties
...
myDbContext.Dogs.Attach(myDog);
myDbContext.Entry(myDog).State = EntityState.Modified;
myDbContext.SaveChanges();
myDbContext.Entry(myDog).State = EntityState.Detached;

Breezejs SaveChanges: is it possible to return a custom SaveResult object, somehow?

Say I have a movie entity, with an average score. A user can rate a movie, and for that I call datacontext.savechanges on the client, sending a Rating object to the server. On the server, the SaveChanges method is called, and in the BeforeSaveEntity method, I adapt the movie's average score.
Here's the question: how to return that average score from the server's SaveChanges method, for example inside the SaveResult object?
I thought I could add the movie entity to the SaveResult Entities list, but then:
- I would need to access attributes from within the saveBundle parameter
- I would have to requery the DB, which I just did in BeforeSaveEntity
Thanks
Nicolas
As pawel pointed out in the comments: To return the movie in the SaveChanges promise, update the movie in your BeforeSaveEntities method on your custom EFContextProvider and add it to the saveMap.
I've put together some code for you.
protected override Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap) {
Movie movie = null;
// initialize the movie variable and update the movie as needed
saveMap.Add(typeof(Movie), movie);
return saveMap;
}
Yes, it is possible.
Write your controller against an EDMX as you would do normally do. For us, it translates into something like this:
public class PersonalizationController : MultiTenantBreezeController<PersonalizationEntities>
where the PersonalizationEntities is an ObjectContext.
Then on the server, we simply define the SaveChanges (do not mind the override, we do have a base class)
[HttpPost]
public override SaveResult SaveChanges(JObject saveBundle)
{
// Deserialize the object that needs to get saved (ApplicationDefaults is my DTO)
var applicationDefaultsList = JsonConvert.DeserializeObject<List<ApplicationDefaults>>(saveBundle.SelectToken("entities").ToString());
// Do whatever logic you need to save the data
using (var repo = ServiceLocator.Current.Container.Resolve<IUserPreferenceRepository>())
{
// Your save logic here
}
// Construct the save result to inform the client that the server has completed the save operation
var keyMappings = new List<KeyMapping>();
return new SaveResult()
{
Entities = applicationDefaultsList.Cast<object>().ToList(),
Errors = null,
KeyMappings = keyMappings
};
}

Server side Validations and security in breeze.js

I’m trying save some entities using breeze.js. Breeze is working fine and it saves all the changes as required. However, I have trouble validating and ensuring authorization is the server side. From what I’ve gather so far I guess the only way to do this is via examining the JObject passed into save bundles and constructing corresponding objects on the server side. I have to do this (instead of relying Breeze.SaveChanges as I have some logic on the server side). How do I do this? And how do I construct the Breeze.WebApi. SaveResult?
Idea of any other way of solving this problem is also very welcome 
This should be done by implementing a custom EFContextProvider.
The code below implements a custom EFContextProvider for the Northwind database and was taken directly from the documentation on the breeze.com website .
public class NorthwindContextProvider: EFContextProvider<NorthwindIBContext> {
public NorthwindContextProvider() : base() { }
protected override bool BeforeSaveEntity(EntityInfo entityInfo) {
// return false if we don’t want the entity saved.
// prohibit any additions of entities of type 'Role'
if (entityInfo.Entity.GetType() == typeof(Role)
&& entityInfo.EntityState == EntityState.Added) {
return false;
} else {
return true;
}
}
protected override Dictionary<Type, List<EntityInfo>> BeforeSaveEntities(Dictionary<Type, List<EntityInfo>> saveMap) {
// return a map of those entities we want saved.
return saveMap;
}
}
#jaq316 is correct: a custom EFContextProvider is the place to intercept changes coming from the client. It is the place to both authorize and validate them . The documentation has more details. The essence of it is that you scrutinize the proposed changes within your overrides of the BeforeSaveEntity and BeforeSaveEntities virtual methods; alternatively you can attach handlers to the BeforeSaveEntityDelegate and BeforeSaveEntitiesDelegate.
So here is my thought on this one, since I am not using a ContextProvider at all. I am utilizing a SQL back-end and Ninject to inject a repository dependency into each controller I have. I have more items than the demo for "Todos" and want separate controllers out there and repositories as well. If I created the ContextProvider as shown by the breeze docs I would have one ContextProvider file with all the entities in it. This would be huge. If I separated them into separate contexts I would duplicating code in all the overrides.
Here is my Save Changes method in ContactFormController.cs :
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
var sr = new SaveResult() { KeyMappings = new List<KeyMapping>(), Entities = new List<object>()};
dynamic entity = saveBundle["entities"][0];
ContactForm form = entity.ToObject<ContactForm>();
EntityState state = entity.entityAspect.entityState;
switch (state)
{
case EntityState.Added:
KeyMapping mapping = new KeyMapping(){EntityTypeName = typeof(ContactForm).ToString(), TempValue = form.Id };
var validationErrors = _contactFormService.ProcessContactForm(ref form).Cast<object>().ToList();
//if we succeed then update the mappings
if (validationErrors.Count == 0)
{
//setup the new mappings
mapping.RealValue = form.Id;
sr.KeyMappings.Add(mapping);
//link the entity
sr.Entities.Add(form);
}
else
{
sr.Errors = validationErrors;
}
break;
}
return sr;
}
I dynamically change the endpoints before saves on the client side so that each controller in my webapi has a SaveChanges() method. I then call into the appropriate repository to process the backend functions as needed. This way I can run mock code or actual SQL changes depending on the repo injected.
If their are errors on the Processing of the form then we cast our custom List list to a List and assign it to the Errors property of the SaveResult. If there are no errors we send back the new key mappings to be updated on the client.
Ideally I want to reduce all the code in this controller and perhaps abstract it out to a utility method so there is less repeat in every controller. I like this method because then I can create normal repositories and not have them depend on a ContextProvider. Breeze independent at that point.

Calling UpdateModel with a collection of complex data types reset all non-bound values?

I'm not sure if this is a bug in the DefaultModelBinder class or what.
But UpdateModel usually doesn't change any values of the model except the ones it found a match for.
Take a look at the following:
[AcceptVerbs(HttpVerbs.Post)]
public ViewResult Edit(List<int> Ids)
{
// Load list of persons from the database
List<Person> people = GetFromDatabase(Ids);
// shouldn't this update only the Name & Age properties of each Person object
// in the collection and leave the rest of the properties (e.g. Id, Address)
// with their original value (whatever they were when retrieved from the db)
UpdateModel(people, "myPersonPrefix", new string[] { "Name", "Age" });
// ...
}
What happens is UpdateModel creates new Person objects, assign their Name & Age properties from the ValueProvider and put them in the argument List<>, which makes the rest of the properties set to their default initial value (e.g. Id = 0)
so what is going on here?
UPDATE:
I stepped through mvc source code (particularly DefaultModelBinder class) and here is what I found:
The class determines we are trying to bind a collection so it calls the method: UpdateCollection(...) which creates an inner ModelBindingContext that has a null Model property. Afterwards, that context is sent to the method BindComplexModel(...) which checks the Model property for null and creates a new instance of the model type if that is the case.
That's what causes the values to be reset.
And so, only the values that are coming through the form/query string/route data are populated, the rest remains in its initialized state.
I was able to make very few changes to UpdateCollection(...) to fix this problem.
Here is the method with my changes:
internal object UpdateCollection(ControllerContext controllerContext, ModelBindingContext bindingContext, Type elementType) {
IModelBinder elementBinder = Binders.GetBinder(elementType);
// build up a list of items from the request
List<object> modelList = new List<object>();
for (int currentIndex = 0; ; currentIndex++) {
string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex);
if (!DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, subIndexKey)) {
// we ran out of elements to pull
break;
}
// **********************************************************
// The DefaultModelBinder shouldn't always create a new
// instance of elementType in the collection we are updating here.
// If an instance already exists, then we should update it, not create a new one.
// **********************************************************
IList containerModel = bindingContext.Model as IList;
object elementModel = null;
if (containerModel != null && currentIndex < containerModel.Count)
{
elementModel = containerModel[currentIndex];
}
//*****************************************************
ModelBindingContext innerContext = new ModelBindingContext() {
Model = elementModel, // assign the Model property
ModelName = subIndexKey,
ModelState = bindingContext.ModelState,
ModelType = elementType,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
object thisElement = elementBinder.BindModel(controllerContext, innerContext);
// we need to merge model errors up
VerifyValueUsability(controllerContext, bindingContext.ModelState, subIndexKey, elementType, thisElement);
modelList.Add(thisElement);
}
// if there weren't any elements at all in the request, just return
if (modelList.Count == 0) {
return null;
}
// replace the original collection
object collection = bindingContext.Model;
CollectionHelpers.ReplaceCollection(elementType, collection, modelList);
return collection;
}
Rudi Breedenraed just wrote an excellent post describing this problem and a very helpful solution. He overrides the DefaultModelBinder and then when it comes across a collection to update, it actually updates the item instead of creating it new like the default MVC behavior. With this, UpdateModel() and TryUpdateModel() behavior is consistent with both the root model and any collections.
You just gave me an idea to dig into ASP.NET MVC 2 source code.
I have been struggling with this for two weeks now. I found out that your solution will not work with nested lists. I put a breakpoint in the UpdateCollection method ,and it never gets hit. It seems like the root level of model needs to be a list for this method to be called
This is in short the model I have..I also have one more level of generic lists, but this is just a quick sample..
public class Borrowers
{
public string FirstName{get;set;}
public string LastName{get;set;}
public List<Address> Addresses{get;set;}
}
I guess that, I will need to dig deeper to find out what is going on.
UPDATE:
The UpdateCollection still gets called in asp.net mvc 2, but the problem with the fix above is related to this HERE

Modelbinding database entities in ASPNET MVC

I'm having trouble trying to think what the best way is to recreate a database object in a controller Action.
I want to make use of ModelBinders so in my action I have access to the object via a parameter, rather than having to repeat code to get an object from the database based on an identifier parameter. So I was thinking of having a ModelBinder that performs a call to the dataaccess layer to obtain the original object (or creates a new one if it doesn't exist in the database), then binds any properties to the database object to update it. However I've read that the ModelBinders shouldn't make database queries (first comment of this article).
If the ModelBinder shouldn't perform a database query (so just using the DefaultModelBinder) then what about database objects that have properties that are other db objects? These would never get assigned.
Saving an object after the user has edited it (1 or 2 properties are editable in the view) the ModelBinded object would be missing data, so saving it as it is would result in data in the database being overwritten with invalid values, or NOT-NULL constraints failing.
So, whats the best way to get an object in a controller action from the database bound with the form data posted back from the view?
Note im using NHibernate.
I get the model object from the database, then use UpdateModel (or TryUpdateModel) on the object to update values from the form parameters.
public ActionResult Update( int id )
{
DataContext dc = new DataContext();
MyModel model = dc.MyModels.Where( m => m.ID == id ).SingleOrDefault();
string[] whitelist = new string[] { "Name", "Property1", "Property2" };
if (!TryUpdateModel( model, whitelist )) {
... model error handling...
return View("Edit");
}
ViewData.Model = model;
return View("Show");
}
Unfortunately you don't have control over the construction of the model binder, so you can't inject any repository implementation.
You can reach out directly into a service locator to pull in your repository & fetch the item:
public class ProductBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext,
ModelBindingContext bindingContext, Type modelType)
{
if(modelType != typeof(Product))
return null;
var form = controllerContext.HttpContext.Request.Form;
int id = Int32.Parse(form["Id"]);
if(id == 0)
return base.CreateModel(controllerContext, bindingContext, modelType);
IProductRepository repository = ServiceLocator.Resolve<IProductRepository>();
return repository.Fetch(id);
}
}
You might even make this work for all of your entities if you can use a base class or interface that provides the Id of the class.
You'll have to set this up in Global.asax:
ModelBinders.Binders.Add(typeof(Product), new ProductBinder());
and then you can do this:
public ActionResult Save([Bind] Product product)
{
....
_repository.Save(product);
}
Let me first state that I don't recommend to access database from ModelBinders, as from perspective of Separation Of Concern ModelBinders should only be responsible of interpretting client request, obviously database is not.
If you dont want to repeat your self (DRY), use repositories/services
However if u really want to do it like that, then
In global.asax.cs Register a custom MyModelBinderProvider to MVC
ModelBinderProviders.BinderProviders.Add(new EntityModelBinderProvider
{
ConnectionString = "my connection string"
));
Cunstruct the custom ModelBinderProvider to contain database settings
public class EntityBinderProvider: IModelBinderProvider
{
public string ConnectionString { get; set; }
public IModelBinder GetBinder(Type modelType)
{
if (Is known entity)
return new EntityBinder(ConnectionString);
else
return null;
}
}
Follow further instructions from Ben Scheirman
You don't actually have to hit the database. Simply setting the Id of the objects will be enough to set the relationship up, but watch your cascades. Make sure your cascde settings won't update the related object as it will clear the values.

Resources