I have MVC project with WCF service.
When I display a list of data, I do want to load everything from the database/service and do a client paging. But I do want a server-side paging. If I have 100 records and my page size is 10, then when a user clicks on page 1, it will only retrieve the first 10 records from the database and if a user clicks on Page 3, then it will only retrieve the corresponding ten records.
I am not using Angular or any other bootstrap.
Can someone guide me how to do it?
public ActionResult Index(int pageNo = 1)
{
..
..
..
MyViewModel[] myViewModelListArray = MyService.GetData();
//when I create this PageList, BLL.GetData have to retreive all the records to show more than a single page no.
//But if the BLL.GetData() was changed to retrieve a subset, then it only shows a single page no.
//what I wanted to do is, show the correct no of pages (if there are 50 records, and pageSize is 10, then show
//page 1,2,3,4,5 and only retrieve 10 records at a time.
PagedList<MyViewModel> pageList = new PagedList<<MyViewModel>(myViewModelListArray, pageNo, pageSizeListing);
..
..
..
return View(pageList);
}
The best approach is to use LINQ to Entities operators Skip & Take.
For example, to page
int items_per_page = 10;
MyViewModel[] myViewModelListArray = MyService.GetData().OrderBy(p => p.ID).Skip((pageNo - 1) * items_per_page).Take(items_per_page).ToArray();
NOTE: The data must be ordered, so the pages have some consistency (but I did by an arbitrary field ID). Also some databases required 'order by' to apply 'limit' or 'top' (which is how Take/Skip are implemented).
I put it that way, because I dont know how you are retrieving the data.
But instead retrieving the full list with GetData and then filtering out, better include the pagination in the query inside GetData (so you don't retrieve unnecessary data).
Add paramters page size and page number to your service method and make the result an object which returns TotalCount and a List Items (Items being the items on the current page). Then you can use those values to create the PagedList.
Inside your business logic code you will do two queries one for the count of items and one for the items on the page.
Also if you are starting the project now do yourself a favor and remove the useless WCF service from your architecture.
Related
I have a web application in which I get data from my database and show in a datatable. I am facing an issue doing this as the data that I am fetching has too many rows(200 000). So when I query something like select * from table_name;
my application gets stuck.
Is there a way to handle this problem with JavaScript?
I tried pagination but I cannot figure how would i do that as datatable creates pagination for already rendered data?
Is there a way through which I can run my query through pagination at
the backend?
I have come across the same problem when working with mongodb and angularjs. I used server side paging. Since you have huge number of records, You can try using the same approach.
Assuming a case that you are displaying 25 records in one page.
Backend:
Get the total count of the records using COUNT query.
select * from table_name LIMIT 25 OFFSET
${req.query.pageNumber*25} to query limited records based on the page number;
Frontend:
Instead of using datatable, display the data in HTML table it self.
Define buttons for next page and previous page.
Define global variable in the controller/js file for pageNumber.
Increment pageNumber by 1 when next page button is clicked and
decrement that by 1 when prev button is pressed.
use result from COUNT query to put upper limit to pageNumber
variable.(if 200 records are there limit will be 200/25=8).
So basically select * from table_name LIMIT 25 OFFSET
${req.query.pageNumber*25} will limit the number of records to 25. when req.query.pageNumber=1, it will offset first 25records and sends next 25 records. similarly if req.query.pageNumber=2, it will offset first 2*25 records and sends 51-75 records.
There are two ways to handle.
First way - Handling paging in client side
Get all data from database and apply custom paging.
Second way - Handling paging in server side
Every time you want to call in database and get records according to pagesize.
You can use LIMIT and OFFSET constraints for pagination in MySQL. I understand that at a time 2 lacs data makes performance slower. But as you mention that you have to use JS for that. So make it clear that if you wants js as frontend then it is not going to help you. But as you mention that you have a web application, If that application is on Node(as server) then I can suggest you the way, which can help you a lot.
use 2 variables, named var_pageNo and var_limit. Now use the row query of mysql as
select * form <tbl_name> LIMIT var_limit OFFSET (var_pageNo * var_limit);
Do code according to this query. Replace the variable with your desire values. This will make your performance faster, and will fetch the data as per your specified limit.
hope this will helpful.
Problem summary:
C# (MVC), entity framework 5.0 and Oracle.
I have a couple of million rows in a view which joins two tables.
I need to populate dropdownlists with filter-posibilities.
The options in these dropdownlists should reflect the actual contents
of the view for that column, distinct.
I want to update the dropdownlists whenever you select something, so
that the new options reflect the filtered content, preventing you
from choosing something that would give 0 results.
Its slow.
Question: whats the right way of getting these dropdownlists populated?
Now for more detail.
-- Goal of the page --
The user is presented with some dropownlists that filter the data in a grid below. The grid represents a view (see "Database") where the results are filtered.
Each dropdownlist represents a filter for a column of the view. Once something is selected, the rest of the page updates. The other dropdownlists now contain the posible values for their corresponding columns that complies to the filter that was just applied in the first dropdownlist.
Once the user has selected a couple of filters, he/she presses the search button and the grid below the dropdownlists updates.
-- Database --
I have a view that selects almost all columns from two tables, nothing fancy there. Like this:
SELECT tbl1.blabla, tbl2.blabla etc etc
FROM table1 tbl1, table2 tbl2
WHERE bsl.bvz_id = bvz.id AND bsl.einddatum IS NULL;
There is a total of 22 columns. 13 VARCHARS (mostly small, 1 - 20, one of em has a size of 2000!), 6 DATES and 3 NUMBERS (one of them size 38 and one of them 15,2).
There are a couple of indexes on the tables, among which the relevant ID's for the WHERE clause.
Important thing to know: I cannot change the database. Maybe set an index here and there, but nothing major.
-- Entity Framework --
I created a Database first EDMX in my solution and also mapped the view. There are also classes for both tables, but I need data from both of them, so I don't know if I need them. The problem by selecting things from either table would be that you can't apply half of the filtering, but maybe there are smart way's I didn't think of yet.
-- View --
My view is strongly bound to a viewModel. In there I have a IEnumerable for each dropdownlist. The getter for these gets its data from a single IEnumerable called NameOfViewObjects. Like this:
public string SelectedColumn1{ get; set; }
private IEnumerable<SelectListItem> column1Options;
public IEnumerable<SelectListItem> Column1Options
{
get
{
if (column1Options == null)
{
column1Options= NameOfViewObjects.Select(item => item.Column1).Distinct()
.Select(item => new SelectListItem
{
Value = item,
Text = item,
Selected = item.Equals(SelectedColumn1, StringComparison.InvariantCultureIgnoreCase)
});
}
return column1Options;
}
}
The two solutions I've tried are:
- 1 -
Selecting all columns in a linq query I need for the dropdownlists (the 2000 varchar is not one of them and there are only 2 date columns), do a distinct on them and put the results into a Hashset. Then I set NameOfViewObjects to point towards this hashset. I have to wait for about 2 minutes for that to complete, but after that, populating the dropdownlists is almost instant (maybe a second for each of them).
model.Beslissingen = new HashSet<NameOfViewObject>(dbBes.NameOfViewObject
.DistinctBy(item => new
{
item.VarcharColumn1,
item.DateColumn1,
item.DateColumn2,
item.VarcharColumn2,
item.VarcharColumn3,
item.VarcharColumn4,
item.VarcharColumn5,
item.VarcharColumn6,
item.VarcharColumn7,
item.VarcharColumn8
}
)
);
The big problem here is that the object NameOfViewObject is probably quite large, and even though using distinct here, resulting in less than 100.000 results, it still uses over 500mb of memory for it. This is unacceptable, because there will be a lot of users using this screen (a lot would be... 10 max, 5 average simultaniously).
- 2 -
The other solution is to use the same linq query and point NameOfViewObjects towards the IQueryable it produces. This means that every time the view wants to bind a dropdownlist to a IEnumerable, it will fire a query that will find the distinct values for that column in a table with millions of rows where most likely the column it's getting the values from is not indexed. This takes around 1 minute for each dropdownlist (I have 10), so that takes ages.
Don't forget: I need to update the dropdownlists every time one of them has it's selection changed.
-- Question --
So I'm probably going at this the wrong way, or maybe one of these solutions should be combined with indexing all of the columns I use, maybe I should use another way to store the data in memory, so it's only a little, but there must be someone out there who has done this before and figured out something smart. Can you please tell me what would be the best way to handle a situation like this?
Acceptable performance:
having to wait for a while (2 minutes) while the page loads, but
everything is fast after that.
having to wait for a couple of seconds every time a dropdownlist
changes
the page does not use more than 500mb of memory
Of course you should have indexes on all columns and combinations in WHERE clauses. No index means table scan and O(N) query times. Those cannot scale under any circumstance.
You do not need millions of entries in a drop down. You need to be smarter about filtering the database down to manageable numbers of entries.
I'd take a page from Google. Their type ahead helps narrow down the entire Internet graph into groups of 25 or 50 per page, with the most likely at the top. Maybe you could manage that, too.
Perhaps a better answer is something like a search engine. If you were a Java developer you might try Lucene/SOLR and indexing. I don't know what the .NET equivalent is.
First point you need to check is your DB, make sure you have to right indexes and entity relations in place,
next if you want to dynamical build your filter options then you need to run the query with the existing filters to obtain what the next filter can be. there are several ways to do this,
firstly you can query the data and extract the values from the return, this has a huge load time and wastes time returning data you don't want (unless you are live updating the results with the filter and dont have paging, in which case you might aswell just get all the data and use linqToObjects to filter)
a second option is to have a parallel queries for each filter that returns the possible filters, so filter A = all possible values of A from data, filter b = all possible values of B when filtered by A in the data, C = all possible values of C when filtered by A & B in the data, etc. this is better than the first but not by much
another option is the use aggregates to speed things up, ie you have a parallel query as above but instead of returning the data you return how many records are returned, aggregate functions are always quicker so this will cut your load time dramatically but you are still repeatedly querying a huge dataset to it wont be exactly nippy.
you can tweak this further using exist to just return a 0 or 1.
in this case you would look at a table with all possible filters and then remove the ones with no values from the parallel query
the next option will be the fastest by a mile is to cache the filters in the DB, with a separate table
then you can query that and say from Cache, where filter = ABC select D, the problem with this maintaining the cache, which you would have to do in the DB as part of the save functions, trigggers etc.
Another solution that can be added in addition to the previous suggestions is to use the /*+ result_cache */ hint, if your version of Oracle supports it (Oracle version 11g or later). If the output of the query is small enough for a drop-down list, then when a user enters criteria that matches the same criteria another user used, the results are returned in a few milliseconds instead of a few seconds or minutes. Result cache is wonderful for queries that return a small set of rows out of millions.
select /*+ result_cache */ item_desc from some_table where item_id ...
The result cache is automatically flushed when any insert/updates/deletes occur on the database tables.
I've done something 'kind of' similar in the past - if you can add a table to the database then I'd explore introducing a 'scratchpad' type table where results are temporarily stored as the user refines their search. Since multiple users could be working simultaneously the table would have to have an additional column for identifying the user.
I'd think you'd see some performance benefit since all processing is kept server-side and your app would simply be pulling data from this table. Since you're adding this table you would also have total control over it.
Essentially I'd imagine the program flow would go something like:
User selects some filters and clicks 'Search'.
Server populates scratchpad table with results from that search.
App populates results grid from scratchpad table.
User further refines search and clicks 'Search'.
Server removes/adds rows to scratchpad table as necessary.
App populates results grid from scratchpad table.
And so on.
Rather than having all the users results in one 'scratchpad' table you could possibly explore having temporary 'scratchpad' tables per user.
Currently i have implemented jqgrid which fetches data from DB and returning the JSON data to JQGRID.
JQGrid call
rowNum: 10,
rowList: [5, 10],
url: "/Home/GetDataFromEntity"
Data returned from C#
return Json(result, JsonRequestBehavior.AllowGet);
What am trying is if the page has 10 records only that 10 records i want to bring from DB and if they click to next page i want to get the next 10 data because if the data is huge i dont want to bring all data to memmory which i think will be a performance Hit.
How can i implement this ?
Thanks
This is fairly straightforward. Basically all you need to do is implement paging, where you are only asking the DB for the page of data that you are going to display. You will see jqGrid will provide this information to your controller so you can use then when retrieving data.
The controller will take this data in via something like (I don't know your back end tech stack so here is C# code) this:
public ActionResult GridDataFetch(string sidx, string sord, int page, int rows, bool _search, string filters)
{
....
Then when you go to retreive your data, you can ask the database for the page of data that your user wants without having to retreive the whole dataset. This can be more complicated then it seems but for the basics it is as simple as somthing like (again C# code)
var pagedQuery = dataset.OrderBy(sidx + " " + sord).Skip((page - 1) * rows).Take(rows);
You can see above we order the data in the manner that the user specified and jqGrid passed along with sidx & sord and then we skip all the records before the page we are interested in via the skip, and then we take the rows we are interested in. This again was a C# method for grabbing a page of data but the basics should be there for any setup. As a side note if you do any filtering via the grid, or other logic you would have filtered your dataset prior to this call.
You would then pass this paged query same as you would usually do in JSON.
You need to use paging for the same. You can use .Take and .Skip methods at server side
After you get your resultset you can do as following
var smallResultSet = fullResultSet.Skip(request.PageIndex * request.RecordsCount).Take(request.RecordsCount).ToList();
here I've made assumption that you are fetching your resultset in fullResultSet variable than filter it and store it in smallResultSet. request parameter will be passed when you bind grid to controller action.
After that iterate through smallResultSet and create your JSONResult.
you have to implement server side pagination to achieve this.
I have implemented in java like this: (you are using c#)
int limit = Integer.parseInt(request.getParameter("rows")); // get how many rows we want to have into the grid
String sidx = request.getParameter("sidx"); // get index row - i.e. user click to sort
String sord = request.getParameter("sord"); // get the direction
int start = (limit* page) - limit;
String rows = request.getParameter("rows");
String query = "select * from ( select a.*, ROWNUM rnum from ( select * from CRM_PROT_STAGES where PROTOCOL_ID = '"+param +"' ) a where ROWNUM <= "+ limit +")where rnum >="+start;
use above paarameters, and then put the condition in query as above
I have a project where I should use external WCF service that has method that looks as following:
Items catalogItems = externalClient.getCatalogItems(auth, idCatalog, 1, 100);
After I call getCatalogItems service method, I should transform the returned array of items to raw SOAP message in this manner:
Message response = Message.CreateMessage(MessageVersion.Default, ReplyAction_GetCatalogItems, catalogItems);
The last 2 parameters in getCatalogItems service method designates the size of chunk of data that should be obtained in each call. For example, if we have 1050 records all of them should be obtained 10 times in chunks of 100 and 1 time in chunk of 50.
I understand I should read the data until they are available. I have 2 questions:
How do I know where I should continue to read? For example, if I've read first portion of 100 records, how do I know where's the current position of reader?
How do I know when I reach the end?
One approach would be to make it the responsibility of the client to remember the state (ie the page number where the client currently is).
So you can change your method call to include a page number and items per page parameters:
Items catalogItems = externalClient.getCatalogItems(auth, idCatalog, pageNumber, itemsPerPage);
The service can then essentially select a set of items based on the pageNumber and itemsPerPage values and it need not hold the state. (Note: this can be easily translated to a select query if you are using a database as repository for the items)
You can possibly alter the return value to include the total number of items as well:
Example:
CatalogResponse respone = externalClient.getCatalogItems(auth, idCatalog, pageNumber, itemsPerPage);
public class CatalogResponse
{
private _totalItems;
private _items;
}
This also provides the flexibility for the client to determine the chunk of items to receive in each call and to the end user to select a page size.
I am loading data into my DataGrid via the ItemsSource property. I have a DataPager as well for pagination. The Grid is populated by calling a WCF service which returns a List.
public void webService_GetProductsCompleted(object sender, GetServiceReference.GetProductsCompletedEventArgs e)
{
PagedCollectionView pagingCollection = new PagedCollectionView(e.Result);
pgrProductGrids.Source = pagingCollection;
grdProductGrid.ItemsSource = pagingCollection;
}
Now there is a new requiremement that I want to load data with server side pagination. I'm a newbie learning Silverlight and for me the concept of server side paging is completely new too. So I came here to know what's required for server side pagination. Any good examples, tutorials,step-by-step guidelines that can give me a direction? I have to complete this task in a limited time. Please guide seniors
I typically use 1 of these 2 options:
1: If your data is sorted/paged by a field that has a unique value in it, let the database do the heavy lifting by utilizing the TOP feature and an ORDER BY. This way the smallest amount of data is returned from the server to the page. In the following example, MyTable has a field "NAME" that is unique and is how I want the data sorted. I am getting 10 records per page.
SELECT TOP 10 * FROM MyTable Where Name> [The last name in the previous 10 results] ORDER BY Name
You could use this for multiple fields if you wanted to use a windowing function like ROW_NUMBER() (I don't know what database you are using, this assumes SQL Server)
2: If this is not the case, then it gets ugly. You need to get all the data and get all the records between [The last page * Number per page] and the number of records per page in some iterative code. (Ugly, slow, lots of memory, not possible on large data sets.)