Breeze Query: Network shows data is returned but javascript results contains an empty model - breeze

I am trying to retrieve data from the server using breeze js. When the code in the controller snippet below runs, it hits the server and returns the correct data to the client (verified in Chrome Network tools). However, if you analyze the data.results in the promise handler, the model in the results appears like a new version of the entity with all default values for the fields (zeroes, null, etc.). No errors appear in the console.
This only happens if you go directly to this page. If you go to the homepage of the site then navigate to this page everything works correctly. It appears to be some sort of race condition when loading the site but I'm not exactly sure where to look.
Any ideas?
//From tnDataContext
function getFromAllTasks(criteria, orderBy, take, skip) {
var predicates = getPredicatesFromTaskCriteria(criteria);
if (!orderBy)
orderBy = 'taskStatus.taskInboxSortOrder, taskPriority.priorityLevel, endDueDate DESC';
var predicate = breeze.Predicate.and(predicates);
var query = breeze.EntityQuery.from('Tasks')
.where(predicate)
.expand('parentTask, taskNotes')
.orderBy(orderBy)
.inlineCount()
.take(take)
.skip(skip);
$rootScope.appProcesses++;
return em.executeQuery(query).finally(function () {
$rootScope.appProcesses--;
});
}
//From Angular Page Controller
return tnDataContext.getFromAllTasks(criteria, sortOrder, take, skip)
.then(function (data) {
if (data.results.length > 0) {
tasks = data.results;
$scope.task = tnDataContext.getEntity('Task', id);
$scope.childTasks = tasks.filter(function (t) {
return t.taskId != id;
});
determineCanUseActions($scope.task.taskType.taskTypeEntityTypes[0]);
}
});

Related

Add Pagination MVC and Azure table storage

Iam trying to apply Pagination to my MVC application. Iam using Azure table storage
Here is what I have tried:-
public List<T> GetPagination(string partitionKey, int start, int take)
{
List<T> entities = new List<T>();
TableQuery<T> query = new TableQuery<T>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey.ToLower()));
entities = Table.ExecuteQuery(query).Skip(start).Take(take).ToList();
return entities;
}
Controller:-
public ActionResult Index()
{
key= System.Web.HttpContext.Current.Request[Constants.Key];
if (String.IsNullOrEmpty(key))
return RedirectToAction("NoContext", "Error");
var items= _StorageHelper.GetPagination(key,0,3);
ItemCollection itemCollection = new ItemCollection();
itemCollection .Items= Mapper.Map<List<ItemChart>, List<ItemModel>>(items);
itemCollection .Items.ForEach(g => g.Object = g.Object.Replace(key, ""));
return View(itemCollection);
}
This currently gives me the first 3 entries from my data. Now how can I show and implement the "Previous" and "Next" to show the rest of the entries on next page? How do I implement the rest of the controller and HTML page?
Any help is appreciated.
When it comes to pagination, there are a few things to consider:
Not all LINQ operators (and in turn OData query options) are supported for Table Service. For example Skip is not supported. For a list of supported operators, please see this link: https://msdn.microsoft.com/en-us/library/azure/dd135725.aspx.
The way pagination works with Table Service is that when you query your table to fetch some data, maximum number of entities that can be returned by table service is 1000. There's no guarantee that always 1000 entities will be returned. It may be less than 1000 or even 0 depending on how you're querying. However if there are more results available, Table Service returns something called a Continuation Token. You must use this token to fetch next set of results from table service. Please see this link for more information on query timeout and pagination: https://msdn.microsoft.com/en-us/library/azure/dd135718.aspx.
Taking these two factors into consideration, you can't really implement a paging solution where a user can directly jump to a particular page (for example, user is sitting on page 1 and then the user can't go to page 4). At the most you can implement next page, previous page and first page kind of functionality.
To implement next page kind of functionality, store the continuation token returned by table service and use that in your query.
To implement previous page kind of functionality, you must store all the continuation tokens returned in an array or something and keep track of which page a user is on currently (that would be the current page index). When a user wants to go to previous page, you just get the continuation token for the previous index (i.e. current page index - 1) and use that in your query.
To implement first page kind of functionality, just issue your query without continuation token.
Do take a look at ExecuteQuerySegmented method in Storage Client Library if you want to implement pagination.
UPDATE
Please see the sample code below. For the sake of simplicity, I have only kept first page and next page functionality:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Queue;
using Microsoft.WindowsAzure.Storage.Table;
namespace TablePaginationSample
{
class Program
{
static string accountName = "";
static string accountKey = "";
static string tableName = "";
static int maxEntitiesToFetch = 10;
static TableContinuationToken token = null;
static void Main(string[] args)
{
var cloudStorageAccount = new CloudStorageAccount(new StorageCredentials(accountName, accountKey), true);
var cloudTableClient = cloudStorageAccount.CreateCloudTableClient();
var table = cloudTableClient.GetTableReference(tableName);
Console.WriteLine("Press \"N\" to go to next page\nPress \"F\" to go first page\nPress any other key to exit program");
var query = new TableQuery().Take(maxEntitiesToFetch);
var continueLoop = true;
do
{
Console.WriteLine("Fetching entities. Please wait....");
Console.WriteLine("-------------------------------------------------------------");
var queryResult = table.ExecuteQuerySegmented(query, token);
token = queryResult.ContinuationToken;
var entities = queryResult.Results;
foreach (var entity in entities)
{
Console.WriteLine(string.Format("PartitionKey = {0}; RowKey = {1}", entity.PartitionKey, entity.RowKey));
}
Console.WriteLine("-------------------------------------------------------------");
if (token == null)//No more token available. We've reached end of table
{
Console.WriteLine("All entities have been fetched. The program will now terminate.");
break;
}
else
{
Console.WriteLine("More entities available. Press \"N\" to go to next page or Press \"F\" to go first page or Press any other key to exit program.");
Console.WriteLine("-------------------------------------------------------------");
var key = Console.ReadKey();
switch(key.KeyChar)
{
case 'N':
case 'n':
continue;
case 'F':
case 'f':
token = null;
continue;
default:
continueLoop = false;
break;
}
}
} while (continueLoop);
Console.WriteLine("Press any key to terminate the application.");
Console.ReadLine();
}
}
}

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

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.

Breeze querying local cache with EF and Web API

Problem
I have a view with 6 drop downs. Each of which is being populated by a Web API call. I want
to use breeze to run the query locally once it has populated from the remote server
The code runs fine when the data call is against the server. The issue is when trying to query the local cache. I never get any results returned. Is my approach flawed or am I doing something wrong ?
SERVER SIDE
View model
class genericDropDown()
{
public int value{get;set;}
public string option{get;set;}
}
The WebAPI [A single sample method]
[HttpGet]
// GET api/<controller>
public object GetSomeVals()
{
return _context.getClinician();
}
The Repository [A single sample method]
public IEnumerable<genericDropDown> getDropDownVal()
{
return context.somemodel(a=>new{a.id,a.firstname,a.lastname}).ToList().
Select(x => new GenericDropDown
{ value = x.id, option = x.firstname+ " " + x.lastname});}
}
CLIENT SIDE
Datacontext.js
var _manager = new breeze.EntityManager("EndPoint");
//Being called from my view model
var getDropDownBindings = function(KO1, KO2) {
//First add the entity to the local metadatastore then populate the entity
$.when(
addDD('clinicianDropDown', webAPIMethod),
getData(KO1, webAPIMethod, null, 'clinicianDropDown'),
addDD('docTypeDropDown', webAPIMethod);
getData(KO2, webAPIMethod, null, 'docTypeDropDown'),
).then(querySucceeded).fail(queryFailed);
function querySucceeded(data) {
logger.log('Got drop down vals', "", 'dataContext', true);
}
};
//Add the entity to local store. First param is typename and second is
resource name (Web API method)
var addDD = function(shortName,resName) {
_manager.metadataStore.addEntityType({
shortName: shortName,
namespace: "Namespace",
autoGeneratedKeyType: breeze.AutoGeneratedKeyType.Identity,
defaultResourceName:resName,
dataProperties: {
value: { dataType: DataType.Int32,
isNullable: false, isPartOfKey: true },
option: { dataType: DataType.String, isNullable: false }
}
});
return _manager.metadataStore.registerEntityTypeCtor(shortName, null, null);
};
//Get the data
var getData = function(observableArray, dataEndPoint, parameters, mapto) {
if (observableArray != null)
observableArray([]);
//TO DO: Incorporate logic for server or local call depending on
// whether this method is accessed for the first time
var query = breeze.EntityQuery.from(dataEndPoint);
if (mapto != null && mapto != "")
query = query.toType(mapto);
if (parameters != null)
query = query.withParameters(parameters);
//This approach doesnt work on local querying as Jquery complains
//there is no 'then' method. Not sure how to implement promises
//when querying locally
/* return _manager.executeQuery(query).then(querySucceeded).fail(queryFailed);
function querySucceeded(data) {
if (observableArray != null)
observableArray(data.results);
}
*/
//The array length from this query is always 0
var data = _manager.executeQueryLocally(query);
observableArray(data.results);
return;
};
//Generic error handler
function queryFailed(error) {
logger.log(error.message, null, 'dataContext', true);
}
viewmodel.js
//In Durandal's activate method populate the observable arrays
dataContext.getDropDownBindings (KO1,KO2);
Viewmodel.html
<select class="dropdown" data-bind="options: KO1, optionsText: 'option', value: 'value', optionsCaption: 'Clinicians'"></select>
<select class="dropdown" data-bind="options: KO2 optionsText: 'option', value: 'value', optionsCaption: 'Document Types'"></select>
You can only execute local queries against types that are described by metadata.
Without more information I can't be sure, but my guess is that your GetSomeVals method is not returning 'entities' but just loose data. In other words, the types of objects returned from the GetSomeVals method must be entities (or contain entities within a projection) in order for breeze to be able to perform a local query. This is because Breeze knows how to cache and query entities but has no ideas how to cache 'arbitrary' query results.
Note that you can return an anonymous type containing entities of different types from the server, (in order to populate mostly static small datasets), but the individual items must be 'entities'. In this case, Breeze will take apart the anon result and pick out any entities to include in the EntityManager cache.
Per you question of how to perform an local query with promises, use the FetchStrategy.FromLocalCache with the using method.
i.e. this query
var results = em.executeQueryLocally(query)
can also be expressed as:
query = query.using(FetchStrategy.FromLocalCache);
return em.executeQuery(query).then(data) {
var results = data.results;
}
The local query is still executed synchonously but is made to look async.

Is it possible that MVC is caching my data some place?

I have the following controller action:
public ActionResult Details(string pk)
{
IEnumerable<ContentDetail> model = null;
try
{
model = _content.Details(pk);
if (model.Count() > 0)
{
return PartialView(getView(pk) + "Details", model);
}
}
catch (Exception e)
{
log(e);
}
return Content("No records found");
}
I call this with the following routine:
$.ajax({
cache: false,
url: "/Administration/" + table + "s/Details",
data: { pk: partitionKey },
dataType: 'html',
success: function (responseText) {
$('#detailData').html(responseText);
$(".updatable")
.change(function (e) {
var type = $(this).attr('id').split('_')[0];
updateField(table, $(this), type);
});
$('.dialogLink')
.click(function () {
dialogClick(this);
return false;
});
},
error: function (ajaxContext) {
ajaxOnFailure(ajaxContext)
}
});
What I notice is that sometimes when I put a break point on the first line of the controller action then it does not seem to stop at the breakpoint. Is it possible the results are being cached by MVC and how could I stop this from happening while I debug?
Check for any variation of OutputCache being set on the controller itself or, if present, a base controller all your controllers may inherit from.
I've also noticed IE likes to cache things and found the only way to alleviate this is doing to the developer window and clearing the cache (or ctrl+shift+del)
Try adding cache:false to your .ajax call and see if that has any bearing (jQuery will append a timestamp variable making each call unique). Nevermind, just noticed you have it included. Must be getting late here--sure sign it's bed time when I miss things like this.
Load up fiddler and see if the request is even coming back from the browser. Ideally you'll want I write out no cache headers through one of the many methods mentioned here
Disable browser cache for entire ASP.NET website

Resources