Bug when deleting unsaved entity - breeze

I think I've found a bug when you add a child entity to another entity and then mark the child entity as deleted (Without saving changes previously).
Here is the test:
test("delete unsaved entity", 1, function () {
var realEm = newEm();
//ok(realEm.hasChanges() === false, "The entity manager must not have changes");
var query = EntityQuery.from("Customers")
.where("CustomerID", "==", "729de505-ea6d-4cdf-89f6-0360ad37bde7")
.expand("Orders");
stop();
realEm.executeQuery(query)
.then(function (data) {
var cust = ko.observable(data.results[0]);
var newOrder = realEm.createEntity("Order", {}, breeze.EntityState.Detached);
cust().Orders.push(newOrder);
//ok(newOrder.entityAspect.entityState.isAdded() === true, "The entity is Added");
newOrder.entityAspect.setDeleted();
//ok(realEm.hasChanges() === true, "The entity manager must have changes? Not clear to me but it's true");
realEm.saveChanges();
ok(realEm.hasChanges() === false, "The entity manager must not have changes");
})
.fin(start);
});

As of breeze v1.0.0 this bug is now fixed. Sorry for the delay
Old post below
Thanks for the repro, it really helps. There is a bug here and it will be fixed in the next release probably later today or early tomorrow.
Just to be clear the bug is that the 'hasChanges' call after the 'setDeleted' call should return false, but currently returns true. The reason is that deleting an 'added' record simply detaches the entity from the entityManager; hence reverting the entityManager to the state it was in before the 'add'. The detach does occur, but the hasChanges function is broken in this case.
Two other issues though.
First, you can replace this line
var cust = ko.observable(data.results[0]);
with this
var cust = data.results[0];
because Breeze will automatically create ko observables out of any entity returned from a query.
and second your call to saveChanges
realEm.saveChanges();
ok(realEm.hasChanges() === false);
needs to be converted to a promise, because saveChanges is asynchonous.
realEm.saveChanges().then(function(r) {
ok(realEm.hasChanges() === false);
}

Related

How to push deletions to local breeze caches?

If I'm using breezejs and one client deletes a record from their cache and it saves to the server, then another client comes along with that record still in their cache, how do I get the second client's cache to update and remove the record that has been hard deleted?
It's a good question and we don't have a really great answer. We refer to these as 'ghost' entities, and the approach we have taken to them in the past is that if you requery for an entity ( or entities) by id, and they are not returned then you can safely remove them from the entityManager. (The code below has not been tested, but should give you the idea);
function checkIfDeleted(entityManager, entities) {
origEntities = entities.slice(0);
var q = EntityQuery.fromEntities(entities);
entityManager.executeQuery(q).then(function(data) {
var foundEntities = data.entities;
foundEntities.forEach(function(e, ix) {
if (entities.indexOf(e)) {
origEntities.splice(ix,1)
}
});
if (origEntities.length > 0) {
origEntities.forEach(function(e) {
entityManager.removeEntity(e);
});
}
});
}

breeze EntityManager: how to attach plain javascript object

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

Breezejs [Q] Unhandled rejection reasons (should be empty)

These days,i have learned breezejs,durandaljs, so i made an spa application for excersizing,but breezejs(or q.js) often gives out errors
[Q] Unhandled rejection reasons (should be empty): ["proto.saveChanges#http:...s/jquery-1.9.1.js:2750\n"] (Firefox)
[Q] Unhandled rejection reasons (should be empty):(no stack) Error: Client side validation errors encountered - see the entityErrors collection on this object for more detail (IE10, but why deleteing an entity triggers validation ?)
I feel disappointed to use breezejs, what on earth am i doing!!!
I just do saving and deleting customer, sometimes error occured as above, sometimes works fine.(how confused i am feeling! :'( )
Here is part of my datacontext
var saveChanges = function () {
return manager.saveChanges()
.then(saveSuccess)
.fail(saveFailure); //.done() does not work either
//
function saveSuccess() {
console.log("Save Success!");
}
//
function saveFailure(error) {
console.log("Save Failure!");
throw error;
}
};
To save a customer:
define(['modules/dataService'], function (datacontext) {
var ctor = function () {
this.entity = ko.observable();
};
ctor.prototype.activate = function () {
//problem code --> [Q] Unhandled rejection reasons (should be empty)
//it will always create empty Customer when activate is called.
//so error occured when i switch in because of creating empty Customer every time.
this.entity(datacontext.createEntity('Customer'));
};
ctor.prototype.saveClick = function () {
if (this.entity().entityAspect.validateEntity())
datacontext.saveChanges();
else
console.log('validation error!');
};
return ctor;
});
To delete a customer
define(function (require) {
var datacontext = require('modules/dataService');
var vm = {
customers: ko.observableArray(),
activate: function () {
var self = this;
return datacontext.getCustomers(self.customers);
},
deleteCustomer: deleteCustomer
};
return vm;
//delete customer
function deleteCustomer(customer) {
vm.customers.remove(customer);
//Sets the entity to an EntityState of 'Deleted'
customer.entityAspect.setDeleted();
datacontext.saveChanges();
}
});
I think my code would work fine, but it can't!
Where is the fatal error i make? plz let me know.
Thanks in advance!
I know this thread has been here for more than a year now but I thought I could share my story.
I just got the same error while using breeze + angularJS. After some research, I figured it out:
I was passing null values in some of the entitie's properties while those fields in the database table where marked as NOT NULL.
Breeze - saveChanges
In the implementation of breeze.saveChanges, a check is done on a internal flag (line 12743 approx : if (this.validationOptions.validateOnSave) ...)
This is to enable verification of the entity against the database schema (aka the metadata).
Now most of the time we tend to call saveChanges without any parameters. And the error does not show otherwise then in the console as a general validation error message.
What have i done
We'll my fix was in 2 parts:
Add some code in my calls to saveChanges in order to trap these errors and display a better message in the console (see code below)
Fix either the DB schema (i.e. Relax NOT NULL fields to NULLABLE) OR set some default values OR enforce the business logic by adding required attributes to input controls.
Here's a snippet of the code I now use to trap the errors:
return manager.saveChanges(null, null, null, function (errors) {
console.log('breeze saveChanges returned some errors: ');
errors.entityErrors.forEach(function(e) {
console.log(e.errorMessage, e);
});
}); // return promise

Find id for specific entity after savechanges

I have a situation, where I am saving multiple types of entities during a single SaveChanges. In some cases, this save will include my "target entity", in some cases not. In those cases where the save does include a "target entity", i need to be able to trap the entity's id as returned from the server using saveChanges() saveResult.
I have been trying to figure out how to use the Breeze EntityType to see if my "target entity" in the the saveResult, but I keep on getting undefined in the approach below. Clearly I'm not understanding how to use this feature?
function trapTargetEntityId(saveResult) {
saveResult.entities.forEach(function(entity) {
if (entity.EntityType === 'targetEntity') {
targetEntitId = entity.id;
}
return;
});
}
Not sure I understand. But if you are looking for a specific entity by key after the save
// make sure that targetType is NOT null
var targetType = myEntityManager.metadataStore.getEntityType("Customer");
var targetId = 23452; // arbitrary id
function trapTargetEntityId(saveResult) {
saveResult.entities.forEach(function(entity) {
// assumes that you have a data property called 'id' on the 'Customer' entityType
if (entity.entityType === targetType && entity.id === targetId) {
targetEntity = entity;
// do something with 'targetEntity'
}
});
}
... and be careful with your casing - in your example it should have been 'entity.entityType' not 'entity.EntityType'

what to expect at the client side when returning false from BeforeSaveEntity

I'm looking for documentation about what to expect at the client side when returning false from BeforeSaveEntity(EntityInfo entityInfo) but i found nothing so I decided to experiment myself.
I made a test on the Doccode project:
test("create customer returning false from contextprovider", 1, function () {
var em = newEm();
var category = em.createEntity('Category', { CategoryName: 'Julian Category' });
stop();
em.saveChanges().then(function (saveResult) {
//ok(category.entityAspect.entityState.isAdded(), "Added state because contextprovider return false");
ok(em.hasChanges() === false,"No changes pending");
})
.fail(function (error) {
debugger;
})
.fin(function () {
start();
});
});
And i found that the two assertions were true, so i think that it may be a bug.
To make the test i created a custom provider:
public class CustomEFContextProvider : EFContextProvider<NorthwindContext>
{
public CustomEFContextProvider() : base()
{
}
protected override bool BeforeSaveEntity(EntityInfo entityInfo)
{
if(entityInfo.Entity.GetType() == typeof( Category)){
return false;
}
return true;
}
}
and changed the NorthwindController to use it:
readonly CustomEFContextProvider _contextProvider =
new CustomEFContextProvider();
I'm returning false when a Category is saved, so the category doesn't get inserted on the database and it's not returned in the saveResult.entities array. The keyMappings is also empty. All of this is what i expected.
What i didn't expect is that the entitymanager.hasChanges function returns false because the category entity is marked as added, what in my opinion leaves the manager inconsistent.
It's a bug? I'm doing something wrong? Were my expectations wrong?
Thx.
Ok, as of v 1.2.5 ( just released), this should be fixed. Please post back to confirm if possible, and thx for the bug report. :)
Hm... it's an interesting question.
The reason that EntityManager.hasChanges returns true is that you haven't saved the "added" Category object, and therefore breeze doesn't call EntityAspect.acceptChanges on it because it is still in fact in an "added" but not savable state.
I think this is actually the right behavior, because if we were to call acceptChanges automatically on this entity, you would never know on the client that it had not been saved. You can of course call acceptChanges directly within the promise callback.
Does this make sense?

Resources