breeze EntityManager: how to attach plain javascript object - breeze

I have custom OData action that is called from my client. The results from this action are a list of JSON objects which need to be merged back into the Breeze cache as entities. How do I convert a JSON object into a Breeze entity and merge that entity back into the entityManager's cache? Some code:
$http.post('/odata/MyEntityType/MyCustomAction/', {
'someData': JSON.stringify(element1),
'SomeOtherData': JSON.stringify(element2)
})
.success(function (results) {
//results.value is an array of *MyEntityType* JSON objects. `//Is there a way to convert these to breeze entities?`
});
Some things I have tried:
manager.importEntities("MyEntityType", theJsonForAnEntity); //just stabbing in the dark here
manager.createEntity("MyEntityType", theJsonForAnEntity); //error: A MergeStrategy of 'Disallowed' does not allow you to attach an entity when an entity with the same key is already attached"

createEntity won't work
Sorry, Jeremy, but that isn't going to work. Here's a test that shows why:
// Failing test
test("merge customer date into existing cached entity using `createEntity`", function () {
var em = newEm();
// Create an unchanged entity in cache as if it had been queried
em.createEntity('Customer', {
CustomerID: dummyCustID,
CompanyName: 'Foo Co',
ContactName: 'Ima Kiddin'
}, UNCHGD); // creates the entity in the Unchanged state
// Try to merge some changes into that entity using createEntity.
var changes = {
CustomerID: dummyCustID,
CompanyName: 'Bar Co',
}
var cust = em.createEntity('Customer', changes,
UNCHGD, breeze.MergeStrategy.OverwriteChanges);
ok(cust.entityAspect.entityState.isUnchanged(), "cust should be 'Unchanged'");
// using Knockout; it's simpler if using Angular
equal(cust.CompanyName(), 'Bar Co', "Company name should be updated by merge'");
equal(cust.ContactName(), 'Ima Kiddin', "Contact name should be unchanged after merge'");
});
The third assert fails because the createEntity method overwrites every property, not just the one data value of interest (CompanyName in this case). That means that ContactName is null.
importEntities won't work either
For the same reason. When you import an entity, you import the entire entity, not just some portion of it. So that too would wipe out the ContactName in this example.
Manual merge
I think if you want to blend the results with the entity in cache, you'll have to do that by hand. You'll have to iterate over the results and update the cached equivalents.
Imagine that the changes variable above is result of your POST. The following test does pass:
// Passing test
test("merge customer date into existing cached entity", function () {
var em = newEm();
// Create an unchanged entity in cache as if it had been queried
em.createEntity('Customer', {
CustomerID: dummyCustID,
CompanyName: 'Foo Co',
ContactName: 'Ima Kiddin'
}, UNCHGD); // creates the entity in the Unchanged state
// Merge some changes into that entity
// Imagine that `changes` came from your server as a result of your POST
var changes = {
CustomerID: dummyCustID,
CompanyName: 'Bar Co',
}
// First find the customer in cache.
// Here I'm assuming it will always be there; you should be careful
var cust = em.getEntityByKey('Customer', changes.CustomerID);
// using Knockout; it's a little simpler in Angular
for (var p in changes) { cust[p](changes[p]); }
cust.entityAspect.setUnchanged(); // cuz the previous updates changed the state to "Modified"
ok(cust.entityAspect.entityState.isUnchanged(), "cust should be 'Unchanged'");
equal(cust.CompanyName(), 'Bar Co', "Company name should be updated by merge'");
equal(cust.ContactName(), 'Ima Kiddin', "Contact name should be unchanged after merge'");
});
Feature Request
I can imagine a future in which Breeze could automate this for you with all of the appropriate error checks. If you think such a "JSON entity merge" feature would be a valuable enhancement to Breeze, please add that to User Voice. I would be quick to agree but we can't add features unless people want them.

I think you're onto something with the manager.createEntity approach. Try specifying a merge strategy (the default is "Disallowed" which is causing problems for your use case):
manager.createEntity("MyEntityType", thePocoEntityFromTheCustomODataAction, breeze.MergeStrategy.PreserveChanges);
Merge strategies: http://www.breezejs.com/sites/all/apidocs/classes/MergeStrategy.html
createEntity: http://www.breezejs.com/sites/all/apidocs/classes/EntityManager.html#method_createEntity

Related

breeze createEntity not initializing one of the navigation property

I am creating a breeze entity using createEntity of entitymanager. Upon creation I find that only 1 of its navigation property is null whereas others are assigned.
var chargeEntity = {
ClientId: data.ClientId,
PatientId: data.PatientId,
AssessmentLogId: data.AssessmentLogId,
MedicalRecordNbr: data.Patient.MedicalRecordNbr,
AssessmentTypeCd: data.AssessmentType.AssessmentTypeCd,
ReviewDate: new Date(),
Qty: 1,
InsertDate: new Date(),
ProductId: data.ProductId
};
var charge = datacontext.createEntity('Charge', chargeEntity);
I find that in the charge entity, AssessmentLog navigation property is null where as others like Client, Patient and Product are assigned.
What could be the reason ?
After debugging for a while, I found that in breeze.debug.js, in the function below:
proto._findEntityGroup = function (entityType) {
return this._entityGroupMap[entityType.name];
};
_entityGroupMap does not contain an entry for the navigation property AssessmentLog, hence that property is being set as null.
The entity AssessmentLog was not in cache for the AssessmentLogId, that's why the navigation property was being set as null.
I executed a breeze query for the AssessmentLogId, and then the charge entity's AssessmentLog was correctly set.

Added Entitties don't change entityState after entityManager.saveChanges()

I create a new Entity in EntityManager like this:
var newCust = manager.createEntity('Customer', { Name: 'MyNewCustomer' });
it's Id is correctly generated. On a Button-Click I save the Entity on the Server:
manager.saveChanges([newCust]).then(function (saveResult) {
alert('saved!');
}
Now everything works perfect BUT, the Entity in newCust keeps it's EntityState "Added". It doesn't change to "Unchanged".
I've debugged a little and found out, that in the function "mergeEntity(mc, node, meta)" of breeze, where the following code happens - no entity is found (eventhough the key it searches is the same Id - the Id-Fixup worked correctly):
var targetEntity = em.findEntityByKey(entityKey);
So everytime I save my new Entity, a new Entity will be created in my EntityManager. When I reload the page, the entity is flagged correctly and works. The only problem is, that a new entity will be saved everytime I save changes.
Thanks for any help!
Edit 1:
I found the problem and I'm not sure what exactly Breeze expects of me. When my ContextProvider saves the Entity it sends a DB-Update-Command (for all the properties) and then returns. The problem is, that the Id of the saveResult-Object is still the "tempId" and not the real one - so when Breeze on Client-Side receives the object it doesn't find it in the entityManager anymore because the Key-Fixup happened already.
What exactly is Breeze expecting to receive from the saveResult? A representation of the object as it is in the Database at this moment? - would make sense but I don't find it documented.
Edit 2:
It looks like I'm not able to replace the object in the saveWorkState.saveMap (EntityInfo info.Entity is readonly). What I would like to do is create the newly added object and return this object instead of the one that breeze sent me. I have calculated values on the newly created object and the new "real" Id. What the NoDB-Sample seems to do is just overwrite the Id for the newId but any other properties are not changing. Maybe I'm not using this mechanism right?
To make sure, that all EntityInfos that are returned in my SaveResult are the latest representation of the Database I just clear the saveWorkState.SaveMap-Lists
saveWorkState.SaveMap[n].Clear()
and add the new representations
saveWorkState.SaveMap[n].AddRange(newEntityInfos);
and for every Entity that is Added or Modified I create a new EntityInfo with the object that is returned by the Database
private EntityInfo SaveEntity(List<KeyMapping> keyMap, EntityInfo info) {
EntityInfo result = info;
switch (info.EntityState) {
case EntityState.Added: {
// CreateInDatabase
// Possible changes in object properties happen (for calculated values)
...
var newObj = GetObjectAgainFromDatabase(info.Entity);
keyMap.Add(new KeyMapping() { EntityTypeName = bu.RuntimeClass.FullName, TempValue = (MyObject)info.Entity.Id, RealValue = newObj.Id });
result = CreateEntityInfo(newObj, EntityState.Added);
break;
}
case EntityState.Deleted: {
// Delete in Database
// EntityInfo doesn't have to change
break;
}
case EntityState.Modified: {
// Update in Database
result = CreateEntityInfo(bu.WrappedPOCO, EntityState.Modified);
break;
}
default: //EntityState.Detached, EntityState.Unchanged
break;
}
return result;
}
Any comments on this solution are welcome!

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
});

Breeze-Unreferenced Entity in Manager

I am developing a Single Page App using Hot Towel Template, using breeze...and I have come across a peculiar problem, and I am unable to figure out the internal working which causes it...
I have a Programmes table, and the Programmes table has a foreign key to Responses, so the structure of Programmes is:
Id, ResponseID, Name and Date
and the Page has Name and Date, the foreign comes from RouteData.
and for one ResponseId in Programmes table, I want to save only on Programme.
So, when user comes to this page, it check the Programmes table that if it has an Entry for that particular Response Id, if yes, it goes in Edit case and if not it goes to Add a new entry case.
To achieve this, what I am doing is below:
var objTempProgramme = ko.observable();
var objProgramme = ko.observable();
function activate(routeData) {
responseId = parseInt(routeData.responseId);
// Create a Programme Entity
objProgramme(datacontext.createProgramme());
// Fill in a Temporary Observable with Programmes data
datacontext.getEntitiesDetailsByRelativeId('responseID', responseId , 'Programmes', objTempProgramme, true).then(function(){
// Check if Programmes Exists
if (objTempProgramme() != null && objTempProgramme() != undefined) {
// What I am doing here is filling my Programmes Entity with data coming from database (if it is there)
objProgramme(objTempProgramme());
} else {
// The Else Part assigns the Foreign Key (ResponseId) to my Entity Created above
objProgramme().responseID(responseId);
}
});
}
In datacontext.js:
var createProgramme = function () {
return manager.createEntity(entityNames.programme);
}
var getEntitiesDetailsByRelativeId = function (relativeIdName, relativeId, lookupEntity, observable, forceRefresh) {
var query = entityQuery.from(lookupEntity).where(relativeIdName, "==", relativeId);
return executeGetQuery(query, observable, forceRefresh);
};
Now when I call manager.saveChanes on my page, I would Expect objProgramme to be saved, in any case, be it edit or be it save,
but what breeze is doing here is that though it is filling objTempProgramme in objProgramme, but it is also leaving the entity objProgramme unreferenced with its manager, so that when I call save, it tries to save that entity too (2 entities in total, objProramme and one unreferenced one), but that entity does not have foreign key set and it fails..but my question is why?
Assigning one entity to another does not mean all its properties get assigned to another? And why is that unreferenced entity present?

Breeze registerEntityTypeCtor not working

I am working on a Hottowel project and I want to format some data passed on from database to Breeze, but it looks like the ctor is not getting registered.
What am I doing wrong?
In datacontext.js:
var manager = configureManager();
function configureManager() {
var mng = new breeze.EntityManager('breeze/data');
breeze.NamingConvention.camelCase.setAsDefault();
model.configureMetadataStore(mng.metadataStore);
return mng;
}
In model.js:
function configureMetadataStore(metadataStore) {
metadataStore.registerEntityTypeCtor
('UserInfo', null, userInfoInitializer);
}
function userInfoInitializer() {
if (this.Email == "")
this.Email = '---';
this.CreateDate = formatDateTime(this.CreateDate);
}
function formatDateTime(DateTime) {
return moment.utc(DateTime).format('DD. MM. YYYY. [у] hh:mm');
}
Datacontext has a reference to model, the data is transferred from database and appears on the screen, but is not formatted. console.log() calls from userInfoInitializer() are not appearing.
When I am constructing an entity, my constructor needs to have an entity to construct. I have not tried your above code but I believe that Breeze passes an entity in and you need to give that entity properties. Using this. MAY work but it is the first thing that stands out to me.
function userInfoInitializer(user) {
if (user.Email == "")
user.Email = "---";
2nd - What is your entity named in your model? Is it UserInfo or just User? You probably already know this but you need to make sure when you are adding a constructor you use the properly named Entity.
3rd - If you are using camelCase then you need to leave the first letter of the property lowercase. Ex . user.email and user.createDate.
Last, I can't tell if you are creating a 'createDate' in the constructor or that is being passed from your model. If it is indeed a property you are creating I would recommend making it an knockout observable or computed property. If it is coming from the database then you need to do something like
if (!user.createDate) { } //set it
Remember that all of the entities being returned from the database will be given that property, so if you have entities that already have a createDate in your example you are overriding that date. If you want to set createDate to now then I would move that into your method where you are creating the object.

Resources