Breeze fetch causing unmapped original values to be lost - breeze

I have a case where a breeze entity loses it's originalValues for unmapped properties when that entity is fetched a second time.
I know why this is happening I just don't think it should.
The originalValues are being cleared on the subsequent fetch because breeze sees that the entity is not modified and correctly merges in the incoming values from the server. However, my entity has unmapped properties that have changed and whose original values are stored in the originalValues hash. These are being cleared.
I can understand an argument that says this should be the behaviour with a MergeStrategy set to OverwriteChanges.
However, this clearing of originalValues occurs with a MergeStrategy of PreserveChanges. That behaviour I do not think is a good idea.
With a MergeStrategy of PreserveChanges, when an entity is considered unmodifed, etc, and the incoming values are merged in I do not think originalValues should be cleared.
Now there is a possible workaround by setting using a MergeStrategy of SkipMerge, however this causes problems as the entity is then never refreshed with incoming values.
So can this be behaviour be changed please?
Thanks
Christian

A very interesting use case ... one worth some serious reflection.
Let's walk through the semantics of merging query results when the MergeStrategy is "PreserveChanges".
First, some observations:
Breeze does not change the EntityState when you change an unmapped property
Breeze DOES preserve the pre-change value of an unmapped property in OriginalValues.
Let's consider how Breeze merges query results:
when the EntityState is in a changed state, all properties (mapped and unmapped) are preserved even if a corresponding queried property value is different.
when the EntityState is "Unchanged", all properties (mapped and unmapped) are replaced by corresponding queried property values discovered in the query results payload.
Breeze does not support a partial merge. It will not overwrite some properties and preserve others.
I don't know for certain why query processing clears OriginalValues (I say that without having looked into it yet or even confirmed that it does). But I think it is doing the right thing.
Consider what it means when any property is merged into an unchanged entity. Suppose property foo was 1 before the query and the new queried value is 2.
Subsequently, we set foo to 3. What should the the "original value" be at that time? It should be 2, right? It shouldn't be 1. It should be the last value retrieved from the server.
It follows that after a merge, original values are implicitly identical to the entity's most current values. That is, if a property had a value in OriginalValues, it should be identical to the corresponding current value. And if the current value === the original value, we might as well remove that property from the OriginalValues ... which means clearing the OriginalValues for that property is harmless.
Because Breeze performs full merges (not partial merges), it follows that clearing the entire OriginalValues is consistent behavior.
According to this line of reasoning - if you believe that mapped and unmapped properties should follow the same merge rules - Breeze is doing exactly the right thing.
Perhaps what you want is to treat a change to an unmapped property as a change to the EntityState.
I think we did that at one time in the past but we were persuaded that the EntityState should only reflect changes to mapped properties which are presumed to be the persisted properties.
It appears that you disagree, at least in your specific use case. I think (please confirm) that when you change an unmapped property, you want Breeze to treat the entity as "dirty".
Fortunately, you can handle this yourself. Add a handler to the EntityManager.entityChanged event and change the EntityState when something updates an unmapped property. You can do this broadly or selectively.
You'll have an uphill climb if you really seek different treatment of mapped and unmapped properties. We'll listen of course ... but you'll have to make one heck of a great argument.
You might get further by proposing something more general purpose such as a new MergeStrategy that supports a developer-defined custom policy.
Maybe we'd extend the metadata such that you can specify a merge function that governs how Breeze merges properties for a given EntityType. That would be cool.
It would be a significant new feature, requiring a ton of testing. I think it's going to take a lot of persuading to get that one on the board.

Related

BreezeJS mixing cache and server data unexpectedly

I'm having a weird (or at least unexpected) problem with Breeze. I have an EF back-end view model to which I send some parameters and it returns some data. I use the withParameters option to do this. The back-end does a lot of Includes and projection and returns the data I want to display in a set of custom view model entities (i.e., not database entities). One of the parameters is a list of keys for which I want to display data.
The keys identify which children of the parent entities I want to retrieve, though I am retrieving a list of parents (e.g. keys [1,2] mean it should get all Parent entities with a Children list property that themselves have a ToyId property that has a value in keys and those Child entites). In other words, the structure is like Parent.Child[] and Child.ToyId and I want to get parents with children that have certain toys and those children themselves (but not other children). Both parent and child sets are large so I do this in SQL via EF (which was an adventure in itself).
Anyway, the problem happens after I select two keys and get the data and then de-select one of the keys. The first query, getting the data for two keys, works as expected. On the second executeQuery's callback, I get the same data as the previous query, meaning it's as if I never de-selected the key. I've verified that Breeze hits the back-end with the correct keys parameter value and the back-end returns just the data I want, but it seems that Breeze is ignoring the data from the back-end or performing a union on the result set from the back-end and its cached entities (for both keys) and sending that union as results into the callback instead of just what the server returned. Is this expected behavior? Unfortunately everything is written this way. We (working on this, our first project using Breeze) all assumed it would only return what the server sent when not using executeQueryLocally, so it will be a big deal to refactor. Sigh.
I tried some where predicates which didn't work and don't see how projecting on the Breeze side would help either. I thought maybe it saw the query as identical so it returned cached data as a shortcut, so I added a where('Parent.Children', 'any', 'ToyId', 'in', keys), but that didn't work, it still brings in the de-selected results.
The only way I've found to get around this is via queryManager.clear() before I make any of the queries, and I suspect doing a noTracking query might work also (albeit without actual entity objects). I thought about converting the keys parameter into a where filter and sourcing it from there instead of the keys parameter, in case that would tell Breeze to only show the back-end data.
Is there a "correct" way of getting back only the data the server sends in the callback?
("The callback" meaning the function passed into executeQuery.then(...))
As you've noticed, Breeze merges the query results into the cache before calling your callback method. This means that, in the callback, a parent entity will have all of its children that exist in the cache.
The array of entities returned by the query is available in the retrievedEntities property of the saveResult object returned in the callback. You can use this to determine which children were returned from the query.
entityManager.executeQuery(query).then(function(data) {
var parents = data.results;
var ret = data.retrievedEntities;
//... filter ret to get the children
//... update viewmodel with children
});

Using breeze.js I only want to send updated properties for a non-cached entity

I have a scenario where I know the primary key of an entity (retrieved from an unrelated source), and I want to update just 1 property (db column). I have NOT already retrieved the entity from the database. If possible I would like to not have to make this extra round trip.
I create the entity using manager.createEntity.
I update one of the properties.
Then set the entityAspect to setModified();
When saving changes, all the properties that were not updated are set to their default values, and the generated SQL UPDATE statement attempts to update all mapped columns.
Is there a way to tell breeze to only generate SQL for specific properties/columns?
thanks
As you discovered, the properties of the originalValuesMap guide the Breeze server's ContextProvider as it prepares the save request. This is documented in the ContextProvider topic.
In your example, you call setModified after you've changed the property. All that does is change the EntityState; it doesn't create an entry in the client entity's entityAspect.originalValuesMap ... therefore the originalValuesMap sent to the server is empty.
I'm a little surprised that the EFContextProvider.SaveChanges prepared an EF update of the entire entity. I would have guessed that it simply ignored the entity all together. I'm making a mental note to investigate that myself. Not saying the behavior is "right" or "wrong".
You do not have to manipulate the originalValuesMap to achieve your goal. Just change the sequence. Try this:
var foo = manager.createEntity('Foo', {
id = targetId
}, breeze.EntityState.Unchanged); // create as if freshly queried
foo.bar = 'new value'; // also sets 'originalValues' and changes the EntityState
manager.saveChanges(); // etc.
Let us know if that does the trick.

iOS: update a field in model any time managed object model gets modified

I have a "last updated" field on my model. Any time a managed object model gets changed, I'd like that field to get updated. Is there any way I can have this happen automatically? Or do I need to just update that field manually when I make the other modifications?
"Is there any way I can have this happen automatically?"
No. Core Data is not a database, it's an API for abstract object graph management. It has no concept of uniqueness, auto-incrementing values, etc.
"Or do I need to just update that field manually when I make the other modifications?"
Correct, for example having a lastModified property that your application sets with each change.
You could KVO all keys in your entity but maybe this is not necessary. I would suggest you only update the lastUpdated property when you actually save the object because before that you could still just throw away the changes (either on purpose or e.g. because the app terminates).
Therefore, you can simply override willSave. This is specifically designed for this kind of situation (it mentions a "last-modified" attribute), but note the caveats mentioned in the documentation
If you want to update a persistent property value, you should typically test for equality of any new value with the existing value before making a change. If you change property values using standard accessor methods, Core Data will observe the resultant change notification and so invoke willSave again before saving the object’s managed object context. If you continue to modify a value in willSave, willSave will continue to be called until your program crashes.
For example, if you set a last-modified timestamp, you should check whether either you previously set it in the same save operation, or that the existing timestamp is not less than a small delta from the current time. Typically it’s better to calculate the timestamp once for all the objects being saved (for example, in response to an NSManagedObjectContextWillSaveNotification).

Using this.Context inside BeforeSaveEntity

I was looking for a good way to organize validation rules within BeforeSaveEntity method and I have found this comment in the file: TodoContextProvider.cs within the project: BreezeMvcSPATemplate:
// A second DbContext for db access during custom save validation.
// "this.Context" is reserved for Breeze save only!
Why this.Context can not be used?
Excellent question. The answer isn't obvious and it's not easy to cover briefly. I will try.
The EFContextProvider takes the save data from the client and (ultimately) turns these data into entities within the EFContextProvider.Context. When the save is approved, the EFContextProvider calls the SaveChanges method on this EF Context and all of its contents are saved as a single transaction.
There are two potential problems.
1. Data integrity and security
Client data can never be fully trusted. If you have business rules that limit what an authorized user can see or change, you must compare the client-derived entity to the corresponding entity from the database.
An EF Context cannot contain two copies of the "same entity". It can't hold two entities with the same key. So you can't use the EFContextProvider.Context both to fetch the clean copy from the database and to hold the copy with changes.
You'll need a second Context to get the clean copy and you'll have to write logic to compare the critical values of the entity-to-save in the EFContextProvider.Context with the values of the clean entity in the second Context.
2. Cross-entity validation
Many validation do not require comparison of values with a clean entity.
For example, the out-of-the-box System.ComponentModel.DataAnnotations attributes, such as Required and MaxLength are simple data validations to determine if an entity is self-consistent. Either there is a value or there is not. The value is less than the maximum length or it is not. You don't need a comparison entity for such tests.
You could write your own custom System.ComponentModel.DataAnnotations attributes that compare data values within a single entity. You might have a rule that says that order.InvoiceDate must be on-or-before order.ShipDate. that is also a self-consistency test and you won't need a comparison entity for that one either.
If these are the only kinds of validation you care about - and you're using an EF DbContext - you can let EF run them for you during its save processing. You won't need a second Context.
But cross entity validations are another story. In a cross-entity validation, entity 'A' is valid only when some condition is true for entity 'B' (and perhaps 'C', 'D', 'E', ...). For example, you may require that an order item have a parent order that is already in the database.
There is an excellent chance that the parent order is not in the EFContextProvider.Context at the time you are validating the order item.
"No problem," you say. "I'll just navigate to the parent with someItem.Order."
No you cannot. First, it won't work because lazy loading is disabled for the EFContextProvider.Context. The EFContextProvider disables lazy loading mostly to break circular references during serialization but also to prevent performance killing "n+1" bugs on the server.
You can get around that by loading any entity or related entities at will. But then you hit the second problem: the entity you load for validation could conflict with another entity that you are trying to save in this batch.
The EFContextProvider doesn't populate its Context all at once. It starts validating the entities one-by-one, adding them to the Context as it goes.
Continuing our example, suppose we had loaded the parent order for someItem during validation. That order is now in EFContextProvider.Context.
The save process continues to the next entity and ... surprise, surprise ... the next entity happens to be the very same parent order. The EFContextProvider tries to attach this copy to the Context which already has a copy (the one we just loaded) ... it can't.
There's a conflict. Which of the two orders belongs in the EFContextProvider? The clean copy we just loaded for validation purposes ... or the one that came from the client with modifications to be saved?
Maybe you think you know the answer. Maybe I agree. But the fact is, the EFContextProvider throws an exception because there is already an order with that key in the Context.
Conclusion
If all your validations are self-consistency checks, the EFContextProvider.Context is all you need. You won't have to create a second Context
But if you have data security concerns and/or business logic that involves other entities, you need a second Context ... and you'll need sufficient EF skills to use that Context.
This is not a limitation of Breeze or the Entity Framework. Non-trivial business logic demands comparable server-side complexity no matter what technology you choose. That's the nature of the beast.

Validating navigation properties, bug or feature?

I am having problems with the validation of navigation properties.
I do not know if it's a bug or just does not work as I expected.
When you have a navigation property required with his foreign key (in my case int type) is successfully added the validator and, as an int can not be null, is assigned the default value 0, but when the entity is being validated, as the property has value (0), is given as correct. I expected that the entity was not valid.
Is it a bug or correct behavior?
Greetings.
It is a good question. I don't think this is a bug or a feature. Nor would I consider an async validation option.
Instead, I would add a custom validation on the FK (or the property) that declared that the FK property is invalid when it is 0. Yes, that means the entity is in an invalid state at the moment of creation. Nothing can be done about that. You're using '0' as a sentinel value that means "I don't know yet". The validation means "... and the entity is invalid until I know."
Btw, Breeze cannot assume that 0 is invalid. That could be a valid FK to the related entity.
This problem isn't unique to FKs. You have it when creating a new order lineitem with 'quantity=0' and your business rules say it must be >0. The type requires you to specify SOMETHING and yet you cannot provide the correct answer a priori. Again, Breeze can't assume that '0' is a bad value. You have to specify that.
There is no bug per se in either scenario. Both require additional developer attention to "do the right thing".
It's a good question.
Breeze can't actually validate the foreign key alone without performing a roundtrip to the datastore/database, and if it did, validation would become a asynchronous operation. Currently, breeze does not support async validations, but, this might be exactly what you want in some scenarios.
There are some problems with async validations that we would need to tackle. The most important is that another operation such as a save or requery or another change to the fk field might occur before the first validation completes. These can all be handled but there is additional complexity.
If you think that this would be a useful addition, please add it to the Breeze UserVoice. We take these submissions seriously.

Resources