I am trying to create a LINQ query that allows the user to select Either Min Or Max from a drop down that I have on my web page. If the user selects Min it will display the record with the lowest Quote (Quote coming from a database table called rentals)
I have not had any luck with .Min() and .Max(). I am not sure how to go about this so any help would be appreciated.
This is my if statement for deciding what option the user has chosen on the drop down box:
namespace CarGarageSales.Pages.Queries
{
[BindProperties]
public class Query3Model : PageModel
{
private readonly CarGarageSales.Data.CarGarageSalesContext _context;
public IList<Rental> Rental { get; set; }
[BindProperty(SupportsGet = true)]
public int UserInput { get; set; }
public Query3Model(CarGarageSales.Data.CarGarageSalesContext context)
{
_context = context;
}
public async Task OnGetAsync()
{
if (UserInput == 0)
{
var Rentals = (from s in _context.Rentals
select s);
Rental = await Rentals.ToListAsync();
}
else if (UserInput == 1)
{
var Rentals = (from s in _context.Rentals
select s).Min();
}
else
{
var Rentals = (from s in _context.Rentals
select s.Quote).Max();
}
}
}
}
This is my HTML section:
#page
#model CarGarageSales.Pages.Queries.Query3Model
#{
ViewData["Title"] = "Query3";
}
<h2>Query3</h2>
<form>
<p>
Min or Max?:<select asp-for="UserInput">
<option></option>
<option>Min</option>
<option>Max</option>
</select>
<input type="submit" value="Search" />
</p>
</form>
<p class="Text">Here is the record for the Rentals you requested!</p>
<table class="table Text">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Rental[0].RentalID)
</th>
<th>
#Html.DisplayNameFor(model => model.Rental[0].Price)
</th>
<th>
#Html.DisplayNameFor(model => model.Rental[0].Duration)
</th>
<th>
#Html.DisplayNameFor(model => model.Rental[0].Quote)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Rental)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.RentalID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Price)
</td>
<td>
#Html.DisplayFor(modelItem => item.Duration)
</td>
<td>
#Html.DisplayFor(modelItem => item.Quote)
</td>
</tr>
}
</tbody>
</table>
Rentals class:
namespace CarGarageSales.Models
{
[BindProperties]
public class Rental
{
[Required]
public int RentalID { get; set; }
[Required]
[Display(Name = "Price")]
[Range(100, 200000, ErrorMessage = "Price must be between 100 and 200,000 Pounds")]
public decimal Price { get; set; }
[Required]
[Display(Name = "Duration")]
[Range(1, 36,ErrorMessage = "Duration must be between 1 and 36 Months")]
public int Duration { get; set; }
[Required]
[Display(Name = "Quote")]
[Range(20, 10000, ErrorMessage = "Quote must be between 20 and 10000 Pounds")]
public decimal Quote { get; set; }
public Customer Customer { get; set; }
public virtual IList<Car> Cars { get; set; }
public virtual IList<SalesManRental> SalesManRental { get; set; }
}
}
Let's see an example below (Here is your live code)
using System;
using System.Linq;
public class Program
{
public static void Main()
{
var option = 1;
var list = new[]{ new Rental{ RentalID = 1, Quote = 1 }, new Rental{ RentalID = 2, Quote = 2 }, new Rental{ RentalID = 3, Quote = 3 }, new Rental{ RentalID = 4, Quote = 1 }, new Rental{ RentalID = 5, Quote = 3 } };
var minQuote = list.Min((p => p.Quote));
var maxQuote = list.Max((p => p.Quote));
var result = list.Where(p => (option == 1 && p.Quote == minQuote) || (option == 2 && p.Quote == maxQuote));
if(option == 0)
result = list;
foreach(var item in result)
Console.WriteLine(item.Quote);
}
public class Rental
{
public int RentalID { get; set; }
public decimal Price { get; set; }
public decimal Quote { get; set; }
}
}
Note: Change value of option in range 0, 1, 2 to test your case.
Not tested, but you could do something like this:
var filteredRentals = _context.Rentals.GroupBy(g => g.RentalID)
.Select(x => new Rental
{
RentalID = x.Key,
Quote = x.Max(z => z.Quote),
Duration = x.FirstOrDefault(r => r.RentalID == x.Key && r.Quote == x.Max(z => z.Quote)).Duration,
Price = x.FirstOrDefault(r => r.RentalID == x.Key && r.Quote == x.Max(z => z.Quote)).Price
}).ToList();
The idea is you group it by RentalID to get the Max/Min Quote, then get the first record that has those RentalID and Quote
In case they have same quote and you want to show all records instead of the first:
var groupedRentals = _context.Rentals.GroupBy(g => g.RentalID)
.Select(x => new
{
RentalID = x.Key,
Quote = x.Max(z => z.Quote)
}).ToList();
var filteredGroupedRentals = _context.Rentals.Where(r => groupedRentals.Any(g =>
g.RentalID == r.RentalID &&
g.Quote == r.Quote))
.ToList();
As others have mentioned, you'll need to use .Select(s => s.Quote).Min() (or .Max). However, your select list options don't have any values. You need to specify the appropriate values, such as:
<option value="0">All</option>
<option value="1">Min</option>
<option value="2">Max</option>
You last example within the else should work to return the max Quote, as long as you are sure have data in _context.Rentals. I am not sure why you are using Async Task but you are not awaiting anything within UserInput == 1 or the else, only UserInput == 0
This should work
var Rentals = (from s in _context.Rentals select s.Quote).Max();
Example (test it here https://dotnetfiddle.net/2SnPcS)
using System;
using System.Collections.Generic;
using System.Linq;
public class Program {
public class Rental
{
public int RentalID { get; set; }
public decimal Price { get; set; }
public int Duration { get; set; }
public decimal Quote { get; set; }
}
public static void Main()
{
// sample data
var rentals = new List<Rental> {
new Rental { RentalID = 1, Price = 100.00m, Duration = 2, Quote = 200.00m },
new Rental { RentalID = 2, Price = 100.00m, Duration = 2, Quote = 200.00m },
new Rental { RentalID = 3, Price = 100.00m, Duration = 1, Quote = 100.00m },
new Rental { RentalID = 4, Price = 100.00m, Duration = 1, Quote = 100.00m },
new Rental { RentalID = 5, Price = 100.00m, Duration = 5, Quote = 500.00m }
};
// get min quote
var minQuote = (from s in rentals select s.Quote).Min();
// get max quote
var maxQuote = (from s in rentals select s.Quote).Max();
Console.WriteLine(string.Format("min={0} max={1}", minQuote, maxQuote));
// Outputs: min=100.00 max=500.00
}
}
I am not sure what the rest of your application looks like or how it is being used, but yes you can call your methods anything you want. Do you want just the Quote decimal value, or the entire Rental associated with it? To change your OnGetAsync method based on what you showed us, getting the decimal value only, it would look something like this (you were almost there).
public decimal GetQuoteAmount()
{
// this could be min
if (UserInput == 0)
{
return (from s in _context.Rentals select s.Quote).Min();
}
// this could be max
else if (UserInput == 1)
{
return (from s in _context.Rentals select s.Quote).Max();
}
else
{
// is there a third option? What do you want to happen if they do not select Min or Max?
return 0;
}
}
I think part of the problem is that you only assign Rental in the case where UserInput == 0. Adding an assignment in the other cases should make your results update when the user changes their input.
Additionally, it sounds like you want to get the full records of all the objects with the largest (or smallest) Quote. If this is the case, then the OrderBy and GroupBy methods should help here because they allow you to sort the data based on it's properties, like the value of Quote, and group data with the same value together.
For example:
public async Task OnGetAsync()
{
if (UserInput == 0)
{
Rental = _context.Rentals().ToListAsync();
}
else if (UserInput == 1)
{
// Get the group of items with min quote
Rental = _context.Rentals()
.GroupBy(r => r.Quote) // Group items with the same quote (Quote becomes the Key)
.OrderBy(g => g.Key) // Order the groups by quote (smallest will be first)
.FirstOrDefault() // Choose the first group (those with min quote)
.ToListAsync(); // Select the items into a list
}
else
{
// Get the group of items with max quote
Rental = _context.Rentals()
.GroupBy(r => r.Quote) // Group items with the same quote (Quote becomes the Key)
.OrderBy(g => g.Key) // Order the groups by quote (smallest will be first)
.LastOrDefault() // Choose the last group (those with max quote)
.ToListAsync(); // Select the items into a list
}
}
Related
How to get the total for each category and at the end get the sum of all categories?
I tried cummulative total but it did not work.
How to get the total for each category and at the end get the sum of
all categories?
Well, though I got your requirement but this is not the correct way to ask question as someone might be misunderstand your issue. It would always be nice idea to share your scenario with reproducible code snippet. However, let's consider below scenario.
Model:
Assuming, I have below class: So will calculate price by each category and finally would calculate the total of all category prices.
public class ProductByCateory
{
public string Category { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
}
Class For Calculated Model:
public class CalculativeProductByCateory
{
public string Category { get; set; }
public string ProductName { get; set; }
public double PriceSumByEachCategory { get; set; }
public double TotalPriceOfAllCategory { get; set; } = 0;
}
Note: Above class I would use while calculate final result derive from ProductByCateory class
Linq Query For Calculating Each Category Sum and Total Sum:
Let's consider I have below List of Product.
List<ProductByCateory> productListByCategory = new List<ProductByCateory>();
productListByCategory.Add(new ProductByCateory() { Category = "Cat-A", Price = 150.5, ProductName = "Product1" });
productListByCategory.Add(new ProductByCateory() { Category = "Cat-A", Price = 250.00, ProductName = "Product1" });
productListByCategory.Add(new ProductByCateory() { Category = "Cat-B", Price = 150.50, ProductName = "Product2" });
productListByCategory.Add(new ProductByCateory() { Category = "Cat-B", Price = 150.50, ProductName = "Product2" });
productListByCategory.Add(new ProductByCateory() { Category = "Cat-C", Price = 500, ProductName = "Product3" });
Thus, if I now calculate each category sum, I should get 401.5 for Cat-A and 301 for Cat-B therefore, 500 for Cat-C.
To meet above goal we can write our query as following:
List<CalculativeProductByCateory> sumListByCategory = productListByCategory
.GroupBy(cat => cat.Category)
.Select(sumByCat => new CalculativeProductByCateory
{
Category = sumByCat.First().Category,
ProductName = sumByCat.First().ProductName,
PriceSumByEachCategory = sumByCat.Sum(c => c.Price),
TotalPriceOfAllCategory = productListByCategory.Sum(p => p.Price)
}).ToList();
Complete Sample:
Asp.net core Razor Page (cshtml.cs):
public class PriceSumByCategoryAndTotalOfALLCategoryModel : PageModel
{
public List<CalculativeProductByCateory> calculativeProductByCateories { get; set; }
public void OnGet()
{
List<ProductByCateory> productListByCategory = new List<ProductByCateory>();
productListByCategory.Add(new ProductByCateory() { Category = "Cat-A", Price = 150.5, ProductName = "Product1" });
productListByCategory.Add(new ProductByCateory() { Category = "Cat-A", Price = 250.00, ProductName = "Product1" });
productListByCategory.Add(new ProductByCateory() { Category = "Cat-B", Price = 150.50, ProductName = "Product2" });
productListByCategory.Add(new ProductByCateory() { Category = "Cat-B", Price = 150.50, ProductName = "Product2" });
productListByCategory.Add(new ProductByCateory() { Category = "Cat-C", Price = 500, ProductName = "Product3" });
List<CalculativeProductByCateory> sumListByCategory = productListByCategory
.GroupBy(cat => cat.Category)
.Select(sumByCat => new CalculativeProductByCateory
{
Category = sumByCat.First().Category,
ProductName = sumByCat.First().ProductName,
PriceSumByEachCategory = sumByCat.Sum(c => c.Price),
TotalPriceOfAllCategory = productListByCategory.Sum(p => p.Price)
}).ToList();
calculativeProductByCateories = sumListByCategory;
}
}
Asp.net core Razor Page (cshtml)::
#page
#model RazorPageDemoApp.Pages.PriceSumByCategoryAndTotalOfALLCategoryModel
#{
double totalSum = 0;
}
<h2>Sum By Category and Total Sum</h2>
<table class="table table table-bordered">
<thead>
<tr>
<th>Category Name
<th>Product Name
<th>PriceSumByEachCategory
</tr>
</thead>
<tbody>
#foreach (var item in Model.calculativeProductByCateories)
{
<tr>
<td>#item.Category</td>
<td>#item.ProductName</td>
<td>#item.PriceSumByEachCategory</td>
<td hidden> #{
var total = #item.TotalPriceOfAllCategory;
totalSum = total;
}</td>
</tr>
}
</tbody>
</table>
<h3>Total:<strong style="margin-left:640px">#totalSum</strong> </h3>
Razor Page Output:
Note: If you are interested on more details about Linq query you could have a look here in official document.
#foreach (var item in Model.calculativeProductByCateories)
{
foreach (var product in ProductBycategory)
{ if (item.Category == product.Category)
{
#product.Category
#product.ProductName
#product.Price
}
}
#item.PriceSumByEachCategory
}
#Model.GrandTotal
</tr>
I want to display the property name of a field in a view(report).
The Model:
public class Report
{
[Display(Name ="Total Attendance")]
public int Attendance { get; set; }
[Display(Name = "Total Offering")]
[DataType(DataType.Currency)]
public double Amount { get; set; }
[Display(Name = "District Name")]
public int LocationId { get; set; }
[ForeignKey("LocationId")]
public Location Location { get; set; }
[Display(Name = "Weekly Service")]
public int WeeklyServiceId { get; set; }
[ForeignKey("WeeklyServiceId")]
[Display(Name = "Weekly Service")]
public WeeklyService WeeklyService { get; set; }
public DateTime? Sdate { get; set; }
public DateTime? Edate { get; set; }
public string UsherName { get; set; }
public string LocationName { get; set; }
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Report(Report model)
{
var startDate = model.Sdate;
var endDate = model.Edate;
var QueryResult = (from pay in _context.PaymentRecords.Include(p=>p.Location).Include(p=>p.WeeklyService)
//join a in _context.Locations on pay.LocationId equals a.Id
//join c in _context.WeeklyServices on pay.WeeklyServiceId equals c.Id
where (pay.DepositDate.Date >= startDate)
where (pay.DepositDate.Date <= endDate)
group pay by new { pay.LocationId,pay.WeeklyServiceId} into g
orderby g.Key.LocationId
select new Report
{
LocationId= g.Key.LocationId,
Attendance = g.Sum(x => x.Attendance),
Amount = g.Sum(x => x.Amount),
WeeklyServiceId =g.Key.WeeklyServiceId
});
return View("Report", QueryResult);
}
The View/Report
<table class="table table-striped table-bordered" id="myTable">
<thead class="thead-dark">
<tr>
<th>SN</th>
<th>#Html.DisplayNameFor(model => model.Location)</th>
<th>#Html.DisplayNameFor(model => model.Attendance)</th>
<th>#Html.DisplayNameFor(model => model.Amount)</th>
<th>#Html.DisplayNameFor(model => model.WeeklyService)</th>
</tr>
</thead>
<tbody>
#if (Model.Count() > 0)
{
int c = 0;
foreach (var item in Model)
{
c++;
<tr>
<td>#c</td>
<td>#Html.DisplayFor(modelItem => item.Location.Name)</td>
<td>#Html.DisplayFor(modelItem=>item.Attendance)</td>
<td>#item.Amount.ToString("C")</td>
<td>#Html.DisplayFor(modelItem=>item.WeeklyService.Name)</td>
</tr>
}
}
else
{
}
</tbody>
</table>
Result of the Above
Note that Location is a model which has Name as a property as well as WeeklyService. But if i change the table data to LocationId and WeeklyServiceId, it will display the results with the Id. But I want it to display the Name of the Location and WeeklyService instead of their Ids.
Here is an exmaple of what we mean in the comments.
You do not Initialize correctly the Report object.
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Report(Report model)
{
var startDate = model.Sdate;
var endDate = model.Edate;
var QueryResult = (from pay in _context.PaymentRecords.Include(p=>p.Location).Include(p=>p.WeeklyService).ToList()
//join a in _context.Locations on pay.LocationId equals a.Id
//join c in _context.WeeklyServices on pay.WeeklyServiceId equals c.Id
where (pay.DepositDate.Date >= startDate)
where (pay.DepositDate.Date <= endDate)
group pay by new { pay.LocationId,pay.WeeklyServiceId} into g
orderby g.Key.LocationId
select new Report
{
LocationId= g.Key.LocationId,
Attendance = g.Sum(x => x.Attendance),
Amount = g.Sum(x => x.Amount),
WeeklyServiceId =g.Key.WeeklyServiceId
Location = g.Select(pp => pp.Location).First() // This is what you are missing
WeeklyService = g.Select(pp => pp.WeeklyService ).First()// Also this
});
return View("Report", QueryResult);
}
The Location and WeeklyService is null. They are never initialized.
I am surprised you do not get a Null Ref Exception. You never mentioned one.
I am saying this because of the (item => item.Location.Name) in your View.
Hope this helps.
Note careful with client side evaluation and EF core 3 https://github.com/dotnet/efcore/issues/17878 , https://github.com/dotnet/efcore/issues/17068
Also taken from the docs: https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#linq-queries-are-no-longer-evaluated-on-the-client
I am wondering if I can sum up decimal in all the columns that are dynamically generated in razor view without having to change this in controller and viewModel. I will be happy to try jQuery option as well.
ViewModel
public class LedgerViewModel
{
public LedgerViewModel(int PayCategoryCount)
{
PayCollection = new List<decimal>(new decimal[PayCategoryCount]);
}
public DateTime PDate { get; set; }
public class MonthlyPaymentsVM
{ public int PayCategoryId { get; set; }
[DisplayFormat(DataFormatString = "{0:MMMM yyyy}")]
public DateTime Date { get; set; }
public IEnumerable<string> PaymentCategories { get; set; }
public List<LedgerViewModel> Payments { get; set; }
public List<LedgerViewModel> Member { get; set; }
}
controller
public ActionResult Report(int year, int no, string nom, Guid gcode)
{
DateTime startdate = (new DateTime(DateTime.Now.Year - 1, 7, 1));
DateTime enddate = (new DateTime(DateTime.Now.Year, 7, 1));
DateTime date = DateTime.Now;
//get all payments
var a = from of in db.OfflinePayMents.Include(i => i.Customer).Include(i => i.PaymentCategory).ToList().Where(x => x.PDate >= startdate && x.PDate < enddate && x.POK && x.CustomerGuid=gcode) select new { of.Customer, of.PaymentCategory, of.CustomerID, of.PaymentCategoryID, of.PDate, of.TxnId, of.Pay, of.PType };
var grouped = a.GroupBy(x => new { customer = x.CustomerID, PaymentCategory = x.PaymentCategory.PaymentCategoryID, txn = x.TxnId, pd = x.PDate, x.PType }).Select(x => new
{
Name = x.First().Customer.Name,
Customer = x.First().Customer,
PaymentCategory = x.First().PaymentCategory,
Txn = x.First().TxnId,
Pd = x.First().PDate,
PType = x.First().PType,
cid = x.First().PaymentCategoryID,
Pay= x.Sum(y => y.Pay)
});
var data = grouped.GroupBy(x => x.Txn);
var PaymentCategories = db.PaymentCategories.OrderBy(z => z.Order);
var PayCategoryCount = PaymentCategories.Count();
var PaymentCategoryIDs = PaymentCategories.Select(x => x.PaymentCategoryID).ToList();
var model = new MonthlyPaymentsVM()
{
//Member = members,
Date = date,
PaymentCategories = PaymentCategories.Select(z => z.PaymentCategoryTitle),
Payments = new List<LedgerViewModel>()
};
foreach (var group in data)
{
LedgerViewModel payment = new LedgerViewModel(PaymentCategoryCount);
var pd = group.First().Pd;
payment.PDate = pd;
foreach (var item in group)
{
int index = PaymentCategoryIDs.IndexOf(item.PaymentCategory.PaymentCategoryID);
if (index < 0)
{
payment.PayCollection[index + 1] = item.Pay;
}
else
{
payment.PayCollection[index] = item.Pay;
}
payment.Total += item.Pay;
}
model.Payments.Add(payment);
}
return View(model);
}
Razor View
<table class="doubleborder" width="99%" border="0" align="center" cellpadding="0" cellspacing="0">
<thead>
<tr>
<th>Date</th>
#foreach (var payname in Model.PaymentCategories)
{
<th>#payname</th>
}
</tr>
</thead>
<tbody>
#foreach (var item in Model.Payments)
{
<tr>
<td>#item.PayDate.ToString("dd/MM/yyyy")</td>
#foreach (var amount in item.PayCollection)
{
<td>#amount.ToString("c")</td>
}
</tr>
}
<tr class="doubleborder">
<td>Total:</td>
#foreach (var item in Model.PaymentCategories)
{
<td>
looking at getting sum totals of each column here
</td>
}
</tr>
</tbody>
</table>
So this groups by customer. The payments are grouped by transaction Date and are iterated in the foreach loop in the view.
Thanks if you are able to help.
If I understand your model correctly, this is what you are looking for:
#Model.Payments.Sum(i => i.Paycollection.Sum(x => x.amount));
Update, based on your comment: (and assuming that all the columns have value for all the PaymentCollections)
#for(int i = 0 ; i < PaymentCategories.Count(); i++)
{
<td>
Model.Payments.Sum(x => x.PayCollection[i]); //this will throw an exception if the value at index is not defined
</td>
}
for part of my program I need to read given values in the past week, group them by weekday and then display them in a table in MVC. I have managed to display a long list of numerical days (showing repeats) with their matching data but I need the format to be from monday-sunday which means it would have to combine values that occur on the same day.
My controller:
public ActionResult Index(string TimeState)
{
DateTime currentDate = DateTime.Now;
DateTime today = DateTime.Today;
DateTime week = DateTime.Today.AddDays(-7);
string userID = User.Identity.GetUserId();
string email = (from x in db.Users where x.Id == userID select x.Email).FirstOrDefault();
IEnumerable<ValuesList> valuesList = from x in db.UsageDatas
where week <= x.Time && x.Time <= today && x.UserEmail == email
select new ValuesList
{
Dates = x.Time.Day.ToString(),
Values = x.Delta ,
};
State state = new State
{
state = TimeState,
valuesLists = valueList,
selectedDate = "0"
};
return View(state);
}
View:
<div class="Table Heading"><h3>Recent Water Usage</h3></div>
#{
}
<table class="greyGridTable">
<thead>
<tr>
<th>Date</th>
<th>Value</th>
</tr>
</thead>
#foreach (var item in Model.valuesLists)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Dates)
</td>
<td>
#Html.DisplayFor(modelItem => item.Values)
</td>
</tr>
}
</table>
</div>
Model:
public class State
{
public string state { get; set; }
public IEnumerable<ValuesList> valuesLists { get;set;}
public string TimeState { get; set; }
public string selectedDate { get; set; }
}
public class ValuesList
{
public string Dates { get; set; }
public string Values { get; set; }
}
what the current table looks like:
note: its showing the current month day*
My LINQ is pretty limited so any insight would be highly appreciated.
here is the new error
You want to adding today's data?
If yes.
DateTime today = DateTime.Today;
DateTime tomorrow = today.AddDays(1);
DateTime week = today.AddDays(-7); // or -6 think about it
string userID = User.Identity.GetUserId();
string email = (from x in db.Users where x.Id == userID select x.Email).FirstOrDefault();
var valuesList = db.UsageDatas
.Where(x => x.Time >= week && x.Time < tomorrow && x.UserEmail == email)
//.AsEnumerable() //posible need it for execute sql before take DayOfWeek
.GroupBy(x => x.Time.DayOfWeek)
.Select(x => new
{
DayOfWeek = x.Key,
Values = x.Select(m => m.Delta).Aggregate((a, b) => a + "," + b)
})
.OrderBy(m => m.DayOfWeek)
.Select(x => new ValuesList
{
Dates = x.DayOfWeek.ToString(),
Values = x.Values
});
Group by DayOfWeek and aggregate Delta as you want
For sum of Deltas: (but it's crutch)
Values = x.Select(x =>
{
int.TryParse(x, out var value);
return value;
})
.Sum()
I am having trouble with grouping a list, and then building a model that represents that list and displaying the results in a table within a view. For example:
List of items ordered
Date
CustomerId
Location
Item
Price
Quantity
How would I correctly model and display that list in a table if I wanted to group the list by location? And what if I wanted to group the list by two properties, for example, Location and CustomerId?
Here is my model:
public class ViewInvoice
{
public string ClientLocation { get; set; }
public List<DetailsGroup> Details { get; set; }
public class DetailsGroup
{
public List<string> Product { get; set; }
public List<string> ProductSize { get; set; }
public List<string> PackageType { get; set; }
public List<DateTime> OrderDate { get; set; }
public List<DateTime> DeliveryDate { get; set; }
public List<int> OrderNumber { get; set; }
public List<decimal> Price { get; set; }
public List<int> ItemQuantity { get; set; }
}
}
I am trying to display this model in a table within my razor view. Here is that code:
#using MyModel.MyTools.Orders.SumOrder
#model SumOrder
#{
ViewBag.Title = "View Invoice";
}
<h2>View Invoice</h2>
<table>
#foreach(var prod in Model.OCI)
{
<tr>
<td>
#prod.ClientLocation
</td>
</tr>
foreach (var orderItem in prod.Details)
{
<tr>
<td>
#orderItem.Product
</td>
<td>
#orderItem.ItemQuantity
</td>
</tr>
}
}
</table>
The first row in the table displays correctly, which is the name of a city, but in the next row I get this:
System.Collections.Generic.List1[System.String] System.Collections.Generic.List1[System.Int32]
Can someone explain to me why I can not get the list returned in a readable format, and how to correct this problem?
Here is the code I used to group the list for the ViewInvoice model:
public SumOrder(List<orders_Cart> order)
{
// create list of order cart item
List<OrderCartItems> cartItems = new List<OrderCartItems>();
// convert orders to ocm
foreach(var item in order)
{
var newCartItem = new OrderCartItems();
try
{
newCartItem.Product = db.product_Product.FirstOrDefault(p =>
p.Id == item.ProductId).ProductDescription ?? "none";
}
catch (Exception)
{
newCartItem.Product = "none";
}
try
{
newCartItem.ClientForProduct = MyTool.OrdersFindClientLocation(
(int) item.ClientForOrdersId);
}
catch (Exception)
{
newCartItem.ClientForProduct = new object[3];
}
try
{
newCartItem.ProductSize = db.products_Size.FirstOrDefault(p => p.Id ==
item.ProductSizeId).ProductSizeCode ?? "none";
}
catch (Exception)
{
newCartItem.ProductSize = "none";
}
try
{
newCartItem.PackageType = db.packaging_PackageType.FirstOrDefault(p =>
p.Id == item.PackageTypeId).PackageTypeCode ?? "none";
}
catch (Exception)
{
newCartItem.PackageType = "none";
}
newCartItem.OrderDate = (DateTime) item.OrderDate;
newCartItem.DeliveryDate = (DateTime) item.DeliveryDate;
newCartItem.OrderNumber = (int) item.OrderNumber;
newCartItem.Price = (decimal) item.Price;
newCartItem.ClientLocation = MyTool.OrdersFindClientLocation(
(int) item.ClientForOrdersId, null);
newCartItem.ItemQuantity = (int) item.Quantity;
cartItems.Add(newCartItem);
}
// group the cartItems according to location
List<ViewInvoice> ordersGrouped = cartItems.GroupBy(c => new
{c.ClientLocation})
.OrderBy(c => c.Key.ClientLocation).Select(s =>
new ViewInvoice()
{
ClientLocation = s.Key.ClientLocation,
Details = new List<ViewInvoice.DetailsGroup>()
{
new ViewInvoice.DetailsGroup()
{
Product = s.Select(p => p.Product).ToList(),
ItemQuantity = s.Select(p => p.ItemQuantity).ToList(),
DeliveryDate = s.Select(p => p.DeliveryDate).ToList(),
OrderDate = s.Select(p => p.OrderDate).ToList(),
OrderNumber = s.Select(p => p.OrderNumber).ToList(),
PackageType = s.Select(p => p.PackageType).ToList(),
Price = s.Select(p => p.Price).ToList(),
ProductSize = s.Select(p => p.ProductSize).ToList()
}
}
}).ToList();
// set the OCI property
OCI = ordersGrouped;
};
Ok, I finally solved my problem. I initially over-thought the problem. I simplified my model, and added some simple logic to my view.
Here is the updated Model:
public class ViewInvoice
{
public string ClientLocation { get; set; }
public List<string> Product { get; set; }
public List<string> ProductSize { get; set; }
public List<string> PackageType { get; set; }
public List<DateTime> OrderDate { get; set; }
public List<DateTime> DeliveryDate { get; set; }
public List<int> OrderNumber { get; set; }
public List<decimal> Price { get; set; }
public List<int> ItemQuantity { get; set; }
}
the updated code used to group the list for the Model:
// group the cartItems according to location
List<ViewInvoice> ordersGrouped = cartItems.GroupBy(c => new
{c.ClientLocation})
.OrderBy(c => c.Key.ClientLocation).Select(s =>
new ViewInvoice()
{
ClientLocation = s.Key.ClientLocation,
Product = s.Select(p => p.Product).ToList(),
ItemQuantity = s.Select(p => p.ItemQuantity).ToList(),
DeliveryDate = s.Select(p => p.DeliveryDate).ToList(),
OrderDate = s.Select(p => p.OrderDate).ToList(),
OrderNumber = s.Select(p => p.OrderNumber).ToList(),
PackageType = s.Select(p => p.PackageType).ToList(),
Price = s.Select(p => p.Price).ToList(),
ProductSize = s.Select(p => p.ProductSize).ToList()
}).ToList();
and the updated view:
#using MyModel.MyTools.Orders.SumOrder
#model SumOrder
#{
ViewBag.Title = "View Invoice";
}
<h2>View Invoice</h2>
#{
int i = 0;
}
<table>
#foreach(var mod in Model.OCI)
{
var modCount = #mod.Product.Count();
<tr>
<th>#mod.ClientLocation</th>
</tr>
<tr>
<th>Product</th>
<th>Price</th>
</tr>
foreach (var items in mod.Product)
{
<tr>
<td>
#mod.Product.ElementAtOrDefault(i)
</td>
<td>
#mod.Price.ElementAtOrDefault(i)
</td>
</tr>
i++;
}
}
</table>
This solution clearly allows me to iterate through the model reproducing any required rows or cells along the way. Played Russian roulette for two days over this problem. Hope this saves some others some time.