I have this method for retrieving a result from my context and caching it using MemoryCache.
public IEnumerable<CustomerRole> GetCustomerRoles()
{
string key = String.Format(CC_CACHE_CUSTOMER_ROLE_ALL, "all");
return _cacheManager.Get(key, () =>
{
return from r in _customerRoleRepository.Table select r;
}
);
}
I then use this in my view like
#foreach (CustomerRole role in Model)
{
}
The problem I have is that because the actual result isn't executed until the data is accessed (in my view), it's not actually caching the result.
How do I force this query to run via my caching function rather than waiting until the data is used?
I've not included what _cacheManager.Get() does as I know it's caching whatever I send to it properly but if you think that is the problem, let me know and I will post the relative code.
Note: I have tried doing it this way hoping it would force the query to run but still no luck
public IEnumerable<CustomerRole> GetCustomerRoles()
{
string key = String.Format(CC_CACHE_CUSTOMER_ROLE_ALL, "all");
return _cacheManager.Get(key, () =>
{
var roles = from r in _customerRoleRepository.Table select r;
return roles.Take(roles.Count());
}
);
}
You need to call a method like ToList() to force linq to get the data. Then just add that list to your cache.
Related
this function takes a ServicePoint object as argument, which has the following attributes:
adminId (String)
name (String)
serviceType (enum)
I want this function to create a new Table with name: "name+adminId". This is achieved.
Also I want this function to create a new Table (if it is not there already) by the name ServicePoints.
ServicePoints stores the relationship between user (with objectId = adminId) and the new Table.
To achieve this, I set "serviceTable" attribute with value as the new Table created, acting as a pointer.
When I run the code first time, I achieve the required tables. But, when I run the function second time, it doesn't add the new row/record to ServicePoints table.
I don't know why.
UPDATE I found that set ParseObject operation is the culprit. But, to my surprize, it executes successfully for the very first time. But fails every next time. This is really absurd behaviour from parse_server_sdk_flutter.
Future<bool> createServicePoint(ServicePoint servicePoint) async {
String newServicePointName = servicePoint.name + servicePoint.adminId;
var newServiceTable = ParseObject(newServicePointName);
var response = await newServiceTable.save();
if (response.success) {
print('Now adding new row to ServicePoints table');
var servicePointsTable = ParseObject('ServicePoints')
..set<String>("serviceName", servicePoint.name)
..set<String>("adminId", servicePoint.adminId)
..set<String>("serviceType", _typeToLabel[servicePoint.serviceType])
..set<ParseObject>("serviceTable", newServiceTable);
var recentResponse = await servicePointsTable.save();
return recentResponse.success;
} else {
return false;
}
}
If anyone runs into this problem, you need to check the result after saving the ParseObject. If there is error like "Can't save into non-existing class/table", then just go to the dashboard and create the table first.
In my table controller, I have:
public IQueryable<MyTable> GetAllMyTable()
I would like to replace the above with my own:
[HttpGet, Route("tables/MyTable")]
public IEnumerable<MyTable> GetAllMyTable()
But I get this response when I call it:
HTTP/1.1 405 Method Not Allowed
Somehow the Web API routing does not reach my method.
Why I'm doing this: the original method produces an inefficient Entity Framework SQL query that takes 3 seconds per call on my local test environment. This is running the query captured from SQL Profiler directly in SQL Mgt Studio. An equivalent query takes less than a second to run. Terrible.
Worse, the inefficient EF queries consumes lots of Azure SQL DTUs, tempting you to up your Azure subscription level if you want a quick fix.
Azure Mobile Apps is wonderful, but the multiple layers of abstraction makes it hard to really see what's going on under the hood, and therefore harder to tune.
Any help would be much appreciated.
HTTP/1.1 405 Method Not Allowed
Per my understanding, the error is obvious. You could send the GET HTTP verb to your endpoint tables/MyTable for retrieving the data. You need to check your request against your mobile app backend via fiddler.
Azure Mobile Apps is wonderful, but the multiple layers of abstraction makes it hard to really see what's going on under the hood, and therefore harder to tune.
For the common table controller, it would look like this:
public IQueryable<Message> GetAllMessage()
{
return Query();
}
The Query() method under EntityDomainManager.cs would equal as follows:
IQueryable<TData> query = this.Context.Set<TData>();
if (!includeDeleted)
{
query = query.Where(item => !item.Deleted);
}
return query;
If it deals with the ODATA queries (e.g. $top, $skip, $filter, etc.), the Nested SQL statement would be generated. We could modify the action to clarify it as follows:
public IEnumerable<Message> GetAllMessage(ODataQueryOptions opt)
{
var message = context.Set<Message>();
var query2=opt.ApplyTo(message, new ODataQuerySettings());
return query2.Cast<Message>().ToList();
}
Here's my rather crude attempt at bypassing the Entity Framework/OData plumbing and using direct SQL. (Wouldn't it be great if Dapper is supported!) This one works well, and is faster than the nested SQL that EF produces. The handling of OData is hacky; I have not had time to investigate using OData to extract the values for UpdatedAt, skip, and top.
I'm only using this approach for one method that needs optimisation. This is the method that the Azure Mobile App client calls when doing a pull.
public IEnumerable<MyTable> GetAllMyTable()
{
var qryValues = HttpUtility.ParseQueryString(Request.RequestUri.Query);
var updatedAtFilter = qryValues["$filter"];
var skip = qryValues["$skip"];
var top = qryValues["$top"];
if (updatedAtFilter != null)
{
var r = new Regex(#"^.+datetimeoffset'(?<time>.+)'.+$", RegexOptions.None);
var m = r.Match(updatedAtFilter);
if (m.Success)
{
var updatedAt = m.Groups["time"].Value.Replace("T", " ");
var sqlString = #"SELECT T0.*
FROM MyTable T0
WHERE T0.UpdatedAt >= #UpdatedAt
ORDER BY UpdatedAt, Id
OFFSET #Skip ROWS
FETCH NEXT #Top ROWS ONLY";
var updatedAtParam = new SqlParameter("UpdatedAt", SqlDbType.DateTimeOffset);
updatedAtParam.Value = updatedAt;
var skipParam = new SqlParameter("Skip", SqlDbType.Int);
skipParam.Value = int.Parse(skip);
var topParam = new SqlParameter("Top", SqlDbType.Int);
topParam.Value = int.Parse(top);
var data = _context.Database.SqlQuery<MyTable>(sqlString, new object[] { updatedAtParam, skipParam, topParam }).AsEnumerable<MyTable>();
return data;
}
}
return null;
}
I have a model called Instance which works fine.
define([], function(){
return Backbone.Model.extend({
urlRoot:'/api/Instances',
parse:function(content){
return content.data;
}
});
});
My REST at here
http://localhost:3000/api/Instances/1
returns this
{"status":"success","data":{"id":1,"name":"bangladesh","write":null,"read":null,"createdAt":"2015-09-01T23:03:16.000Z","updatedAt":"2015-09-01T23:03:16.000Z","UserId":1}}
hence the parse function in my model. All good so far.
If I just call
http://localhost:3000/api/Instances
Then I get a block of all my records,
{"status":"success","count":212,"data":[
{"id":1,"name":"bangladesh","write":null,"read":null,"createdAt":"2015-09-01T23:03:16.000Z","updatedAt":"2015-09-01T23:03:16.000Z","UserId":1},
{"id":2,"name":"abqride","write":null,"read":null,"createdAt":"2015-09-01T23:03:58.000Z","updatedAt":"2015-09-01T23:03:58.000Z","UserId":1},
....
And my collection code is just this
define(['models/instance.js'], function(Model){
return Backbone.Collection.extend({
url:'/api/Instances',
model:Model,
parse:function(content){
return content.data;
}
})
});
The above code for the Collection will create 212 models. I've checked that with an initialise function in the model just to see if it was being called OK with the right data, and it is.
But in my view code when I go
this.collection.each(function(model) {
console.log("model id="+model.get("id")+" count="+count++);
out+=model.get("id")+"="+model.get("name")+"<br>";
});
there's nothing in these models, but there are 212 of them, I just get "undefined=null" 212 times.
I can see three options, two of which involve customizing the use of Backbone: 1. Calling collection.sync manually and then executing custom code afterward, 2. Passing a custom option in the initial collection.fetch() and looking for it in the Model parse() method.
These two options don't solve the problem at the source, however, since the concern is at the initial response level. In order to apply a response-level filter to your data, try overriding Backbone.ajax() before starting your application:
Backbone.ajax = function () {
var settings = arguments[1] || arguments[0]; // jQuery.ajax(url[, settings])
var success = settings.success;
settings.success = function (data, status, xhr) {
if (success) { success(data.data, status, xhr); }
};
return Backbone.$.ajax.apply(Backbone.$, arguments);
};
I want to be able to fetch data from an external Api for a specific request, but when that data is returned, also make it available in the cache, to represent the current state of the application.
This solution seems to work:
var Rx = require('rx');
var cached_todos = new Rx.ReplaySubject(1);
var api = {
refresh_and_get_todos: function() {
var fetch_todos = Rx.Observable.fromCallback($.get('example.com/todos'));
return fetch_todos()
.tap(todos => cached_todos.onNext(todos));
},
current_todos: function() {
return cached_todos;
}
};
But - apparently Subjects are bad practice in Rx, since they don't really follow functional reactive programming.
What is the right way to do this in a functional reactive programming way?
It is recommended not to use Subjects because there is a tendency to abuse them to inject side-effects as you have done. They are perfectly valid to use as ways of pushing values into a stream, however their scope should be tightly constrained to avoid bleeding state into other areas of code.
Here is the first refactoring, notice that you can create the source beforehand and then your api code is just wrapping it up in a neat little bow:
var api = (function() {
var fetch_todos = Rx.Observable.fromCallback($.get('example.com/todos'))
source = new Rx.Subject(),
cached_todos = source
.flatMapLatest(function() {
return fetch_todos();
})
.replay(null, 1)
.refCount();
return {
refresh: function() {
source.onNext(null);
},
current_todos: function() {
return cached_todos;
}
};
})();
The above is alright, it maintains your current interface and side-effects and state have been contained, but we can do better than that. We can create either an extension method or a static method that accepts an Observable. We can then simplify even further to something along the lines of:
//Executes the function and caches the last result every time source emits
Rx.Observable.withCache = function(fn, count) {
return this.flatMapLatest(function() {
return fn();
})
.replay(null, count || 1)
.refCount();
};
//Later we would use it like so:
var todos = Rx.Observable.fromEvent(/*Button click or whatever*/))
.withCache(
Rx.Observable.fromCallback($.get('example.com/todos')),
1 /*Cache size*/);
todos.subscribe(/*Update state*/);
I've got a mySql db with non-standard IDs and field names, so I was trying to use both jsonResultsAdapterProvider and setRestangularFields. Here's the code in my app.config file:
RestangularProvider.setBaseUrl(remoteServiceName);
RestangularProvider.setRestangularFields({id: 'personID'});
RestangularProvider.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
if (data.error) {
return data.error;
}
var extractedData = data.result;
return jsonResultsAdapterProvider.$get().camelizeKeys(extractedData);
});
RestangularProvider.addRequestInterceptor(function(elem, operation, what, url) {
return jsonResultsAdapterProvider.$get().decamelizeKeys(elem);
});
It's all good until I try to do a put/save. When I look at the request payload within the browser dev tools, it's: {"undefined":12842} (but the url is correct, so I know the id is set) If I don't use the ResultsAdapter and change the id field to Person_ID, payload looks good, so I know I'm making the right calls to Get and Save the Restangular objects. But for what it's worth, here's the code:
$scope.tests = Restangular.all('members').getList().$object;
vm.testEdit = function () {
$scope.test = Restangular.one('members', 12842).get().then(function(test) {
var copy = Restangular.copy(test);
copy.title = 'xxxx';
copy.put(); // payload was: undefined: 12842
});
}
// I also tried customPUT...
// copy.customPUT(copy, '', {}, {'Content-Type':'application/x-www-form-urlencoded'});
I tried "fixing" the id other ways too, too. like this:
Restangular.extendModel('members', function(model) {
model.id = model.personID;
return model;
});
but that messed up the urls, causing missing ids. And I tried getIdFromElem, but it only got called for my objects created with Restangular.one(), not with Restangular.all()
Restangular.configuration.getIdFromElem = function(elem) {
console.log('custom getIdFromElem called');
if (elem.route === 'members') { // this was never true
return elem[personID];
}
};
It seems like Restangular needs to substitute 'personID' most of the time, but maybe it needs 'Person_ID' at some point during the Save? Any ideas on what I could try to get the Save working?
I finally figured it out! The problem was in my config code and in the way I was decamelizing. Because of inconsistencies in my db field names (most use underscores, but some are already camelCase), I was storing the server's original elem names in an array within the jsonResultsAdapterProvider. But since I was calling jsonResultsAdapterProvider.$get().camelizeKeys(extractedData); within the interceptors, I was reinstantiating the array each time I made a new request. So, the undefined in the PUT request was coming from my decamelizeKeys() method.
My updated config code fixed the problem:
RestangularProvider.setBaseUrl(remoteServiceName);
RestangularProvider.setRestangularFields({id: 'personID'});
var jsonAdapter = jsonResultsAdapterProvider.$get();
RestangularProvider.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
if (data.error) {
return data.error;
}
var extractedData = data.result;
// return extractedData;
return jsonAdapter.camelizeKeys(extractedData);
});
RestangularProvider.addRequestInterceptor(function(elem, operation, what, url) {
return jsonAdapter.decamelizeKeys(elem);
});