How to count sum of products - c#

I have a action , ViewModel that shows totaly of product grouped by ProductName. But this doesn't shows me how many in every department.
Let say I have 20 computers in It-department and 10 computers in adminstration department then my code shows my productname which is "Computers".
And Totaly withch is 30 but not How many in it-department and the same for administration.
And the same for Servers or other products.
So I'am trying to use this action to get number of products in every department. I know alrteady departemnt Id's and those department are not populate dynamicaly.
// This is in my HomeController and this is my action trying to get sum of every department
[HttpGet]
public ActionResult GetStatusOfDepartments(int ProductId, int departmentId )
{
var products = context.Inventory.Where(x => x.ProductId == ProductId && x.departmentId == departmentId).FirstOrDefault();
if(products != null)
{
return Content(products.Status.ToString());
}
else
{
return Content("No products");
}
}
And I want to call "GetStatusOfDepartments" action In this ViewModel but this givs me null. Can you please help me what is wrong
to call action in this ViewModel?
#model IEnumerable<ProductsInventory.Models.StatusModel>
<table class="table">
<tr>
<th>
Produkt name
</th>
<th>
It-Department
</th>
<th>
Adminstration
</th>
<th>
TotalProducts
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.ProductName)
</td>
<td>
// Here I want to call like this
#Html.Action("GetStatusOfDepartments", "Home", new { ProductId = item.ProductId, departmentId = 1 })
</td>
<td>
// The same Here I want to call like this
#Html.Action("GetStatusOfDepartments", "Home", new { ProductId = item.ProductId, departmentId = 2 })
</td>
<td>
#Html.DisplayFor(modelItem => item.Status)
</td>
</tr>
}
</table>

There are a couple of things that stand out with what you have done so far.
It's very unusual to see an ActionResult returning Content(). That means a controller is providing a raw string to a view, which is not really the point of controllers.
Currently the view has #Html.Action() requests embedded within a loop, which is a big indicator that the view is not being provided with an appropriate model.
The question's title suggests it has more to do with a database query than MVC.
At the moment, a single page load will result in many database queries, at least twice the number of product types. It is best to perform as few database queries as possible as they often have a big impact on the time it takes to load each page.
In my attempt to answer this question it became obvious that there is a relatively complex database query required to create the model. Maybe you have ended up using the approach above as a way to get past that, so I will try and answer this question without a complex database query while still adhering to the MVC design patterns (that should avoid the issues mentioned above)
Create a model
You already have a pretty good idea of what you want to display on the view. It's different enough from the database models that we should create a new model.
public class ProductsInDepartments
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public int ITTotal { get; set; }
public int AdminTotal { get; set; }
public int Status { get; set; }
}
This is simply a property for each column in your view. I think you only have two departments and their names aren't mapped in the database. If that's not correct then I would suggest a different model and approach.
Controller
The controller needs to prepare a model. In this case, we will get all of the data, then transform it to the model structure we need:
public enum Departments // Enumerating a database key like this can be helpful if the database itself doesn't describe the numbers in a lookup table or something
{
IT = 1,
Admin = 2
};
[HttpGet]
public ActionResult Status()
{
var Inventory = context.Inventory.ToList(); // Get all records
var ViewModel = new List<Models.ProductsInDepartments>();
foreach (int ProductId in Inventory.Select(e => e.ProductId).Distinct().ToList())
{
ViewModel.Add(new Models.ProductsInDepartments()
{
ProductId = ProductId,
ProductName = Inventory.First(e => e.ProductId == ProductId).ProductName,
AdminTotal = Inventory.Count(e => e.ProductId == ProductId && e.DepartmentId == (int)Department.Admin),
ITTotal = Inventory.Count(e => e.ProductId == ProductId && e.DepartmentId == (int)Department.IT),
Status = Inventory.First(e => e.ProductId == ProductId).Status // I'm not sure what you are trying to do with Status, so you might need to change this
});
}
return View(ViewModel);
}
View
Now the view is very straightforward.
#model List<ProductsInventory.Models.ProductsInDepartments>
<table class="table">
<tr>
<th>Product Name</th>
<th>IT Department</th>
<th>Administration</th>
<th>Total Products</th>
</tr>
#foreach (var Item in Model)
{
<tr>
<td>#Model.ProductName</td>
<td>#Model.ITTotal.ToString()</td>
<td>#Model.AdminTotal.ToString()</td>
#if (Model.Status == 0)
{
<td>No Products</td>
}
else
{
<td>#Model.Status</td>
}
</tr>
}
</table>
Again, I'm not sure what you're trying to do with status but for string overrides you can do those in the view like this and it's perfectly fine. The view should handle various aspects of presentation-layer concerns.

In #Html.Action the first parameter should be action name and second parameter should be controller name. You need to change your code to match that.

Related

Why the database view show data correct but when I called it in controller show different result?

I changed the project design and created database view :
ALTER VIEW [dbo].[samplesList]
AS
SELECT DISTINCT
results.machine_id,
sample_id,
program_id,
Machines.Machine_id AS 'ID',
custid,
sys_users.user_full_name AS 'HospitalName',
Programs.name AS 'ProgramName',
machines.Machine_name AS 'MachineName',
samples.name AS 'SampleName'
FROM
results
INNER JOIN
programs ON RESULTS.program_id = Programs.id
INNER JOIN
Machines ON RESULTS.machine_id = Machines.Machine_id
INNER JOIN
sys_users ON RESULTS.custid = sys_users.user_id
INNER JOIN
samples ON RESULTS.sample_id = samples.id
This is the result in the database :
See the screenshot - it shows the correct data sample no 1, sample no 2, sample no 3 and their machines are correct.
But in the controller when I link the view with the controller its not show same result from the database this is the controller code :
public ActionResult Indexs()
{
int UserId = Convert.ToInt32(Session["UserID"]);
var samples = _context.samplesLists
.Where(x=> x.custid == UserId).ToList();
return View(samples);
}
This is the model :
namespace warehouse.Models
{
using System;
using System.Collections.Generic;
public partial class samplesList
{
public Nullable<int> machine_id { get; set; }
public Nullable<int> sample_id { get; set; }
public Nullable<int> program_id { get; set; }
public int ID { get; set; }
public Nullable<int> custid { get; set; }
public string HospitalName { get; set; }
public string ProgramName { get; set; }
public string MachineName { get; set; }
public string SampleName { get; set; }
}
}
And finally the surprise this is the output for same view in the site :
All data appears as "sample no 1":
This is the view markup:
#model IEnumerable<warehouse.Models.samplesList>
#{
ViewBag.Title = "Indexs";
Layout = "~/Views/Shared/_LayoutDashboard.cshtml";
}
<img style="margin-left:250px;" src="~/images/weblogo.png" />
<p style="margin-left:40px;">
#*<h3 style="margin-left:100px; font-family:Andalus;text-underline-position:below">
#Html.Label("Hospital Name :")
#Html.DisplayFor(model => Model.FirstOrDefault().)
</h3>*#
<table class="table table-bordered">
<tr style="background-color:hotpink">
<th>
#Html.DisplayNameFor(model => model.ProgramName)
</th>
<th>
#Html.DisplayNameFor(model => model.SampleName)
</th>
<th>
#Html.DisplayNameFor(model => model.MachineName)
</th>
#*<th></th>
<th></th>
<th></th>*#
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.ProgramName)
</td>
<td>
#Html.DisplayFor(modelItem => item.SampleName)
</td>
<td>
#Html.DisplayFor(modelItem => item.MachineName)
</td>
Please I need your help what happening why all solutions not working and what I need to change ?
EF has its share of troubles with views - because views typically don't have a defined primary key, which is crucial for EF to detect which data has already been loaded (and which hasn't).
What happens here is: since there's no primary key for your view, EF will just use all non-nullable columns from the view as a "substitute" PK.
And when EF reads the data, it will go:
read the row
check the primary key (or the "substitute" PK in this case)
if it has already read a row with that PK - it just duplicates that row that it already has - it will disregard any non-PK columns actually read from the view!
So in your case, once it's read a first row with the "substitute" PK, any further rows with those same values will just get the already read values - no matter what's stored in the database!
SOLUTION: EF needs a proper primary key to uniquely identify each individual row of data.
One solution is to add every single PK of all the underlying tables, and make sure those are marked as "non-nullable" in your model class. Another solution might be to add a custom ROW_NUMBER() OVER (....) column as an "artificial" dummy PK which gives each row a unique value - so that no two rows being read from the view are considered identical.
I solved it in different way as marc_s said there is different activity in database views and primary keys not deal like the tables primary key
What I did :
I created new TABLE (MASTER_RESULTS) and inserted the data I need without duplicate and created the controller and view based on this master table .

Bind HTML table TH dynamically using View Model Failed

I am trying to bind the HTML table using the View Model of type Object on the Razor Page. Code is as below :
index.cshtml.cs
[BindProperty]
public List<object> TableData { get; set; }
public class Cainfo
{
public string Ca_id { get; set; }
public object Nca { get; set; }
}
public async Task<IActionResult> OnGetAsync()
{
List<object> tablerows = GetTableRows(tabledata);
TableData = tablerows;
}
public List<object> GetTableRows(GetTableRowsResponse getTableRowsResponse)
{
List<object> tableRows = new List<object>();
var tables = getTableRowsResponse.rows;
foreach (var table in tables)
{
var tab = JsonConvert.SerializeObject(table);
var row = JsonConvert.DeserializeObject<Cainfo>(tab);
tableRows.Add(row);
}
return tableRows;
}
index.cshtml
<table class="resultTable">
<thead class="grid-header">
<tr>
#foreach (var property in #Model.TableData.GetType().GetGenericArguments()[0].GetProperties())
{
<th class="col-lg-1">#property.Name</th>
}
</tr>
</thead>
<tbody class="grid-body">
#if (#Model.TableData != null)
{
#if ((#Model.TableData.Count != 0))
{
#foreach (var row in Model.TableData)
{
<tr>
#foreach (var property in #row.GetType().GetProperties())
{
<td class="col-lg-1">#property.GetValue(#row)</td>
}
</tr>
}
}
}
</tbody>
</table>
var tables = getTableRowsResponse.rows; return the JSON data. Problem is that table <th> is not getting bind. #Model.TableData.GetType().GetGenericArguments()[0].GetProperties() is getting empty. <td> is getting bind as expected. Maybe I am doing a mistake somewhere, I am new to the asp .net core. Now, I am using the Model Cainfo but in future, I need to set different models according to data and bind the same table. That's why I am giving View Model type as Object. Please help.
I would not use reflection like this when you can design common models for the view. That's the art of designing which makes things easier. However here assume that you want to stick with that solution anyway, I'll show where it's wrong and how to fix it and how it's limited.
First this Model.TableData.GetType().GetGenericArguments()[0] will return Type of object. And that Type of course contains no properties. That's why you get no <th> rendered. The generic argument type exactly reflects what's declared for Model.TableData which is a List<object>.
Now to fix it, assume that all the items in the List<object> are of the same type, you can get the first item's Type, like this:
#foreach (var property in #Model.TableData?.FirstOrDefault()
?.GetType()?.GetProperties()
?? Enumerable.Empty<PropertyInfo>())
{
<th class="col-lg-1">#property.Name</th>
}
That has a limit in case the Model.TableData contains no item. No <th> will be rendered. If that's acceptable (instead of rendering an empty table with headers, you will render nothing or just some message) then just go that way. Otherwise, you need to provide a Type for the element/row's Typevia your model, such as via a property like Model.RowType. Then you can use that instead of this:
Model.TableData?.FirstOrDefault()?.GetType()
The remaining code is just the same.

Display multiple models in a single view

I have to create one view of invoice. I have many models (tables) and I want to display all data from multiple models in one view of invoice. I created one empty (without model) view and put into a partial views. One of Partial View return one view with one model but this solution return me exception... This is my work:
This is my action in controller:
public ActionResult Customer()
{
var data = db.Customer;
return PartialView("Index", data);
}
public ActionResult Invoice()
{
var data = db.Invoice;
return PartialView("Index", data);
}
public ActionResult Dealer()
{
var data = db.Dealer;
return PartialView("Index", data);
}
public ActionResult Paid()
{
var data = dane.Paid;
return PartialView("Index", data);
}
public ActionResult Products()
{
var data = dane.Products;
return PartialView("Index", data);
}
This is one of partial view:
#model IEnumerable<Invoice_v1._0.Models.Products>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Price)
</th>
<th>
#Html.DisplayNameFor(model => model.Amount)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Price)
</td>
<td>
#Html.DisplayFor(modelItem => item.Amount)
</td>
</tr>
}
</table>
This is my "Index" view with Partial Views:
#Html.Partial("_Customer")
#Html.Partial("_Invoice")
#Html.Partial("_Dealer")
#Html.Partial("_Paid")
#Html.Partial("_Products")
How can I fix them?
If you insist on having a single view, then you can make rendering of partial views conditional:
if (Model != null && Model is Customer)
Html.Partial("_Customer", Model as Customer)
, and so on.
On a related note, since all the models are exclusive (you never have more than one object), I don't see the point of having one Index view. You already have one (partial) view per class, so why not just use them as regular views?
Update
If you choose to return separate view from each of the action methods, then you can do that by specifying the name of each view. Refer to this question for details: How to return ActionResult with specific View (not the controller name)
In your particular case, that would mean:
public ActionResult Customer()
{
return View("_Customer", db.Customer);
}
public ActionResult Invoice()
{
return View("_Invoice", db.Invoice);
}
public ActionResult Dealer()
{
return View("_Dealer", db.Dealer);
}
public ActionResult Paid()
{
return View("_Paid", db.Paid);
}
public ActionResult Products()
{
return View("_Products", db.Products);
}
Make sure that each of the views expects a strongly typed model. That will make it easy to implement the views.
While your approach may work, you will have less or no control over the data (if you want to let's assume join or use any of the parent data). In such situations what I do is create a ViewModel with more than one objects as part of it. For example
public class InvoiceViewModel
{
public InvoiceHead Header { get;set; }
public IList<InvoiceDetail> Details { get; set; }
}
So now I can populate this View Model before going into my view.
public ActionResult Invoice(int id)
{
InvoiceViewModel viewModel = new InvoiceViewModel();
viewModel.Header = db.InvoiceHead.SingleOrDefault(i => i.ID == id);
if(viewModel.Header != null)
{
viewModel.Details = db.Details.Where(d => d.Inv_id == id).ToList();
}
return View(viewModel);
}
Now I can design my view with either Partial views or one whole view for my invoice data.
I will put as model the IEnumerable or IList
if I use partial for both it will be
#model db.Objects.InvoiceHead // for the header
#model IEnumerable // for the details
in html code will be like that
#Html.Partial("~/Views/Customers/_AttachFileList.cshtml")
use your own folder path. hope works

Filter a property IENumerable to only include items that pass a test

I have a list of items, Articles, and each article can have a series of reviews. Right now I grab the list of articles from my database using Entity Framework in my controller, db.Articles and I send that to my view. In my view I want to display all the articles and some of the reviews. For instance, the reviews done by a certain user.
The way I do this know looks like:
#foreach (var item in Model) { //item is a article, model is IEnumerable<Article>
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#foreach (var reviewStatus in item.ReviewStatus.Where(u=> u.user.Id == ViewBag.userId))
{
#Html.DisplayFor(model => reviewStatus.Status)
#Html.Raw(", ")
}
</td>
</tr>
}
I feel like there should be a way in my Controller to send Articles that already have this filter applied so that I don't have to do it in my view (since it can be a bit slow).
Primarily it is not good that you are using the Entity Framework object as model for your view. Model in MVC is conceptually still element in UI layer, but EF is far away from it in persistence layer. And you should have at least domain and business logic layer in between.
What I usually do is to define the Model as an extra class, I construct the instance of class in controller action handler and I take all the data I need within the view from that instance (which is available in view as Model indeed).
In your case the class(es) would look somehow like this:
public class ArticleModel {
public string Title { get; set; }
// other article properties
private IEnumerable<ReviewModel> reviews;
public IEnumerable<ReviewModel> Reviews {
get {
if (reviews == null) {
// lazy loader to get ReviewModels from your business/domain logic
}
return reviews;
}
}
}
public class ArticlesPageModel {
public string PageTitle { get; set; }
private IEnumerable<ArticleModel> articles;
public IEnumerable<ArticleModel> Articles {
get {
if (articles == null) {
// lazy load articles from business/domain logic
}
return articles;
}
}
}
etc... I hope you see the pattern...
Usually my "model" classes have a constructor, which takes domain object (EF entity in your case) and they know how to initialize themselves.
I think in the action in your controller you can do something like this:
var allArticles = new IEnumerable<Article>();
allArticles = db.GetAllArticles(); //or whatever method that you have for grabing everything from db
var filteredArticles = allArticles.FinAll(x=>x.ReviewStatus != null && x.ReviewStatus.Count > 0 && x.ReviewStatus.Exists(u=> u.user.Id == 1234) && x.ReviewStatus.Exists(u.otherCriteria = 4321 ));
foreach(var article in filterArticles)
{
var specificReviews_1 = article.ReviewStatus.FinaAll(u=> u.user.Id == 1234).Take(3);
var specificReviews_2 = article.ReviewStatus.FinaAll(u=> u.user.Id != 1234 && u.otherCriteria = 4321).Take(3);
article.ReviewStatus = new List<ReviewStatus>();
article.ReviewStatus.AddRange(specificReviews_1 );
article.ReviewStatus.AddRange(specificReviews_2 );
}
return View("yourView", filterArticles);
and then in view do something like this
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#foreach (var reviewStatus in item.ReviewStatus)
{
#Html.DisplayFor(model => reviewStatus.Status)
#Html.Raw(", ")
}
</td>
</tr>
}

Rendering partial view in ASP.Net MVC3 Razor using Ajax

The base functionality I wish to achive is that the contents of a table are updated when a dropdownlist item is selected. This will update when the user makes a new selection and retrieve new information from the database and repopulate the table.
It's also worth noting that the DropDownListFor that I want the .change() to work with is not contained within the AjaxForm but appears elsewhere on the page (admittedly in another form)
To achieve this I looked at this question: Rendering partial view dynamically in ASP.Net MVC3 Razor using Ajax call to Action which does a good job of going part the way of what I want to do.
So far, I have a controller method which handles populating a customized viewmodel for the partial view:
[HttpPost]
public ActionResult CompanyBillingBandDetails(int id = 0)
{
var viewModel = new BillingGroupDetailsViewModel();
var billingGroupBillingBands =
_model.GetAllRecordsWhere<BillingGroupBillingBand>(x => x.BillingGroupId == id).ToList();
foreach (var band in billingGroupBillingBands)
{
viewModel.BillingBands.Add(band.BillingBand);
}
return PartialView("BillingGroupDetailsPartial", viewModel);
}
The ViewModel I wish to populate each call:
public class BillingGroupDetailsViewModel
{
public List<BillingBand> BillingBands { get; set; }
}
The strongly typed model I'm using as a model for the partial view
public class BillingBandsObject
{
public int BillingBandId { get; set; }
public int RangeFrom { get; set; }
public int RangeTo { get; set; }
public Decimal Charge { get; set; }
public int BillingTypeId { get; set; }
public bool Delete { get; set; }
}
The partial view it populates and returns:
#model xxx.xxx.DTO.Objects.BillingBandsObject
<tr>
<td>
#Html.DisplayTextFor(x => x.RangeFrom)
</td>
</tr>
<tr>
<td>
#Html.DisplayTextFor(x => x.RangeTo)
</td>
</tr>
<tr>
<td>
#Html.DisplayTextFor(x => x.Charge)
</td>
</tr>
The Razor code for this section of the page:
<table>
<thead>
<tr>
<th>
Range From
</th>
<th>
Range To
</th>
<th>
Charge
</th>
</tr>
</thead>
<tbody>
#using (Ajax.BeginForm("CompanyBillingBandDetails", new AjaxOptions() { UpdateTargetId = "details", id = "ajaxform" }))
{
<div id="details">
#Html.Action("CompanyBillingBandDetails", new { id = 1 })
</div>
}
</tbody>
</table>
and finally the function I lifted almost straight from Darin's answer:
$(function () {
$('#billinggrouplist').change(function () {
// This event will be triggered when the dropdown list selection changes
// We start by fetching the form element. Note that if you have
// multiple forms on the page it would be better to provide it
// an unique id in the Ajax.BeginForm helper and then use id selector:
var form = $('#ajaxform');
// finally we send the AJAX request:
$.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: form.serialize(),
success: function (result) {
// The AJAX request succeeded and the result variable
// will contain the partial HTML returned by the action
// we inject it into the div:
$('#details').html(result);
}
});
});
});
At the moment I have fought through a number of errors, currently I am faced with :
"Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerAsyncWrapper'."
However, i feel my understanding of the problem as a whole may be lacking.
Any help appreciated!
This error means that there was an exception while rendering your child view. Probably something related to your data, ie. NulLReferenceException.
Just attach your debugger and set to to break when an exception is thrown.

Categories

Resources