How to handle IEnumerable with IGrouping in the view? - linq

I am trying to send to the view an IEnumerable and to show every element in it but I have a problem when I send it. It says that:
{System.Linq.Enumerable.WhereSelectEnumerableIterator<System.Linq.IGrouping<string,MvcApplication4.Models.USER>,<>f__AnonymousType8<string,int>>}
and I dont know how to handle it.
This is my view :
#model IEnumerable<dynamic>
I get dynamic because i go to this view from couple of functions how send different types.
<table border="1" style="text-align:center">
<tr>
<td> </td>
<td> user name </td>
<td>age </td>
<td>comments </td>
</tr>
#foreach (var t in (Model))
{
<tr>
<td>#(count++))</td>
<td> #Console.WriteLine(t)</td>
<td> </td>
<td> </td>
</tr>
}
</table>
I get the IEnumerable from this linq :
var allU = (from m in comList
join c in users on m.user equals c.user into cm1
from cm2 in cm1.DefaultIfEmpty()
group cm2 by m.user into grouped
select new { name = grouped.Key, count = grouped.Count() }).AsEnumerable();
So my question is really : how can I get the elements of it in the view?

You can't reference the type in your view because it is an anonymous type. Instead, define a type to store the results of your query:
public class Result
{
public string Name { get; set; }
public int Count { get; set; }
}
Then change your query to project into that type:
var allU = (from m in comList
join c in users on m.user equals c.user into cm1
from cm2 in cm1.DefaultIfEmpty()
group cm2 by m.user into grouped
select new Result { Name = grouped.Key, Count = grouped.Count() }).AsEnumerable();
Then you'll be able to bind to the type IEnumerable<Result> in your view.

Related

LINQ query performance issue when fetching data from db in MVC Razor

Problem Statement:
I'm trying to bind Multi-table data from db to view using Linq query which is taking more time.I'm having around 10000 records in db.Someone suggested to use the IQueryable instead of IEnumerable,but does it affect my current code(In both View and Controller)?? Or without using that can I accomplish this??
What I should do in-order to increase the performance of loading the result??
What I'm doing wrong??
Please suggest me some better way of doing this...
Controller:
public ActionResult Index()
{
var result = (from pr in db.Prod.AsEnumerable()
join s in db.Shift.AsEnumerable() on pr.Shift equals s.ShiftID
join m in db.Module.AsEnumerable() on pr.Module equals m.ModuleID
select new GlobalModel()
{
prodModelIndex = pr,
prodModel = prodModel,
shiftModel = s,
moduleModel = m,
ddlShift = objTransactionGeneralController.GetAllShift(),
ddlModule = objTransactionGeneralController.GetAllModule()
}).ToList();
return PartialView(result);
}
public TransGeneralModel GetAllModule()
{
objTransGeneralModel.ddlModule = (from m in db.Module.AsEnumerable()
select new SelectListItem
{
Value = m.ModuleID.ToString(),
Text = m.ModuleName,
}).ToList();
return objTransGeneralModel;
}
public TransGeneralModel GetAllShift()
{
objTransGeneralModel.ddlShift = (from s in db.Shift.AsEnumerable()
select new SelectListItem
{
Value = s.ShiftID.ToString(),
Text = s.ShiftName,
}).ToList();
return objTranGeneralModel;
}
View:
#model IEnumerable<SIA.Models.Trans.GlobalModel>
#using GridMvc.Html
#{
Layout = "~/Views/Shared/_Layout.cshtml";
ViewBag.Title = "Index";
}
<link rel="stylesheet" href="#Url.Content("~/Content/jquery.dataTables.min.css")">
<script src="#Url.Content("~/Scripts/jquery-2.1.1.min.js")"></script>
<h2>Details</h2>
<hr />
<div style="width: 1000px; padding-left: 70px">
#Html.Partial("Create")
<br />
</div>
<h5 class="pull-right">
<b class="fa fa-keyboard-o" style="color: blue"></b>
#Ajax.ActionLink("Edit", "ProdEdit", "Prod", new { }, new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "prod-details",
HttpMethod = "GET",
}, new { style = "color:blue" })
</h5>
<br />
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
if (Model.FirstOrDefault().prodModelIndex != null)
{
<div id="prod-details">
<table class="table table-striped" id="tblProdDetails">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().prodModelIndex.ProdID)
</th>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().prodModelIndex.Date)
</th>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().prodModelIndex.Module)
</th>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().productionModelIndex.Shift)
</th>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().prodModelIndex.Hour)
</th>
<th>
#Html.DisplayNameFor(model => model.FirstOrDefault().prodModelIndex.Output)
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr id="customer-row-#item.prodModelIndex.ProdID">
<td>
#Html.DisplayFor(modelItem => item.prodModelIndex.ProdID)
</td>
<td>
#Html.DisplayFor(modelItem => item.prodModelIndex.Date)
</td>
<td>
#Html.DisplayFor(modelItem => item.moduleModel.ModuleName)
</td>
<td>
#Html.DisplayFor(modelItem => item.shiftModel.ShiftName)
#Html.HiddenFor(modelItem => item.prodModelIndex.Shift)
</td>
<td>
#Html.DisplayFor(modelItem => item.prodModelIndex.Hour)
</td>
<td>
#Html.DisplayFor(modelItem => item.prodModelIndex.Output)
</td>
</tr>
}
</tbody>
</table>
</div>
}
}
<script>
$(document).ready(function () {
$('#tblProdDetails').dataTable({
"order": [[1, "desc"], [3, "asc"]]
});
});
</script>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("~/Scripts/jquery.dataTables.min.js")
<script type='text/javascript'>
$(function () {
$('.datepicker').datepicker({
format: "dd M yyyy",
}).on('changeDate', function (e) {
$(this).datepicker('hide');
});
})
</script>
}
First, when you call a method like ToList(), AsEnumerable() or FirstOrDefault(), it will execute the query on the database. In your case, would be nice to remove them to hit a single query with joins.
var result = (from pr in db.Prod
join s in db.Shift on pr.Shift equals s.ShiftID
join m in db.Module on pr.Module equals m.ModuleID
select new GlobalModel()
{
prodModelIndex = pr,
prodModel = prodModel,
shiftModel = s,
moduleModel = m
}).ToList();
If the tables are referenced by foreign key then no need to join. you can directly access the referred record like this
var result = (from pr in db.Prod
select new GlobalModel()
{
prodModelIndex = pr,
prodModel = prodModel,
shiftModel = pr.Shift,
moduleModel = pr.Module
}).ToList();
TL;DR
Filter your primary table (Prod) with a .Where or at least use .Take() to limit the number of rows to something sane to show on a screen
Drop the .AsEnumerable() - you are materializing whole tables into memory
Add foreign keys to your table, regenerate your DBML, and use navigation instead of explicit joins
Be careful of what you put into a Select projection - ddlShift = objTransactionGeneralController.GetAllShift() will be called for each row in the result set.
In Detail
By applying .AsEnumerable() to your collections like :
var result = (from pr in db.Prods.AsEnumerable()
join s in db.Shifts.AsEnumerable() on pr.ShiftID equals s.ShiftId
join m in db.Modules.AsEnumerable() on pr.ModuleID equals m.ModuleId
select new ...
Your current code results in 3 explicit queries to Sql Server, each of which will load the whole table into memory: (e.g. use Sql Profiler, or LinqPad, etc)
SELECT [t0].[ModuleId], ... other columns
FROM [dbo].[Module] AS [t0];
SELECT [t0].[ShiftId], ... other columns
FROM [dbo].[Shift] AS [t0];
SELECT [t0].[ProdID], [t0].[ShiftID], [t0].[ModuleID], ... other columns
FROM [dbo].[Prod] AS [t0];
Given that you have no WHERE predicate at all, this might not be all that much slower than joining in the database. However, in general, doing this isn't a good idea at all, since:
By applying .AsEnumerable(), you are taking away the ability for Linq2Sql to parse an IQueryable expression tree into native Sql. Generally, doing joining and filtering in the database will be quicker, and require less memory than doing this in memory. Assuming that Prod, Shift and Module are the Linq.Table<>s, the solution here is to simply remove .AsEnumerable() - this will allow Linq to use the IQueryable extension methods for joining, filtering, aggregating etc.
It is unusual to retrieve all rows in a table and show them all at once in a single screen, unless the table size guaranteed to have a small number of rows. Usually you will apply some kind of filter to a table.
As per Bhaarat's comment, if you have set up your foreign keys between the tables correctly (as it is implied by your sample code, it appears there are designed join keys), when you import your tables into Linq2Sql DBML, you will also get navigation between the entities, and thus will not need to join the tables explicitly.
Putting this into your Select projection - ddlShift = objTransactionGeneralController.GetAllShift() will be called for each row in the result set. This looks expensive. Do this once, store the result in a local variable, and if needed, reference it in each of the projections. Or change your ViewModel so that it doesn't repeat the reference on every row, if it is needed once.
Lazy loading can be a performance problem (the 1 to N problem) - turn this off on DataContext via db.DeferredLoadingEnabled = false, and instead, explicitly specify the depth of graph to be eager loaded with appropriate LoadWith<> statements
Your code will now look like this:
using (var db = new DataClasses1DataContext())
{
// Switch off Lazy Loading in favour of eager loading
db.DeferredLoadingEnabled = false;
var ds = new DataLoadOptions();
ds.LoadWith<Prod>(p => p.Shift);
ds.LoadWith<Prod>(s => s.Module);
// Do this once, not in a tight loop
var ddlShift = objTransactionGeneralController.GetAllShift();
var ddlModule = objTransactionGeneralController.GetAllModule();
var result = db.Prods
.Where(p => p.ProdID > 5 && p.ProdID < 10) // Apply some kind of filtering
.Take(1000) // And / Or Limit the rows to something sane
.Select(pr => new GlobalModel()
{
prodModelIndex = pr,
// These 2 fields are actually redundant, as we now have navigation fields
// for these off prodModelIndex
shiftModel = s,
moduleModel = m,
ddlShift = ddlShift,
ddlModule = ddlModule
}).ToList();
return PartialView(result);
}
And the generated SQL will be a single query, with saner row limits:
SELECT TOP 1000 [t0].[ProdID], [t0].[ShiftID], [t0].[ModuleID], [t1].[ShiftId] AS [ShiftId2], [t2].[ModuleId] AS [ModuleId2]
FROM [dbo].[Prod] AS [t0]
INNER JOIN [dbo].[Shift] AS [t1] ON [t0].[ShiftID] = ([t1].[ShiftId])
INNER JOIN [dbo].[Module] AS [t2] ON [t0].[ModuleID] = ([t2].[ModuleId])
WHERE [t0].[ProdID] BETWEEN 5 AND 10;

ASP.NET MVC 3 WITH RAZOR : How to pass selected checkbox' ids in a Partial view to controller action?

I have a partialview [_SearchProduct] within the main view, let's say [product] view. The Partialview has a number of checkboxes segregated into different sections like search by company,search by product etc. with one [search] button.
A User can select multiple checkboxes. When user clicks [search] button I need to pass ids of all selected checkbox to controller action and re-render the page again considering the user's selection . Please guide me how to pass selected checkbox ids to my controller action.
My partial view is something like below:
<fieldset>
<legend>By Company</legend>
<table style="border-style: none;">
<tr>
#{
int i = 0;
foreach (var item in Model.CompanyName)
{
i = i + 1;
<td style="border-style: none;text-decoration:none;" >
#Html.CheckBox("chkCompany",new {id="chkCompany_" + Model.CompanyId.Tostring()}) #Model.CompanyName
</td>
if (i == 5)
{
#:</tr><tr>
i = 0;
}
}
}
</tr>
</table>
</fieldset>
<fieldset>
<legend>By Product</legend>
<table style="border-style: none;">
<tr>
#{
i = 0;
foreach (var item in Model.Product)
{
i = i + 1;
<td style="border-style: none;text-decoration:none;" >
#Html.CheckBox("chkProduct",new {id="chkProduct_" + Model.CompanyId.Tostring()}) #Model.ProductName
</td>
if (i == 10)
{
#:</tr><tr>
i = 0;
}
}
}
</tr>
</table>
</fieldset>
checkboxes are dynamic
Checkbox id represent the primarykey of respective table based on which i do filtering.
Please guide me>>
So it sounds like you have a structure containing names (of companies/products), and ids.
I would create a View Model structure that looked like
public class PartialViewModel //Make sure this is included in your main view model
{
public List<ObjectsToInclude> Companies { get; set; }
public List<ObjectsToInclude> Products { get; set; }
}
public class ObjectsToInclude //Give this a better name
{
public string Name { get; set; }
public string Id { get; set; }
public bool Include { get; set; }
}
Then in order to bind them you could do
for (int i =0; i<Model.Companies.Count(); i++)
{
<td style="border-style: none;text-decoration:none;" >
#Html.HiddenFor(m => m.Companies[i].Id)
#Html.CheckBoxFor(m => m.Companies[i].Include) #Model.Companies[i].Name
</td>
if (i == 5)
{
#:</tr><tr>
i = 0;
}
}
Then provided your post takes a parameter of PartialViewModel (or some MainViewModel where that contains an instance of PartialViewModel), you'll have lists of companies and products binded. You can loop through the list, and take the respective ids of anything checked to be included.
Edit: If you wanted a single comma separated array to be posted, it would be possible by by creating an onclick event for your checkboxes, and then setting a value of a hidden input every time a checkbox is clicked. But then your code would only work with JavaScript enabled. If you need a comma separated string, you can create it server side with the view model I suggested.
string companyIds = String.Join(",", model.Companies
.Where(company => company.Include)
.Select(company => company.Id));
http://dotnetnsqlcorner.blogspot.in/2012/09/multiple-checkboxes-in-mvc-3-and-post.html

MVC3 Some of my fields validate while some do not

Im sitting here scratching my head with a validation problem in ASP MVC3.
Somehow I'm able to validate the field Quantity, but the field OrderNumber does not validate. I can leave it empty and it still accepts it. I've tried to add other restrictions to it as well (such as max and min length) but same result - it accepts anything.
I also try changing 'TextBoxFor' to 'EditorFor' - but it's the same result.
Quantity on the other hand works as I want it. It requires you to enter an integer and it cannot be blank.
Hopefully some of you will be able to see what I'm doing wrong here :)
Here is my model:
public class Order
{
[Required(ErrorMessage="Insert Ordernumber (6-digits)")]
public string OrderNumber { get; set; }
[Required]
public string Partnumber { get; set; }
[Required]
public long Quantity { get; set; }
public Order()
{
}
}
And here is my view :
model POWeb.Models.AddModel
#using (Html.BeginForm("Add", "Home", FormMethod.Post))
{
//Create table
<table>
<tr>
<td>Select Partnumber to produce</td>
<td>#Html.DropDownListFor(model => model.SelectedPartNumber, Model.PartNumbers)</td>
</tr>
<tr>
<td>Enter PO number</td>
<td>#Html.TextBoxFor(model => model.OrderNumber)#Html.ValidationMessageFor(model => model.OrderNumber)</td>
</tr>
<tr>
<td>Quantity</td>
<td>#Html.TextBoxFor(model => model.Quantity)#Html.ValidationMessageFor(model => model.Quantity)</td>
</tr>
<tr>
<td colspan="2">
<button type="submit" name="SubmitButton">Add</button>
</td>
</tr>
</table>
}
You have the view of type POWeb.Models.AddModel, but you try to validate Order type. I'm pretty sure validation attributes on those types are not the same, so you get problems
Anders,
My 'guess' is that your ViewModel model POWeb.Models.AddModel isn't mirroring the [Required] attribute on OrderNumber. Can you add the definition of AddModel to your question for verification on that please as it's more than likely that the Order class differs.

Can't set SelectedItem in DropDownList in my MVC3 view

I know that I can set the SelectedItem in my controller, but I can't figure out how to set it in my view. I'm working on a sort of flashcard (study guide) application and I have imported about 400 test questions. Now I want to write a page for the instructor to be able to select a "Category" for each question. I'd like them to be able to update the category for all the questions on one page. My model has a question entity that contains a foreign key field to the category entity (the field is called QuestionCategory). So, my view is based on the Question entity, but I'm sending over the list of Categories (there are 14) in the ViewBag (so I don't have to send a full SelectList over with each of the 400 questions. As my view is iterating thru the items in my View, I just want to add a SelectList that contains the 14 categories in my ViewBag and then set the SelectedItem based on the value of item.QuestionCategory. I can't make it work.
Here's my controller action:
public ActionResult Index()
{
var context = new HBModel.HBEntities();
var query = from q in context.tblQuestions.Include("tblCategory") select q;
var questions = query.ToList();
ViewBag.Categories = new SelectList(context.tblCategories, "CategoryID", "CategoryName");
return View(questions);
}
Here's some of the things I've tried in the view (with associated error messages in the comments)
#model IEnumerable<HBModel.tblQuestion>
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Index</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table>
<tr>
<th>
Question
</th>
<th>
Answer
</th>
<th>
AnswerSource
</th>
<th>
Category
</th>
<th>
Action
</th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#item.Question
</td>
<td>
#item.Answer
</td>
<td>
#item.AnswerSource
</td>
<td>
#item.tblCategory.CategoryName
#*This one works, but cannot initialize the selected item to be current database value*#
#Html.DropDownList("Categories")
#*compile error - CS0200: Property or indexer 'System.Web.Mvc.SelectList.SelectedValue' cannot be assigned to -- it is read only*#
#*#Html.DropDownListFor(m => item.QuestionCategory, (ViewBag.Categories as SelectList).SelectedValue = item.QuestionCategory)*#
#*error {"DataBinding: 'System.Web.Mvc.SelectListItem' does not contain a property with the name 'CategoryId'."}*#
#Html.DropDownListFor(m => item.QuestionCategory, new SelectList(ViewBag.Categories, "CategoryId", "CategoryName"))
#*error - {"DataBinding: 'System.Char' does not contain a property with the name 'CategoryId'."}*#
#Html.DropDownListFor(m => item.QuestionCategory, new SelectList("Categories", "CategoryId", "CategoryName"))
)
</td>
<td style="width: 100px">
#Html.ActionLink("Edit", "Edit", new { id = item.QuestionID }) |
#Html.ActionLink("Delete", "Delete", new { id = item.QuestionID })
</td>
</tr>
}
</table>
Of course, if I can get this to work, I'll need to try and add an action to go back to the controller and update all the records, but I'll just be happy to resolve my current issue.
I would really appreciate any help on this - Thanks!
You need to explicitly create the options in the select tag, using #Html.DropDownList, as follows (taken from a working app):
#Html.DropDownListFor(model => model.IdAccountFrom, ((IEnumerable<FlatAdmin.Domain.Entities.Account>)ViewBag.AllAccounts).Select(option => new SelectListItem {
Text = (option == null ? "None" : option.AccountName),
Value = option.AccountId.ToString(),
Selected = (Model != null) && (option.AccountId == Model.IdAccountFrom)
}), "Choose...")
#Html.ValidationMessageFor(model => model.IdAccountFrom)
You obviously need to change to the properties on your #Model.
NOTE:
This code was auto-generated by the MvcScaffolding NuGet package when I scaffolded a controller.
This package requires you to use Entity Framework Code First POCO classes for your entities. These are easy to generate from an existing database using the Entity Framework Power Tools CTP.
With MVC, you need to spend some time researching the tooling that is out there to help you generate the stuff that you need. Using these tools is a great way to get started and to see how to do things. You can then tweak the output to your heart's content.

How can I reference MVC item/model in HTMLAttributes?

I'm trying to list two radio buttons in each row of a table, but I haven't been able to assign unique IDs to each radio button. I'd like to assign the IDs based on the #item.myID as follows:
#foreach (var item in Model)
{
<tr>
<td>
#Html.RadioButton("Yes", #item.myID, #item.IsCool, new { id = "#item.myID", autopostback = "true" })
#Html.RadioButton("No", #item.myID, !#item.IsCool, new { id = "#item.myID", autopostback = "true" })
</td>
</tr>
}
However, the IDs keep rendering literally as "#item.myID". In other words, it's not treating the # sign as a special character. I've also tried using parenthesis, like this: "#(item.myID)".
You need to remove the quotes from around the id assignment, like so;
#foreach (var item in Model)
{
<tr>
<td>
#Html.RadioButton("Yes", item.myID, item.IsCool, new { id = item.myID, autopostback = "true" })
#Html.RadioButton("No", item.myID, !item.IsCool, new { id = item.myID, autopostback = "true" })
</td>
</tr>
}
Also, note that you don't need the additional "#" symbol when you are already in the context of another Razor code block - the Razor View Engine is pretty clever :)

Resources