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.
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
Out of the 4 tables that I have, 3 contain similar(client,policyno,policytype..) fields but with different data. The other table, however, has very different fields(client,sex,telephone,dob..). I have a search box in my view that makes use of the telephone number to display related records from all the 3 tables. Unfortunately, I am not able to handle the fields of the fourth table. Keeps returning the error
System.IndexOutOfRangeException: 'PolicyNo'
My Model:
public class TableModels
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string Client { get; set; }
public string PolicyNo { get; set; }
public short? PolicyType { get; set; }
public string Telephone { get; set; }
//Health
public DateTime? DOB { get; set; }
public string Sex { get; set; }
public string DepMemberNumber { get; set; }
protected TableModels ReadValue(SqlDataReader reader)
{
TableModels obj = new TableModels();
if (reader["ID"] != DBNull.Value)
{
obj.ID = (int)reader["ID"];
}
if (reader["Client"] != DBNull.Value)
{
obj.Client = (string)reader["Client"];
}
if (reader["PolicyNo"] != DBNull.Value)
{
obj.PolicyNo = (string)reader["PolicyNo"];
}
if (reader["PolicyType"] != DBNull.Value)
{
obj.PolicyType = (short)reader["PolicyType"];
}
The error is returned on the Protected TableModel class.
Any advice would be appreciated
View:
#model IEnumerable
#foreach (var item in Model.OrderByDescending(m => m.Client))
{
<tr>
<td>
#Html.DisplayFor(modelitem => item.Client)
</td>
<td>
#Html.DisplayFor(modelitem => item.Telephone)
</td>
<td>
#Html.ActionLink("Details", "Details", new { id = item.Telephone })
</td>
</tr>
}
Controller:
public ActionResult Details(string id)
{
using (MainContext db = new MainContext())
{
if (id == null)
{
return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest);
}
List<SingleView> x1 = db.SingleViews.Where(a => a.Telephone == id).ToList();
List<SingleViewM> x2 = db.SingleViewMs.Where(a => a.Telephone == id).ToList();
List<SingleViewWst> x3 = db.SingleViewWsts.Where(a => a.Telephone == id).ToList();
List<PensionsView> x4 = db.PensionsViews.Where(a => a.Telephone == id).ToList();
List<Health> x5 = db.Health.Where(a => a.Telephone == id).ToList();
SingleModel objview = new SingleModel();
objview.USSD = x1;
objview.Mombasa = x2;
I am trying to populate an HTML table with data from a table in my database. The issue is simply that the HTML table is not getting populated with any data.
Here is the ViewModel:
public class TestViewModel
{
public string MatchedId { get; set; }
public string UnmatchedId { get; set; }
public string Auth { get; set; }
public DateTime CreditDate { get; set; }
public string CreditNumber { get; set; }
public decimal CreditAmount { get; set; }
public DateTime DeniedDate { get; set; }
public int DeniedReasonId { get; set; }
public string DeniedNotes { get; set; }
}
Controller Action:
[HttpPost]
public ActionResult UploadValidationTable(HttpPostedFileBase csvFile)
{
var inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true
};
var cc = new CsvContext();
var filePath = uploadFile(csvFile.InputStream);
var model = cc.Read<Credit>(filePath, inputFileDescription);
try
{
var entity = new Entities();
//model here is the .csv, doesn't have anything to do with this issue
foreach (var item in model)
{
var tc = new TemporaryCsvUpload
{
Id = item.Id,
CreditAmount = item.CreditAmount,
CreditDate = item.CreditDate,
CreditNumber = item.CreditNumber,
DeniedDate = item.DeniedDate,
DeniedReasonId = item.DeniedReasonId,
DeniedNotes = item.DeniedNotes
};
entity.TemporaryCsvUploads.Add(tc);
}
entity.SaveChanges();
System.IO.File.Delete(filePath);
//This is where the database table is getting filled
entity.Database.ExecuteSqlCommand("Insert into CsvReport Select p.Id as MatchedId, case when p.Id is null then t.Id end as UnmatchedId, p.Auth,p.CreditDate, p.CreditNumber,p.CreditAmount, p.DeniedDate,p.DeniedReasonId, p.DeniedNotes from TemporaryCsvUpload t left join PermanentTable p on p.Id = t.Id;");
TempData["Success"] = "Updated Successfully";
}
catch (LINQtoCSVException)
{
TempData["Error"] = "Upload Error: Ensure you have the correct header fields and that the file is of .csv format.";
}
return View("Upload");
}
View:
#model IEnumerable<TestProject.TestViewModel>
#if (Model != null)
{
foreach (var item in Model.Where(x => x.IdMatched != null))
{
<tr>
<td>
#item.MatchedId
</td>
<td>
#item.Auth
</td>
<td>
#item.CreditDate
</td>
<td>
#item.CreditNumber
</td>
<td>
#item.CreditAmount
</td>
<td>
#item.DeniedDate
</td>
<td>
#item.DeniedReasonId
</td>
<td>
#item.DeniedNotes
</td>
</tr>
}
}
It's a little weird because I am populating the database with an SQL command. What am I missing here? Do I need to try and pass it through the controller action? Let me know if you need more information. Thanks!
Edit
I tried to pass the instance through, but I may still be doing it incorrectly:
var testModel = new TestViewModel();
return View("Upload", testModel);
Here is what its padding through:
public class TestViewModel
{
public IEnumerable<Test> Test { get; set; }
}
Made an answer so essentially the view doesn't know what to render you need to pass an actual filled model (in your case an IEnumerable to the view). This can be done using the method:
View("Upload", viewModelList);
Controller.View docs on MSDN
It looks like you are not adding any data to your view model.
If your view model is a collection of Test objects, you need to add some
Test objects to the collection.
var model = new TestViewModel()
{
Test = new List<Test>() { new Test(), new Test(), ... }
}
return View("Upload", model);
I hope I explain this correctly..
What I am trying to do is build up a session array with a list of products in.
Then display these on a form in text boxes with quantiles next to them and be able to submit them. I think I need to use template editor. But I don't know how to put data into the list of items.
This is how my session variable is currently being populated..
IList<EnqProduct> items2 = Session["enquiry"] as IList<EnqProduct>;
desc = desc.Replace(",", "");
EnqProduct item = new EnqProduct();
item.Id = (items2.Count + 1).ToString();
item.Product = desc;
item.Quantity = "0";
items2.Add(item);
So desc, can be productone, product two etc.
Enquiry Product model:
namespace MvcEditorTemplates.Models
{
public class EnqProduct
{
public string Id { get; set; }
public string Product { get; set; }
public string Quantity { get; set; }
}
}
Normal Enquiry Model:
public class Enquiry
{
public List<EnqProduct> EnqProduct { get; set; }
}
How i am trying to populate the model, but this is static. I need it to be populated from the array items:
var EnquiryModel = new Enquiry {
EnqProduct = items2.Select(c => new EnqProduct()
{
Quantity = c.Quantity,
Product = c.Product
})
};
Enquiry product template view:
#model MvcEditorTemplates.Models.EnqProduct
<div class="fl">
<p>
#Html.LabelFor(x => x.Product)
#Html.TextBoxFor(x => x.Product)
</p>
<p>
#Html.LabelFor(x => x.Quantity)
#Html.TextBoxFor(x => x.Quantity)
</p>
</div>
This is how im trying to get it to be displayed din the view:
#Html.EditorFor(model => model.EnqProduct)
EDIT:
at items2.Select(c => new EnqProduct()
i get a IEnumerbale error something about cast?
Try something like this:
public class ErrorMessage
{
public DateTime ErrorDate { get; set; }
public string ErrorText { get; set; }
public int DexRowId { get; set; }
}
public class Transaction
{
public string TransactionType { get; set; }
public string Processed { get; set; }
public DateTime UpdateDate { get; set; }
public int DexRowID { get; set; }
public string Text { get; set; }
}
public class Result
{
public List<ErrorMessage> errorMessageList { get; set; }
public List<Transaction> transactionList { get; set; }
}
In your controller:
List<Transaction> transactionList = ...;//query to populate your list;
List<ErrorMessage> errorMessageList = ...;//query to populate your list;
Result result = new Result();
result.ErrorMessageList = errorMessageList;
result.TransactionList = transactionList;
return View(result);
and in your view:
#model Models.Result
#{
ViewBag.Title = "Result";
Layout = "~/Views/Shared/_ResultLayout.cshtml";
}
EDIT:
#model IENumerable<MvcEditorTemplates.Models.EnqProduct>
#{
foreach( EnqProduct ep in #model)
{
.... your code comes here.........
}
}