Paging in LINQ+MVC with a randomly ordered collection - asp.net-mvc

I have a bunch of records that are sorted RANDOMLY, like so:
var entries = DataContext.Entries.OrderBy(e => Random());
The Random function returns a randomly-generated GUID, thereby ordering the records in a random manner. Now my problem is paging. In MVC, I have a List action for the Entry controller that lists the entries:
public class EntryController : Controller
{
public ActionResult List(int page)
{
int pageSize = 10;
var entries = DataContext.Entries.OrderBy(e => Random()).Skip((page - 1) * pageSize).Take(pageSize);
ViewData["entries"] = entries;
return View();
}
}
My problem here is that whenever I go from one page to another page, the entries are REARRANGED ANEW. So when I go to page 1 (step 1), then go to page 2 (step 2), then back to page 1 (step 3), the entries that were shown in the step 1 are different from those shown in step 3. I absolutely need to have the records arranged randomly the first time around, but not in the subsequent look ups.
Any ideas on how best to address this problem?

Because each time you invoke the action the randomness occurs all over again to the whole records before paging, what you need to do is:
Order the records randomly as you want it.
Cache the result.
Fetch the cache every time the action is invoked while applying paging.
public ActionResult List(int page)
{
var dataSource = CacheContext["RandomRecords"];
if(dataSource == null){
dataSource = DataContext.Entries.OrderBy(e => Random());
}
int pageSize = 10;
var entries = dataSource.Skip((page - 1) * pageSize).Take(pageSize);
ViewData["entries"] = entries;
return View();
}

Add a column named sortOrder, being an int.
Do not forget to add a non-unique index on it or sorting on it will be suboptimal.
Put a number there, increased from record to record, not in the same order than your id or an order that would sort the records alphabetically.
Then
dataSource = DataContext.Entries.OrderBy(e => e.sortOrder);
Guids will be slower to sort on, but you can alternatively use a guid column for the task. Every now and then (once per night), you can loop through all records to put new values in the sortOrder column so people will not recognize the order.

You can add following SQL code to get products, you only need to generate some random number and store it into the session or cache and get product again..
DECLARE #NumberOfProductsInDatabase INT
DECLARE #RandomNumber VARCHAR(10)
SET #NumberOfProductsInDatabase = (SELECT COUNT(*) FROM Product)
SET #RandomNumber = '45'
SELECT CAST(100000 * SQRT(EXP(COS(p.SellerId + #RandomNumber))) AS INT) % #NumberOfProductsInDatabase AS RandomProductNr,
p.Id,p.Name,pv.Price,pv.SalePrice
FROM Product p,ProductVariant pv
WHERE pv.ProductId = p.Id
ORDER BY RandomProductNr

Related

Entity Framework get real-time value from database

I have a nopcommerce website. I find a problem. Please see below code
// My account / Order details page
[HttpsRequirement(SslRequirement.Yes)]
public virtual IActionResult Details(int orderId)
{
var order = _orderService.GetOrderById(orderId);
if (order == null || order.Deleted || _workContext.CurrentCustomer.Id != order.CustomerId)
return Challenge();
var orderTotal = order.OrderTotal;
order = _orderService.GetOrderById(orderId);
var orderTotal2 = order.OrderTotal;
var model = _orderModelFactory.PrepareOrderDetailsModel(order);
return View(model);
}
When I put a breakpoint on
var orderTotal = order.OrderTotal;
I get the value (100) of orderTotal from table Order. Then I changed the value of orderTotal from 100 to 200 in the database, and continue to execute the code.
order = _orderService.GetOrderById(orderId);
var orderTotal2 = order.OrderTotal;
This code should get the new value (200) of orderTotal from the table Order, however, it still returns a value of 100 for orderTotal2.
I want to get the refreshed data in function in the controller. Could you help me solve this problem?
Because the GetOrderById Method retrieves the cached order in the memory using 'ToCachedGetById', you can use GetOrderByGuid method instead to get the order directly from DB and skip the caching order

Filtering list using linq and mvc

Below is the code in question. I receive Object reference not set to an instance of an object. on the where clause inside the Linq query. However, this only happens after it goes through and builds my viewpage.
Meaning: If I step through using debugger, I can watch it pull the correct order I am filtering for, go to the correct ViewPage, fill in the model/table with the correct filtered item, and THEN it comes back to my Controller and shows me the error.
public ActionResult OrderIndex(string searchBy, string search)
{
var orders = repositoryOrder.GetOpenOrderList();
if (Request.QueryString["FilterOrderNumber"] != null)
{
var ordersFiltered = from n in orders
where n.OrderNumber.ToUpper().Contains(Request.QueryString["FilterOrderNumber"].ToUpper().ToString())
select n;
return View(ordersFiltered);
}
return View(orders);
}
its always better to manipulate your strings and other things outside the linq query ,
please refer : http://msdn.microsoft.com/en-us/library/bb738550.aspx
from the readability point of view also its not good ,
public ActionResult OrderIndex(string searchBy, string search)
{
var orders = repositoryOrder.GetOpenOrderList();
var orderNumber = Request.QueryString["FilterOrderNumber"];
if (!string.IsNullOrEmpty(orderNumber))
{
orderNumber = orderNumber.ToUpper();
var ordersFiltered = from n in orders
where n.OrderNumber.ToUpper().Contains(orderNumber)
select n;
return View(ordersFiltered);
}
return View(orders);
}
Your query is not being executed in your Action method because you don't have a ToList (or equivalent) added to your query. When your code returns, your query will be enumerated somewhere in your view and that's the point where the error occurs.
Try adding ToList to your query like this to force query execution in your action method:
var ordersFiltered = (from n in orders
where n.OrderNumber.ToUpper().Contains(Request.QueryString["FilterOrderNumber"].ToUpper().ToString())
select n).ToList();
What's going wrong is that a part of your where clause is null. This could be your query string parameter. Try moving the Request.QueryString part out of your query and into a temporary variable. If that's not the case make sure that your orders have an OrderNumber.
You both were right. Just separately.
This fixed my problem
var ordersFiltered = (from n in orders
where !string.IsNullOrEmpty(n.OrderNumber) && n.OrderNumber.ToUpper().Contains(Request.QueryString["FilterOrderNumber"].ToUpper().ToString())
select n);

Inefficient MVC ViewModel Making Multiple Calls to the Database?

I clearly don't know what I'm doing. This MVC stuff is really blowing my mind in trying to keep with the pattern. I've been following the MVC tutorials as well as mega-googling and this is the corner I've painted myself into.
I have multiple similar pieces of data I'm trying to get to a view. I'm able to get my code to work, but to me it just looks like it's going to be highly inefficient as we start pulling large recordsets from the db due to multiple calls to the db. So, I have a OrderSummary class, inside the class is this:
public IEnumerable<Order> GetOrders()
{
var orders = (from s in db.Orders
where s.UserId == uId
select s);
return orders.ToList();
}
Then this:
public decimal GetGrossProfitTotal()
{
var orders = (from s in db.Orders
where s.UserId == uId
select s);
decimal? grossprofittotal = orders.Sum(s => s.Profit);
return grossprofittotal ?? decimal.Zero;
}
So, if we take that last chunk of code and copy it for totalcommission and netprofittotal that's basically how I have things layed out. I would guess four calls to the db?
Then in the controller:
var ordersummary = new OrdersSummary();
var viewModel = new OrderSummary
{
Orders = ordersummary.GetOrders(),
GrossProfitTotal = ordersummary.GetGrossProfitTotal(),
CommissionTotal = ordersummary.GetCommissionTotal(),
NetProfitTotal = ordersummary.GetNetProfitTotal(),
};
return View(viewModel);
This gets me all the data I need in the view so I can work with it. To me, it just seems unnecessarily redundant and I'm guessing inefficient? If you throw in that I'm also doing sort and search parms, it's a lot of duplicate linq code as well. It seems like I should be able to do something to consolidate the data like this:
var orders = (from s in db.Orders
where s.UserId == uId
select s).ToList();
decimal grossprofittotal = orders.Sum(s => s.Profit);
decimal commissiontotal = orders.Sum(s => s.Commission);
decimal netprofittotal = orders.Sum(s => s.Profit + s.Commission);
and then wrap those four pieces of data (orders list, and three decimal values) up nicely in an array (or whatever) and send them to the controller/view. In the view I need to be able to loop through the orders list. Am I way off here? Or, what is standard procedure here with MVC? Thanks.
Yes, fetching the same data four times is indeed inefficient, and completely unneccesary. You can very well fetch it only once and then do the other operations on the data that you have.
You can keep the GetOrders method as it is if you like, but that's all the data that you need to fetch. If you fetch the data in the controller or in the model constructor is mostly a matter of taste. Personally I tend to put more logic in the model than the controller.
As long as you use ToList to make sure that you actually fetch the data (or any other method that realises the result as a collection), you can calculate the sums from what you have in memory. (Without it, you would still be doing four queries to the database.)
Instead of summing up the profit and commision from all items to get the net profit total, you can just calculate it from the other sums:
decimal netprofittotal = grossprofittotal + netprofittotal;
LinqToEntities tranlates all query into SQL. If you don't want to make more than one transaction, you can fetch the result into a variable by .ToList(),querying this object make the calculation by linqToObject in the memory.
Backward: It fetchs all orders from database first.
var ordersInMemory = orders.ToList();
decimal grossprofittotal = ordersInMemory.Sum(s => s.Profit);
decimal commissiontotal = ordersInMemory.Sum(s => s.Commission);
decimal netprofittotal = grossprofittotal + commissiontotal ;

JQGrid Loading lots of data

SITUATION
I am using Trirand JQGrid for MVC[server side] in my proj.
I've got more than 5 hundred thousand records in a single table.
I load the data by calling this piece of code. this is what gives 500000 records collection.
IEnumerable<myIndexViewModel> myviewmodel= _allincidents.Select(x => new myIndexViewModel
{
IncidentRequestStatus = x.RequestStatus,
RequestByUserName = x.RequestByUserName,
Subject = x.Subject
});
gridModel.JqGrid.DataBind(myviewmodel.AsQueryable());
JQgrid handles the json based ajax requests very nicely for every next page i click.
PROBLEM
I dont want to load 5 hundred thousand records all together on the page load event as it kills jqgrid.
If i write a stored procedure in the DB for requesting a specific page to be displayed then its gonna load only that page in the myviewmodel collection.
How do i get pages on the fly from the DB when the next page is clicked. is this even possible in jqgrid?
SITUATION 2
Based on the answers from VIJAY and MARK the approach they have shown is absolutely correct but over here the JQGRID for MVC sets up the DATAURL property for making the method call. In this case its the IncidentGridRequest.
How do i send in the page number when the grid next page or previous page is clicked?
incidentModel.IncidentGrid.DataUrl = Url.Action("IncidentGridRequest")
public JsonResult IncidentGridRequest()
{
}
Your controller action that will provide your grid with results can accept some extra information from jqGrid.
public ActionResult GetGridData(string sidx, string sord, int page, int rows, bool _search, string filters)
The main parts you are interested in is the page, rows (sidx is for column sorting, sord for the sorting order, _search if there was a search done on the grid, and if so filters contains the search information)
When you generate your results you should be able to then
IEnumerable<myIndexViewModel> myviewmodel = allincidents.Select(x => new myIndexViewModel
{
IncidentRequestStatus = x.RequestStatus,
RequestByUserName = x.RequestByUserName,
Subject = x.Subject
}).Skip((page - 1) * rows).Take(rows)
PS. I'm not sure if you using IEnumberable will be moving a large amount of data from your DB but you might want to use IQueryable when you generate this subset of data for the jqGrid.
Edit: To deal with your paging issues, You should be calculating the number of total records in your query and passing that value to the grid, Ex
int totalRecords = myviewmodel.Count();
and then later you would pass that to your grid as a jSon value. Ex
var jsonData = new
{
total = (totalRecords + rows - 1) / rows,
page = page,
records = totalRecords,
userdata = new {SearchResultsFound = searchResultsFound},
rows = (
......
Yes, for example if you are accepting the page number you want to turn to in a variable named page and the have the size of page in a variable pageSize then:
IEnumerable<myIndexViewModel> myviewmodel = allincidents.Select(x => new myIndexViewModel
{
IncidentRequestStatus = x.RequestStatus,
RequestByUserName = x.RequestByUserName,
Subject = x.Subject
}).Skip((page-1)*pageSize).Take(pageSize));
will give you the records of size pageSize to you.
The Trirand jqGrid for ASP.NET MVC is using IQueryable interface inside the JqGrid.DataBind() method to implement pagin, sorting and filtering.
So the key here is to use datasource, which handle these types of operations at the database level (by crafting SQL queries to the database in such a way that only the data required is fetched). All major ORMs have this support, this includes: LINQ-2-SQL, Entity Framework, NHbiernate, LLBLGen.
You just need to use one of this technologies, and past the required context directly to JqGrid.DataBind() method (without extracting the data manually like you do it in your sample).
An easier approach by using PagedList library (from Nuget). There is a useful blog by Joseph Schrag
public JsonResult Users(int PageNo, int Rows)
{
var UserList = db.Users.Select(t => new
{
t.UserId,
t.Username,
t.Firstname,
t.Lastname,
t.Designation,
t.Country,
t.Email
}).OrderBy(t => t.UserId);
var pagedUserList = UserList.ToPagedList(PageNo, Rows);
var results = new
{
total = pagedUserList.PageCount, //number of pages
page = pagedUserList.PageNumber, //current page
records = UserList.Count(), //total items
rows = pagedUserList
};
return new JsonResult() { Data = results, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}

get count from Iqueryable<T> in linq-to-sql?

The following code doesn't seem to get the correct count.....
var materials = consRepository.FindAllMaterials().AsQueryable();
int count = materials.Count();
Is it the way to do it.... Here is my repository which fetches records...
public IQueryable<MaterialsObj> FindAllMaterials()
{
var materials = from m in db.Materials
join Mt in db.MeasurementTypes on m.MeasurementTypeId equals Mt.Id
where m.Is_Deleted == 0
select new MaterialsObj()
{
Id = Convert.ToInt64(m.Mat_id),
Mat_Name = m.Mat_Name,
Mes_Name = Mt.Name,
};
return materials;
}
Edit:
when i use this,
var materials = consRepository.FindAllMaterials().AsQueryable();
return View("Materials", materials);
I get 18 rows in my table... So y cant i get the count as 18 instead it gives 12
Ans:
Breakpoint doesn't seem produce me the result but response.Write(count) did...
This should get the correct count:
int count = consRepository.FindAllMaterials().Count();
How are you iterating through the model in your view?
Is it possible that you are displaying duplicate entries?
Is it possible that you're not joining on the correct columns? The only reason I ask is that your id columns aren't consistently named in each class. For Materials you are using Mat_id, but in MeasurementTypes you are using simply, Id. It makes me wonder if you're trying to join a natural key value against an artificial primary key instead of the corresponding natural foreign key.

Resources