Reputation: 1541
I'm probably not explaining this well...
I'm using breeze js to materialise an entity and that entity contains nearly 100 float fields. These are calculation result fields where there are 4 individual parts and then a total and this is repeated 19 times. So 19 x 5 = 95 fields, hence "nearly 100".
It's basically:
calculationResult1_materials
calculationResult1_processes
calculationResult1_packaging
calculationResult1_transport
calculationResult1_total
calculationResult2_materials
calculationResult2_processes
calculationResult2_packaging
calculationResult2_transport
calculationResult2_total
calculationResult3_materials
etc...
As would seem obvious, the "total" field contains the sum of the other 4 parts but is also stored as there is a legacy system that accesses just that field and this prevents the sum having to be done every time someone wants to see the total.
As breeze materialises the total fields as a plain observable just like the other fields, it means that every time I recalculate any one of the 4 other fields for any of the calculations, I have to manually re-sum the 4 individual constituent parts (materials, packaging, processes, transport) and put the result into the "total" observable which is bound to the div in my html page that displays it.
Whereas this works, it strikes me that this may not be the most efficient way of doing things. Obviously I could independently create 19 computedObservables in my viewmodel that contain the 4 other fields for each calculation and then bind to that instead of each actual "total" observable, but that would mean updating the code manually if a field name changed for example.
Is there any way of redefining the observable as a computedobservable in some way such that it would automatically calculate whenever any of the 4 other parts of the calculation changed and still be part of the entity so that breeze saved the changes afterwards?
Upvotes: 0
Views: 204
Reputation: 17863
I would NOT use a KO computed!
Instead, in the custom initializer, I would subscribe to the Breeze EntityAspect.propertyChanged
event with a recalculation function added to the EntityType's prototype).
This will be far lighter weight (one method instead of 1000s), more performant, and easier to maintain once you figure out the pattern for updating totals based on the name of the property that changed.
The code would look something like this:
function calculationResultCtor() {/*... stuff in the ctor */ }
calculationResultCtor.recalcTotals(propertyName, newValue)
/* do whatever based on the newValue and the propertyName
remember to use the parens required by KO observables
*/
}
function calculationResultInitializer(cr) {
// listen for any property change in this calculationResult instance
cr.entityAspect.propertyChanged.subscribe(function(args){
var propName = args.propertyName;
if (/_total/.test(propName)) return; // skip changes to total properties
args.entity.recalcTotals(propName, args.newValue);
});
}
metadataStore.registerEntityTypeCtor('CalculationResult',
calculationResultCtor, calculationResultInitializer);
I can think of even more clever ways (e.g., a dictionary of calculation fns, keyed by propertyName
) to make this more efficient but you see where I'm going.
The one thing I'd worry about (and if it's an issue it will be an issue for with your KO computed approach too) is what I call "propertyChanged" storms where some automated process is updating a lot of properties at once.
The solution for that - if it is a problem and I'm not saying that it is - would be to latch the recalc event so that it terminates during the storm and then, when it's over, you just recalc every total in a single pass through the entity.
The important point is where I started: do NOT create 1000s of KO computeds! And yes there will be 1000s because your plan would add at least 19 computeds per entity so a mere 53 entity instances puts you over 1000. That is not good.
Upvotes: 2
Reputation: 721
You should have something like "model.js" in your app where you define how Breeze handles data. You should expose function configureMetadataStore
and there you should place function:
function configureMetadataStore(metadataStore){
metadataStore.registerEntityTypeCtor('MyContainerClassFromServerModel', null, thisClassInitializer);
After that, you need to define thisClassInitializer
where you tell Breeze what to do with, as you call it, "materialized object":
function thisClassInitializer(myObject){
myObject.customTotal = ko.computed({
read: function(){
return myObject.materials() + myObject.processes() + myObject.packaging() + myObject.transport();
},
write: function(newValue){
myObject.total(newValue);
}
});
}
This should do the trick. Or what just came to my mind, you can define it like:
function thisClassInitializer(myObject){
myObject.customTotal = ko.computed({
var newValue = myObject.materials() + myObject.processes() + myObject.packaging() + myObject.transport();
myObject.total(newValue);
return newValue;
});
}
The difference is that for the first one I can't guarantee without trying it (not that skilled with this ko binding), but for the other one I'm quite sure it should work.
Explanation: When you define something like this, whenever Breeze "materializes" an object from server into an observable object, it will call initializer as well. This way you can modify what fields your object has - usually you put computed values in there (like total) so you don't need to save it on the server as well. Ideally, you don't need "total" on server because it holds no additional information - all the information is in other variables and this value can be calculated.
Upvotes: 1