Possible Breeze 1.4.8 Bug? fetchEntityByKey not waiting for metadata - breeze

I have the following code that went from this:
var getUserByGuid = function (guid, entityObservable) {
return datacontext.manager.user.fetchEntityByKey('User', guid, true)
.then(fetchSucceeded)
.fail(queryFailed);
function fetchSucceeded(data) {
var entity = data.entity;
if (ko.isWriteableObservable(entityObservable))
entityObservable(entity);
return entity;
}
function queryFailed(error) {
logger.error(error);
}
};
to this:
var getUserByGuid = function (guid, entityObservable) {
if (datacontext.manager.user.metadataStore.isEmpty()) {
return datacontext.manager.user.metadataStore.fetchMetadata('breeze/user')
.then(function () {
return datacontext.manager.user.fetchEntityByKey('User', guid, true)
.then(fetchSucceeded)
.fail(queryFailed);
});
} else {
return datacontext.manager.user.fetchEntityByKey('User', guid, true)
.then(fetchSucceeded)
.fail(queryFailed);
}
function fetchSucceeded(data) {
var entity = data.entity;
if (ko.isWriteableObservable(entityObservable))
entityObservable(entity);
return entity;
}
function queryFailed(error) {
logger.error(error);
}
};
Notice the extra check to verify metadataStore is ready? Since I am making a call to fetch, I would assume this check would happen internally but for some reason it is not.
My code runs well with the following "work-around" in place but wanted to bring this to light.

Updated 3/1/2014
As of Breeze 1.4.9 (or later), available now this has been fixed.
Previous post
I think you are right. The problem, I think, is that fetchEntityByKey doesn't actually have to perform a fetch when you tell it to search the local cache first. But in this case, if you don't have metadata then the localQuery fails. I'll try to get this fixed in the next release, probably out later this week.

Related

Return data from OData Action using OData Client

I am testing the OData Action using ODataActionsSample, which is downloaded from http://aspnet.codeplex.com/sourcecontrol/latest#Samples/WebApi/OData/v4/ODataActionsSample/ODataActionsSample/, as a server and calling the "CheckOut" action which is,
[HttpPost]
public IHttpActionResult CheckOut(int key)
{
var movie = _db.Movies.FirstOrDefault(m => m.ID == key);
if (movie == null)
{
return BadRequest(ModelState);
}
if (!TryCheckoutMovie(movie))
{
return BadRequest("The movie is already checked out.");
}
return Ok(movie);
}
The action returns the movie with updated "DueDate" proprety in the sample program which is calling the action from javascript as below:
// Invoke "checkout" or "return" action. Both actions take no parameter data.
function invokeAction(url) {
ajaxRequest("post", url)
.done(function (updated) {
updateMovie(updated);
})
.fail(function (jqXHR, textStatus, errorThrown) {
//parent.errorMessage(errorThrown);
parent.errorMessage(url);
});
}
self.update(data);
// Update the model with new data from the server.
function updateMovie(data) {
var dueDate = data.DueDate ? new Date(data.DueDate) : null;
self.dueDate(dueDate);
if (data["#ODataActionsSample.Models.CheckOut"]) {
self.checkoutUrl(data["#ODataActionsSample.Models.CheckOut"].target);
}
else {
self.checkoutUrl(null);
}
if (data["#ODataActionsSample.Models.Return"]) {
self.returnMovieUrl(data["#ODataActionsSample.Models.Return"].target);
}
else {
self.returnMovieUrl(null);
}
}
However, the call from OData Client returns the movie without the DueDate updated. The client code is as below:
string serviceUri = "http://localhost:44221/OData/";
var container = new Container(new Uri(serviceUri));
var movieQuery = from movie in container.Movies select movie;
DataServiceCollection<ODataActionsClient.Movie> trackedMovies = new DataServiceCollection<ODataActionsClient.Movie>(movieQuery, TrackingMode.AutoChangeTracking, "Movies",null,null);
var myMovie = trackedMovies[0];
try
{
var checkouttedMovie = myMovie.CheckOut().GetValue();
}
catch (Exception ex)
{
Console.WriteLine(ex.InnerException.ToString());
}
What is wrong with my code in the client side ?
The default value for merge option is AppendOnly : No current values are modified.
OverwriteChanges : All current values are overwritten with current store values, regardless of whether they have been changed.
PreserveChanges: Current values that have been changed are not modified, but any unchanged values are updated with the current store values. No changes are lost in this merge.
So you have to decide which option you want to achieve, in this case I think OverwriteChanges is good enough.
FYI : https://msdn.microsoft.com/en-us/library/system.data.objects.mergeoption(v=vs.110).aspx

Breeze js cherry pick saves issue

I have a bulk insert screen which allows the user to insert products line by line.. Each product has it's own Units of measurement.
Here is my save changes Code:
save = function (product) {
var entitiesToSave = product.units().slice();
entitiesToSave.push(product);
var so = new breeze.SaveOptions({ allowConcurrentSaves: true })
return manager.saveChanges([entitiesToSave],so)
.then(saveSucceeded)
.fail(saveFailed);
}
Once I try to save; I get this message:
The 'entities' parameter is optional or it must be an array where each element must be an entity
Modifying the code to:
save = function (product) {
var so = new breeze.SaveOptions({ allowConcurrentSaves: true })
return manager.saveChanges([product,product.units()[0]],so)
.then(saveSucceeded)
.fail(saveFailed);
}
Works fine for one product unit.. However, I needed to save a specific product with all of it's units
in one shot..
Any help is appreciated.
For those who might have similar issue; I got it fixed by modifying the code to the following:
save = function (product) {
var entitiesToSave = new Array(product);
product.Units().forEach(function (Unit) {
entitiesToSave.push(Unit);
});
var so = new breeze.SaveOptions({ allowConcurrentSaves: true })
return manager.saveChanges(entitiesToSave,so)
.then(saveSucceeded)
.fail(saveFailed);
}
Regards to all.

Refreshing lookups in SPA with Breeze.js when value has changed

I have an application that records a transaction and the user can pick the category from a drop down.
Categories are loaded up at application startup as they are "mostly" static / rarely going to change.
So, in my datacontext.js I do the usual and prime my data;
var primeData = function () {
var promise = Q.all([
getLookups(),
getBankAccountPartials(null, true)])
.then(applyValidators);
return promise.then(success);
function success() {
datacontext.lookups = {
categories: getLocal('Categories', 'name', true),
transactiontypes: getLocal('TransactionTypes', 'name', true),
payees: getLocal('Payees', 'name', true)
};
log('Primed data', datacontext.lookups);
}
function applyValidators() {
model.applyBankAccountValidators(manager.metadataStore);
}
};
function getLookups() {
return EntityQuery.from('Lookups')
.using(manager).execute()
.then(processLookups)
.fail(queryFailed);
}
Now, occasionally in an Admin screen the user can edit and add a category.
In the categoryadd.js viewmodel my save code looks something like this (extract shown);
save = function () {
isSaving(true);
datacontext.saveChanges()
.then(goToEditView).fin(complete);
function goToEditView(result) {
router.replaceLocation('#/categorydetail/' + category().id());
}
function complete() {
isSaving(false);
}
},
How do I refresh just the Categories lookup data? Or, am I just doing this wrong and should perhaps NOT have categories as a lookup?
Thanks.
Breeze.js synchronises automatically and knows to search out the Category and update it in its lookup list.
I checked this by calling datacontext.lookups from the browser console after the save had been performed and inspecting the objects it showed me the category name had been refreshed.

Retrieving a record with breeze

I develop an application with asp.net mvc + breeze.
So far, I retrieve a specific record (based on id) like this:
var getTransportById = function (transportId, transportObservable) {
return manager.fetchEntityByKey('Transport', transportId, true)
.then(fetchSucceeded)
.fail(queryFailed);
}
function fetchSucceeded(data) {
var s = data.entity;
return ...
}
Now I need to retrieve the same record but need to 'expand' the property named sender which links to another entity (table). I did not find a way to 'expand' one property through fetchEntityByKey so I used a query like this:
var getTransportById = function (transportId, transportObservable) {
var query = EntityQuery.from('Transports')
.where('id', 'eq', transportId)
.expand('Sender')
.orderBy(orderBy.transport);
return manager.executeQuery(query)
.then(fetchSucceeded)
.fail(queryFailed);
}
function fetchSucceeded(data) {
var s = data.results[0];
return ...
}
My question: is it the good way to proceed? Is there another way of doing?
Thanks.
You can create a query from an EntityKey and then expand whichever properties you want. Something like this:
var entityKey = new EntityKey("Transport", transportId);
// expand whichever nav props you want here.
var query = EntityQuery.fromEntityKey(entityKey).expand("Sender").orderBy(...);
return entityManager.executeQuery(query).then( {
...
});

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