Kendo grid server side grouping - nhibernate

I am using Asp net 5, NHibernate 3.3 and Kendo UI MVC wrapper for grid to render the table of client orders. There are lots of orders in database already and the number is constantly growing. So I decided to use server side paging to avoid fetching all orders from database. As far as I know you can't do paging manually and delegate filtering, sorting and grouping to ToDataSourceResult method. It's either all or nothing. Therefore I made an attempt to implement so called 'custom binding'. No problem until I get to grouping. I need to group first, then sort inside of a group, then extract data for specific page and all that without loading all data to memory. My code is something like this (I put it all in one piece to simplify reading):
var orderList = CurrentSession.QueryOver<Order>();
// Filtering. Filter is a search string obtained from DataSourceRequest
var disjunction = new Disjunction();
disjunction.Add(Restrictions.On<Order>(e => e.Number).IsLike("%" + filter + "%"));
disjunction.Add(Restrictions.On<Order>(e => e.Customer).IsLike("%" + filter + "%"));
orderList = orderList.Where(disjunction);
// Sorting. sortColumn is also from DataSourceRequest
switch (sortColumn)
{
case "Number":
orderList = orderList.OrderBy(x => x.Number).Desc;
break;
case "GeneralInfo.LastChangeDate":
orderList = orderList.OrderBy(x => x.LastChangeDate).Desc;
break;
default:
orderList = orderList.OrderBy(x => x.Number).Desc;
break;
}
}
// Total is required for kendo grid when you do paging manually
var total = orderList.RowCount();
var orders = orderList
.Fetch(x => x.OrderGoods).Eager
.Fetch(x => x.OrderComments).Eager
.Fetch(x => x.Documents).Eager
.Fetch(x => x.Partner).Eager
.Skip((request.Page - 1)*request.PageSize).Take(request.PageSize).List();
I will be glad to have any advice on how to add grouping here.

I worked for literally months to figure out server-side grouping using the Kendo DataSource with a Kendo Grid. Paging, sorting and filtering were fairly easy. But for whatever reason, Telerik did not offer sufficient support documentation for such a critical LOB process as grouping. I’m glad you posted this question so I’d have an opportunity to share my code.
The Solution
Basically, the solution comes down to knowing 2 key parts, and they can be viewed in the following sample project: https://www.dropbox.com/s/ygtk8rwl1hwjvth/KendoServerGrouping.zip?dl=0
There is a single web application project in the Visual Studio (2012 | 2013) solution you’re downloading, which contains a reference to the Kendo.Mvc library. You can download the latest UI for ASP.NET binaries from Telerik's Control Panel installation program. The binaries will be located in the following Windows directory after install: C:\Program Files (x86)\Telerik\UI for ASP.NET MVC [Telerik Release Version]\wrappers\aspnetmvc\Binaries\ [Your Version of MVC]\Kendo.Mvc.dll.
Note: My solution uses Telerik’s MVC transport mechanism, which provides full-fledged server-side paging, filtering, sorting and, most notably, grouping. However, I use pure JavaScript to configure the Kendo DataSource and not the MVC wrappers. Still, I've recently found a link in Telerik's documentation that shows the MVC wrapper declaration in Razor/ASPX.
The Server Magic
Basically, the first part of the magic is the following server side code, residing in the sample WebApi controller in the KendoServerGrouping.Web\Controllers directory:
[System.Web.Http.AcceptVerbs("GET", "ASPNETMVC-AJAX")]
public Kendo.Mvc.UI.DataSourceResult GetAllAccounts([System.Web.Http.ModelBinding.ModelBinder(typeof(WebApiDataSourceRequestModelBinder))] Kendo.Mvc.UI.DataSourceRequest request)
{
var kendoRequest = new Kendo.Mvc.UI.DataSourceRequest
{
Page = request.Page,
PageSize = request.PageSize,
Filters = request.Filters,
Sorts = request.Sorts,
Groups = request.Groups,
Aggregates = request.Aggregates
};
// Set this to your ORM or other data source
IQueryable accounts = dbContext.Accounts;
/*
The data source can even be a MongoDB collection using the
.AsQueryable() extension and the MongoDB C# driver
var accounts = collection.FindAllAs<Account>().AsQueryable();
*/
var data = accounts.ToDataSourceResult(kendoRequest);
var result = new DataSourceResult()
{
AggregateResults = data.AggregateResults,
Data = data.Data,
Errors = data.Errors,
Total = data.Total
};
return result;
}
This is all you’ll need for any of the four server-side actions that the grid will handle auto-magically when the user interacts with it. Pay special attention to the AcceptVerbs attribute above the method; it must include the “ASPNETMVC-AJAX” attribute for the DataSourceRequest input parameter to work properly. ToDataSourceResult() is an extension provided by recent versions of the Kendo.Mvc.dll library, which I pointed to earlier.
The code above will (to my knowledge) work with any IQueryable data source, such as those from ORMs (I've tested Entity Framework and Telerik Data Access/Open Access). I've also been able to group a MongoDB collection using the MongoDB C# driver. However, this is meant as a proof-of-concept, and it has not been tested for performance.
For the purposes of this example, there is static data source in the WebAPI controller to fake an IQueryable collection. Naturally, you can delete the static data from lines 45-57 when you've swapped in your own data source.
The Client Magic
The Kendo DataSource automatically passes in a specialized DataSourceRequest object from the grid containing all the parameters for server-side paging, filtering, sorting and grouping, provided you wrap your DataSource schema inside the following JavaScript:
schema: $.extend(true, {}, kendo.data.schemas["aspnetmvc-ajax"], {
});
This was perhaps the single most elusive line of code I’ve ever tracked down. It took about a dozen exchanges with Telerik over several months to get them to cough it up. And even then, it was by pure chance that it was revealed. Why such a critical nuance was absent in their documentation is beyond me.
Carefully review each of the Kendo DataSource configuration settings toward the bottom half of index.html file. Most importantly, pay attention to what isn’t there, such as the batch and mvcTransport options. Including the latter option somehow negates the above “aspnetmvc-ajax” schema attribute.
In the DataSource's parmaterMap function, make note that when – and only when - performing a read operation, the following line must be present:
return mvcTransport.parameterMap(options, operation);
You will also want to be sure to include this in your HTML, before the DataSource executes:
<script src="//cdn.kendostatic.com/[Version]/js/kendo.aspnetmvc.min.js"></script>
The End Result
Run the KendoServerGrouping.Web project (index.html) and, if all goes well, a grid will be populated with 5 records containing AccountId, AccountName, AccountTypeCode and CreatedOn fields. If you set the number of visible grid rows to 2 and group by AccountTypeCode or CreatedOn, you’ll see that the grouping traverses the paging, which I believe is the end result you are looking for.
I hope the sample project works and is a good fit for your situation. Please let me know if you have any questions, and I’ll do my best to help.
P.S. This is my first post to SO, so please go easy on me if something isn’t up to SO standards. Good luck!

I would like to add onto #aaron-jessen answer with this jewel I found on Telerik's forums:
$("#grid").kendoGrid({
dataSource: {
type: "aspnetmvc-ajax", // If missing may cause NULL values in ApiController
}
})

Related

Apply a date field value to expiration date in inventory details subrecord

I'm a newbie in NetSuite Scripting and was recently asked to apply the value from a date field (custbody_expiration_date) on item receipt transaction body to the expiration date field in the inventory details of all items when the item receipt is created.
Since there is no way to create a workflow on inventory details, I've managed to work out below codes however I'm keeping getting all sorts of different error message. Below is one of them after I click on save on item receipt.
Notice (SuiteScript)
org.mozilla.javascript.EcmaError: TypeError: Cannot find function getCurrentLineItemValue in object standard record. (/SuiteScripts/ARROW/Expiration_date_apply_to_all (1).js#27)
I am very confused on the difference between dynamic and standard mode, which functions should be used in which mode? Also, I am a bit hesitated on whether user event script is the correct way to go?
/**
*#NApiVersion 2.0
*#NScriptType UserEventScript
*#NModuleScope Public
*/
define(['N/record','N/search'], function (record, search) {
function beforeSubmit(context) {
var IRrecord = context.newRecord;
var numberOfLineItems = IRrecord.getLineCount({
sublistId: 'item'
});
var expirationdate = IRrecord.getValue({
fieldId: 'custbody_expiration_date'
});
for (var i=1; i<=numberOfLineItems; i++){
IRrecord.setSublistValue({
sublistId: 'item',
fieldId: 'item',
line: i,
value: true
});
//First get Lot Number and Quantity
var lotNumber = IRrecord.getCurrentLineItemValue('item', 'receiptinventorynumber');
var quantity = IRrecord.getCurrentLineItemValue('item', 'quantity');
var inventoryDetail = IRrecord.createCurrentLineItemSubrecord('item','inventorydetail');
inventoryDetail.selectNewLineItem('inventoryassignment');
inventoryDetail.setCurrentLineItemValue('inventoryassignment', 'issueinventorynumber', lotNumber);
inventoryDetail.setCurrentLineItemValue('inventoryassignment', 'quantity', quantity);
inventoryDetail.setCurrentLineItemValue('inventoryassignment', 'expirationdate', expirationdate);
inventoryDetail.commitLineItem('inventoryassignment');
inventoryDetail.commit();
IRrecord.commitLineItem('item');
}
nlapiSubmitRecord(IRrecord);
}
return {
beforeSubmit: beforeSubmit
}
});
Dynamic records are the kind you see client-side (as a rule) - modify a field value and some other field becomes refreshed and updated in real time. Forms sometimes need to have their fields filled in a particular order to prevent form completion errors triggering or field sourcing to work. For example, when entering a sales order, selecting the customer then defaults the sales tax when items are added to the order. Errors may be thrown at any point before the record save because a field is triggering dynamic sourcing (updating other fields), based on what has been entered.
Standard mode is - less dynamic. You populate the fields of the record in any order you choose, and when the save is performed, you choose whether sourcing (updating other fields from the data available) is triggered. Any errors in data entry are reported when the save is performed. I think it also has a lower client-side load as there are fewer AJAX queries being triggered.
Both are available in client-side and server-side javascript, but some record types cannot be updated client-side and must be done server-side using workflow actions, User Event, Restlet, Suitelets, or scheduled scripts. To the best of my knowledge, inventory subrecords on fulfillments, receipts and the like are one such type.
The way lines are updated changes between dynamic and standard mode. In dynamic mode, lines are selected, updated then committed and the methods used would be :
selectLine
setCurrentLineItemValue
commitLine (only do this if actually changing the line)
For standard mode, the way of changing lines is only to use setSublistValue and include the line number in the parameters.
Workflow action scripts will load the record in dynamic mode, but the load method can be investigated using the isDynamic() method on the record.
The other thing is, in SuiteScript 2, sublist lines are indexed from 0, not from 1 as your script is using. What's confusing is, in Suitescript 1, indexing was from 1. The code is using a mix of v1 & v2. nlapiSubmitRecord is v1, IRrecord.save is v2.
And for more information, see SuiteAnswer 79715 which explains how to set a value on the inventory detail on an item receipt. The example reloads the record in standard mode and updates the inventoryStatus field. SuiteAnswer 45372 explains the Record object and the difference between standard and dynamic modes. Take a look at SuiteAnswer 67605 which explains the basics of SuiteScript v2. SuiteAnswers is an amazing resource and the search is surprisingly good. I can also recommend Eric T Grubaugh's site (#erictgrubaugh) which has some great videos including comparisons between v1 & v2.

How to retrieve the Id of a newly Posted entity using ASP.Net Core?

I Post a new Waste entity using the following code:
var result = await httpClient.PostAsJsonAsync(wasteApiRoute, waste);
The Api Controller (using the code created by VS) seems to try to make life easy for me by sending back the new Id of the Waste entity using:
return CreatedAtAction("GetWaste", new { id = waste.Id }, waste);
So the resultvariable wil contain this data. Indeed, I find it in its Headers.Location property as an url.
But how do I nicely extract the Id property from the result without resorting to regular expressions and the like? Surely the creators of ASP.Net Core will have included a nifty call for that?
Well, the best I can come up with is:
var result = await httpClient.PostAsJsonAsync(wasteApiRoute, waste);
var newWaste = await result.Content.ReadFromJsonAsync<Waste>();
Where waste has an Id of zero, newWaste has its Id set.

How to search multiple api services

I am developing a search engine with angular 2.
Therefore I use APIs from multiple platforms.
It works if I call the search function from every api service manually.
But is it possible to do the same foreach api service?
Every api service has the same function:
search (query: string): Observable<Array<SearchResult>> { ... }
In the UI I want to separate the results by tabs.
Therefore every api service has a title:
public title: string = "the title";
For storing the search results locally I have a class that is extended by every api service. This class has helper functions etc.
Depending on the behaviour you need you can use merge, concat or forkJoin to merge multiple streams into one.
The code would look pretty much the same.
For example using merge in order to merge 2 streams into one.
If you have a list of apis you need to call for the search. Your code would look like this.
let apis: string[] = [];
let observables = apis.map(api => search(api)); // get an array of observables
let merged = observables.reduce((previous, current) => previous.merge(current), new EmptyObservable()); // merge all obserbable in the list into one.
merged.subscribe(res => doSomething(res));
This article might be helpful.

Understanding Orchard Joins and Data Relations

In Orchard, how is a module developer able to learn how "joins" work, particularly when joining to core parts and records? One of the better helps I've seen was in Orchard documentation, but none of those examples show how to form relations with existing or core parts. As an example of something I'm looking for, here is a snippet of module service code taken from a working example:
_contentManager
.Query<TaxonomyPart>()
.Join<RoutePartRecord>()
.Where(r => r.Title == name)
.List()
In this case, a custom TaxonomyPart is joining with a core RoutePartRecord. I've investigated the code, and I can't see how that a TaxononmyPart is "joinable" to a RoutePartRecord. Likewise, from working code, here is another snippet driver code which relates a custom TagsPart with a core CommonPartRecord:
List<string> tags = new List<string> { "hello", "there" };
IContentQuery<TagsPart, TagsPartRecord> query = _cms.Query<TagsPart, TagsPartRecord>();
query.Where(tpr => tpr.Tags.Any(t => tags.Contains(t.TagRecord.TagName)));
IEnumerable<TagsPart> parts =
query.Join<CommonPartRecord>()
.Where(cpr => cpr.Id != currentItemId)
.OrderByDescending(cpr => cpr.PublishedUtc)
.Slice(part.MaxItems);
I thought I could learn from either of the prior examples of how to form my own query. I did this:
List<string> tags = new List<string> { "hello", "there" };
IContentQuery<TagsPart, TagsPartRecord> query = _cms.Query<TagsPart, TagsPartRecord>();
query.Where(tpr => tpr.Tags.Any(t => tags.Contains(t.TagRecord.TagName)));
var stuff =
query.Join<ContainerPartRecord>()
.Where(ctrPartRecord => ctrPartRecord.ContentItemRecord.ContentType.Name == "Primary")
.List();
The intent of my code is to limit the content items found to only those of a particular container (or blog). When the code ran, it threw an exception on my join query saying {"could not resolve property: ContentType of: Orchard.Core.Containers.Models.ContainerPartRecord"}. This leads to a variety of questions:
Why in the driver's Display() method of the second example is the CommonPartRecord populated, but not the ContainerPartRecord? In general how would I know what part records are populated, and when?
In the working code snippets, how exactly is the join working since no join key/condition is specified (and no implicit join keys are apparent)? For example, I checked the data migration file and models classes, and found no inherent relation between a TagsPart and a CommonPartRecord. Thus, besides looking at that sample code, how would anyone have known in the first place that such a join was legal or possible?
Is the join I tried with TagsPart and ContainerPartRecord legal in any context? Which?
Is the query syntax of these examples primarily a reflection of Orchard, of NHibernate, or LINQ to NHibernate? If it is primarily a reflection of NHibernate, then which NHibernate book or article is recommended reading so that I can dig deeper into Orchard?
It seems there is a hole in the documentation regarding these kinds of thoughts and questions, which makes it hard to write a module. Whatever answers can be found for this topic, I'd be glad to compile into an article or community Orchard documentation.
The join is only there to enable the where that follows it. It doesn't mean that the part being joined will be actually brought down from the DB. That will happen no matter what with the latest 1.x source, and will happen lazily with 1.3.
You don't need a condition as you can only join parts this way. The join condition is implicit: parts are joined by the item id.
Yes. What is not legal is that the condition in the where is using data that is not available from the joined part records.
Those examples are all Orchard Content Manager queries, so they are fairly constrained, but also fairly easy to build as long as you don't step outside of their boundaries because so much can be assumed and will happen implicitly. If you need more control, you could use the new HQL capabilities that were added in the latest 1.x drops.
As for holes in the documentation, well, but of course. The documentation that we have today is only covering a very small part of the platform. Your best reference today is the source code. Any contribution you could make to this is highly appreciated by us and by the rest of the community. Let me know if you need help with this.

How do I wrap an EF 4.1 DbContext in a repository?

All,
I have a requirement to hide my EF implementation behind a Repository. My simple question: Is there a way to execute a 'find' across both a DbSet AND the DbSet.Local without having to deal with them both.
For example - I have standard repository implementation with Add/Update/Remove/FindById. I break the generic pattern by adding a FindByName method (for demo purposes only :). This gives me the following code:
Client App:
ProductCategoryRepository categoryRepository = new ProductCategoryRepository();
categoryRepository.Add(new ProductCategory { Name = "N" });
var category1 = categoryRepository.FindByName("N");
Implementation
public ProductCategory FindByName(string s)
{
// Assume name is unique for demo
return _legoContext.Categories.Where(c => c.Name == s).SingleOrDefault();
}
In this example, category1 is null.
However, if I implement the FindByName method as:
public ProductCategory FindByName(string s)
{
var t = _legoContext.Categories.Local.Where(c => c.Name == s).SingleOrDefault();
if (t == null)
{
t = _legoContext.Categories.Where(c => c.Name == s).SingleOrDefault();
}
return t;
}
In this case, I get what I expect when querying against both a new entry and one that is only in the database. But this presents a few issues that I am confused over:
1) I would assume (as a user of the repository) that cat2 below is not found. But it is found, and the great part is that cat2.Name is "Goober".
ProductCategoryRepository categoryRepository = new ProductCategoryRepository();
var cat = categoryRepository.FindByName("Technic");
cat.Name = "Goober";
var cat2 = categoryRepository.FindByName("Technic");
2) I would like to return a generic IQueryable from my repository.
It just seems like a lot of work to wrap the calls to the DbSet in a repository. Typically, this means that I've screwed something up. I'd appreciate any insight.
With older versions of EF you had very complicated situations that could arise quite fast due to the required references. In this version I would recomend not exposing IQueryable but ICollections or ILists. This will contain EF in your repository and create a good seperation.
Edit: furthermore, by sending back ICollection IEnumerable or IList you are restraining and controlling the queries being sent to the database. This will also allow you to fine tune and maintain the system with greater ease. By exposing IQueriable, you are exposing yourself to side affects which occur when people add more to the query, .Take() or .Where ... .SelectMany, EF will see these additions and will generate sql to reflect these uncontrolled queries. Not confining the queries can result in queries getting executed from the UI and is more complicated tests and maintenance issues in the long run.
since the point of the repository pattern is to be able to swap them out at will. the details of DbSets should be completly hidden.
I think that you're on a good path. The only thing I probaly ask my self is :
Is the context long lived? if not then do not worry about querying Local. An object that has been Inserted / Deleted should only be accessible once it has been comitted.
if this is a long lived context and you need access to deleted and inserted objects then querying the Local is a good idea, but as you've pointed out, you may run into difficulties at some point.