breeze.js navigation properties not available to custom initializer when importing - breeze

When retrieving entities and metadata from the server my custom initializer functions are able to access thier navigation properties as expected. When exporting metadata and entities to localstorage and reimporting them the navigation properties are not available. I have ensured that the ctors and initializers are registered to the metadatastore before import as the methods are called. I took a look at the breeze source and it appears that the initialize functions are called prior to the item being fully populated and attached.
targetEntity = entityType._createEntityCore();
updateTargetFromRaw(targetEntity, rawEntity, dataProps, true);
if (newTempKeyValue !== undefined) {
// fixup pk
targetEntity.setProperty(entityType.keyProperties[0].name, newTempKeyValue);
// fixup foreign keys
if (newAspect.tempNavPropNames) {
newAspect.tempNavPropNames.forEach(function (npName) {
var np = entityType.getNavigationProperty(npName);
var fkPropName = np.relatedDataProperties[0].name;
var oldFkValue = targetEntity.getProperty(fkPropName);
var fk = new EntityKey(np.entityType, [oldFkValue]);
var newFkValue = tempKeyMap[fk.toString()];
targetEntity.setProperty(fkPropName, newFkValue);
});
}
}
*targetEntity.entityAspect._postInitialize();*
targetEntity = entityGroup.attachEntity(targetEntity, entityState);
if (entityChanged) {
entityChanged.publish({ entityAction: EntityAction.AttachOnImport, entity: targetEntity });
if (!entityState.isUnchanged()) {
entityGroup.entityManager._notifyStateChange(targetEntity, true);
}
}
}
if (targetEntity) {
targetEntity.entityAspect.entityState = entityState;
if (entityState.isModified()) {
targetEntity.entityAspect.originalValuesMap = newAspect.originalValues;
}
entityGroup.entityManager._linkRelatedEntities( targetEntity);
}
If I move the post initialize call below the ._linkRelatedEntities call it appears to work exactly as the query materialization
if (targetEntity) {
targetEntity.entityAspect.entityState = entityState;
if (entityState.isModified()) {
targetEntity.entityAspect.originalValuesMap = newAspect.originalValues;
}
entityGroup.entityManager._linkRelatedEntities( targetEntity);
*targetEntity.entityAspect._postInitialize();*
}
Am I missing something here? Is this the designed functionality? I have found a work around by passing in the EntityManager, setting the entityAspect manager and calling loadNavigationProperties however this seems redundant. Any insight fromt he Breeze folks would be greatly appreciated.
Thanks,
Brad

Not sure but this issue might be related to a bug that we just fixed in Breeze 1.4.1 available now.

Related

breezejs how to using as datasource

i'm using breezejs for some time now, and i'm happy about it and it's rich functionality , but the problem with breezejs is that i cant use it as datasource almost for anything.
there is no grid that you could show data and not losing functionality, and you cant use your array of entities as normal array. (so you cant use it as datasource for dropdown ...)
so for showing data in UI i end up converting data to normal array and losing Breeze functionality (like track change) and before save converting them back.
some code like this one for converting to normal array:
if(data.length>0)
{
var names = Object.getOwnPropertyNames(data[0]._backingStore);
var columns: string[] = [];
for (var i = 0; i < names.length; i++)
{
columns.push(names[i]); //Getting columns name
}
var newArray = [];
data.forEach((item, index, array) => {
newArray.push(item._backingStore);
});
}
my question is how do you show your data in UI using breezejs properly?
(i'm using angualr (hottowel))
Assuming you're trying to solve issues like this:
The grid doesn't like the entityType and entityAspect properties on Breeze entities.
This grid doesn't know how to handle Knockout style "properties" that are functions.
Creating a POCO object using the Breeze entity's property values disconnects you from the change tracking goodness.
You could try creating your POCO object using Object.defineProperty, using the Knockout observable as the property's getter and setter functions. Here's a simple example:
Typescript + Knockout:
class PocoBreezeEntity {
constructor(private entity: breeze.Entity) {
entity.entityType.dataProperties.forEach(
dataProperty => {
Object.defineProperty(
this,
dataProperty.name,
{
get: entity[dataProperty.name],
set: entity[dataProperty.name],
enumerable: true,
configurable: true
});
});
}
}
Typescript + Angular:
class PocoBreezeEntity {
constructor(private entity: breeze.Entity) {
entity.entityType.dataProperties.forEach(
dataProperty => {
Object.defineProperty(
this,
dataProperty.name,
{
get: function() { return entity[dataProperty.name]; },
set: function(newValue) { entity[dataProperty.name] = newValue; },
enumerable: true,
configurable: true
});
});
}
}
With this kind of approach you have the benefits of POCO entities without losing the Breeze change tracking.

Can I assign a Future to a controller's attribute in AngularDart?

I need to get a list of values asynchronously from the server. The API's method returns a Future.
Of course I can create a callback and assign the result manually, I wonder if there's a way to delegate this to AngularDart?
class IndexPageController {
var apps;
EasyAPIClient api = new EasyAPIClient('/api/');
IndexPageController() {
_loadData();
}
_loadData() {
api.getAppNames().then((app_names){
apps = app_names;
});
}
}
What I'd like to have approximately:
class IndexPageController {
var apps;
EasyAPIClient api = new EasyAPIClient('/api/');
IndexPageController() {
_loadData();
}
_loadData() {
// this code executes successfully,
// however the template never receives the list.
apps = api.getAppNames();
}
}
AngularDart doesn't currently support Futures/Streams in templates.

BreezeJS - Using expand

I am querying the server to get an entity with expand
function _loadIncidents() {
var deffered = Q.defer(),
queryObj = new breeze.EntityQuery().from('Incidents').expand(['Deployments', 'IncidentComments', 'DTasks', 'ExtendedProperties', 'IncidentEvents']);
dataRepository.fetchEntitiesByQuery(queryObj, true).then(function (incidents) {
var query = breeze.EntityQuery.from("DTasks"),
incidentIds = dataRepository.getEntitiesByQuerySync(query);
deffered.resolve();
}, function(err) {
deffered.reject(err);
});
return deffered.promise;
};
I am getting the results and all is fine, how ever when I query breeze cache to get the entities - I am getting empty collection. So when using expand does the expanded entities are added to the cache?
Yes the related entities identified in the expand should be in cache ... if the query is "correct" and the server interpreted your request as you intended.
Look at the payload of the response from the first request. Are the related entities present? If not, perhaps the query was not well received on the server. As a general rule, you want to make sure the data are coming over the wire before wondering whether Breeze is doing the right thing with those data.
I do find myself wondering about the spelling of the items in your expand list. They are all in PascalCase. Are they these the names of navigation properties of the Incident type? Or are they the names of the related EntityTypes? They need to be former (nav property names), not the latter.
I Had problem with the navigation property - as I am not using OData webapi not using EF , there is problem with the navigation properties so for the current time i just wrote
Object.defineProperty(this, 'Deployments', {
get: function () {
return (this.entityAspect && this.entityAspect.entityManager) ?
this.entityAspect.entityManager.executeQueryLocally(new breeze.EntityQuery("Deployments").
where('IncidentID', 'eq', this.IncidentID)) :
[];
},
set: function (value) { //used only when loading incidents from the server
if (!value.results) {
return;
}
var i = 0,
dataRepository = require('sharedServices/dataRepository');
for (i; i < value.results.length; i++) {
dataRepository.addUnchangedEntity('Deployment', value.results[i]);
}
},
enumerable: true
});

How do I set a property on a breeze entity client-side?

I've tried drilling down into the object and looking at the docs but haven't found anything. I've created an entity and I need to assign some properties manually. I see _backingStore and entityAspect on the object... and I know the property names but don't know how to set them via the breeze entity.
In case it matters, I'm creating a new object and then copying properties over from another object to facilitate cloning.
function createDocument() {
var manager = datacontext.manager;
var ds = datacontext.serviceName;
if (!manager.metadataStore.hasMetadataFor(ds)) {
manager.fetchMetadata(ds).then(function () {
return manager.createEntity("Document");
})
}
else {
return manager.createEntity("Document");
}
}
function cloneDocument(doc) {
var clonedDocument = createDocument();
// Copy Properties Here - how?
saveChanges()
.fail(cloneFailed)
.fin(cloneSucceeded);
}
Not knowing what your properties might be, here are two scenarios -
function cloneDocument(doc) {
var clonedDocument = createDocument();
clonedDocument.docId(doc.docId());
clonedDocument.description(doc.description());
saveChanges()
.fail(cloneFailed)
.fin(cloneSucceeded);
}
There are a few things to note here - I am assuming you are using Knockout and needing to set the properties. If you are not using Knockout then you can remove the parans and use equals -
clonedDocument.docId = doc.docId;
I believe this is true for if you are not using Knockout (vanilla js) and if you are using Angular, but I have not used Breeze with Angular yet so bear with me.
And here's another way that works regardless of model library (Angular or KO)
function cloneDocument(doc) {
var manager = doc.entityAspect.entityManager; // get it from the source
// Check this out! I'm using an object initializer!
var clonedDocument = manager.createEntity("Document", {
description: doc.description,
foo: doc.foo,
bar: doc.bar,
baz: doc.baz
});
return clonedDocument;
}
But beware of this:
clonedDocument.docId = doc.docId; // Probably won't work!
Two entities of the same type in the same manager cannot have the same key.
Extra credit: write a utility that copies the properties of one entity to another without copying entityAspect or the key (the id) and optionally clones the entities of a dependent navigation (e.g., the order line items of an order).

Breeze.js: how to transform a Breeze entity to a plain JSON object

I have an entity instance created using the metadata coming from the server:
breeze.NamingConvention.camelCase.setAsDefault();
...
var applicationType = metadataStore.getEntityType('Application');
var application = applicationType.createEntity();
None of the objects in this particular model has circular dependencies.
After the user has made some changes to the corresponding object, I need to perform some custom validation on that entity, so I need to convert that object back to its JSON simple form and send it back to a validation Controller (ASP.NET MVC4).
The question is how to transform the entity to JSON such that:
The resulting object reflects the naming conventions used in the server side.
The object contains simple properties and not knockout observables.
And does not contain any other additional property or function used internally by breeze.
I was expecting to find something like:
var json = application.toJS();
But such method does not exist. Using ko.toJS(application) also doesn't work because (1), (2) and (3) are not fulfilled.
I'm sure this should be trivially easy to do, but I just can't find anything even remotely related on the documentation.
UPDATE: You'll forgive me for the horrible hack, but because I'm in a hurry what I did that temporarily solved my problem was just exposing the unwrapEntities internal function of entityManager. I also changed the function definition a little bit (just to exclude that annoying entityAspect):
function unwrapEntities(entities, metadataStore, includeEntityAspect) {
var rawEntities = entities.map(function(e) {
var rawEntity = unwrapInstance(e);
if (includeEntityAspect !== undefined && includeEntityAspect === false) {
return rawEntity;
}
...
});
}
And because I have the entityManager available at all times in my services, I was able to extend my types definition to do something like the following:
function createApplicant(initialValues) {
var applicant = applicantType.createEntity(initialValues);
applicant.toJS = function () {
var unwrappedEntities = entityManager.unwrapEntities([applicant], entityManager.metadataStore, false);
return unwrappedEntities[0];
};
return applicant;
}
And that's precisely what I need:
var json = application.toJS();
This is a good idea! Could you please add it to the Breeze User Voice. It makes a lot of sense to expose the "unwrapping" of a Breeze entity.
Just a small side note, the unwrap "hack" that you wrote may not work in a future version of Breeze because we are currently in process of refactoring some of this code, but I will try to expose a "cleaner" version as part of the Breeze api.
This may be outdated solution but I did some hack in my typescript class to do this without help from the breeze core.
I did this hack since my breeze version and typing file did not include the unwrap method and I did not want to upgrade it just now.
Another benefit is that the result class is absolutely clean of any extra properties not included in the metadata as well as you can control if you want just the plain entity or if you want the related entities embedded in the result as well.
Just include this code in your typescript class to get this done:
public unwrapEntity(entity: breeze.Entity, includeRefs: boolean): any {
var refs = [];
return this.unwrapEntityInner(entity, refs, includeRefs);
}
private objInArray(obj: any, refs: Array<any>): boolean {
var ret = false;
for (var i = 0; i < refs.length; i++) {
if (obj === refs[i]) {
ret = true;
break;
}
}
if (!ret)
refs.push(obj);
return ret;
}
private unwrapEntityInner(entity: breeze.Entity, refs: Array<any>, includeRefs: boolean): any {
var data = {};
for (var prop in entity) {
if (entity.hasOwnProperty(prop) && ko.isObservable(entity[prop])) {
data[prop] = entity[prop]();
if (typeof data[prop] !== 'undefined' && data[prop] != null) {
if (typeof data[prop].entityAspect !== 'undefined') {
data[prop] = (includeRefs && !this.objInArray(data[prop], refs)) ? this.unwrapEntityInner(data[prop], refs, includeRefs ) : null;
}
else if ($.isArray(data[prop])) {
if (!includeRefs || this.objInArray(data[prop], refs))
data[prop] = [];
else {
var tmp = data[prop];
data[prop] = [];
for (var i = 0; i < tmp.length; i++) {
data[prop].push(this.unwrapEntityInner(tmp[i], refs, includeRefs));
}
}
}
}
}
}
return data;
}

Resources