Loading LINQ results into a ViewModel that has a calculated attribute (column) - c#

I've seen the scenarios where a ViewModel is populated with one LINQ query as shown below. Question: How can I populate the TotalSale attribute (column) - that is a grand total of Sale column - of the following ViewModel? Note: I'm using latest version of ASP.NET Core with VS2015.
ViewModel:
public class CategProdViewModel
{
public string Category { get; set; }
public string ProductName { get; set; }
public float Sale { get; set; }
public float TotalSale { get; set; }
}
Controller:
var innerJoinQuery = from category in categories
join prod in products on category.ID equals prod.CategoryID
select new CategProdViewModel { Category = category.Name, ProductName = prod.Name, Sale = prod.Sale };

You will have many of these in your view:
public class CategProdViewModel
{
public string Category { get; set; }
public string ProductName { get; set; }
public float Sale { get; set; }
public real TotalSale { get; set; }
}
But you will only have the last property TotalSale once. Therefore, change your view model to this:
public class CategProdViewModel
{
public string Category { get; set; }
public string ProductName { get; set; }
public float Sale { get; set; }
}
Create this model for your view and pass it to your view.
public class UseThisOnYourView // Give it another name
{
public List<CategProdViewModel> Items { get; set; }
public real TotalSale { get { return this.Items.Sum(x => x.Sale); } }
}
Then your query will be like this:
var innerJoinQuery = from category in categories
join prod in products on category.ID equals prod.CategoryID
select new CategProdViewModel { Category = category.Name, ProductName = prod.Name, Sale = prod.Sale };
var model = new UseThisOnYourView
{
Items = innerJoinQuery.ToList()
};
NOTE: Please keep in mind you will need to adjust your view to use the new type you are passing to it and adjust the code accordingly.

You don't clarify the structure of Product, but you can Sum multiple values
var innerJoinQuery = from category in categories
join prod in products on category.ID equals prod.CategoryID
select new CategProdViewModel {
Category = category.Name,
ProductName = prod.Name,
Sale = prod.Sale,
TotalSales = products.Sum(t => t.Sale)
};
Without more about the code, it's hard to say, and also, the prod.sale is probably only going to be the amount of the last product. You can use Sum(this IEnumerable<TSource> source, Func<TSource, double> selector) to sum the total of multiple items.
This is a quick answer. It will probably need to be altered depending on what the rest of your code looks like. You can also Group the query results to sum.

Related

C# MVC View to return City and State names instead of id's

I am having some issues trying to figure out how to display the names of the state and city instead of the StateId and CityId.
I am aware that you can put an instance of States and Cities in the model like this:
public virtual Cities Cities { get; set; }
public virtual States States { get; set; }
And then add this to the view:
#Html.DisplayFor(modelItem => item.Cities.CityName)
#Html.DisplayFor(modelItem => item.States.StateName)
However I have a few things that do not allow me to do this without error.
I use a ViewModel that gives me what I need for the view. It relies on a list from Addresses Table and in the Addresses Model I have Select Lists for these so that I can populate a dropdown list for each on edit and create.
When I add these to this to the Model and remove the selectLists I get an error that says it cannot find the Column Cities_CityId, States_StateId.
When I add it to the viewModel it just isn't available on the view. If I only needed it while showing 1 record I can use
Model.Cities.CityName
But I am retrieving several records, if they exist and using that would display the same city and state for all records, I believe..
I believe that all of my joins are correct but could be missing something.
Below is the ViewModel:
public partial class AddressOverview
{
public static AddressOverview GetAddressByCompany(int id, GeneralEntities db)
{
var qCus = from ad in db.Addresses
join cn in db.CompanyNames on ad.CompanyId equals cn.CompanyId
join cit in db.Cities on ad.City equals cit.CityId
join st in db.States on ad.State equals st.StateId
where (ad.CompanyId == id)
select new AddressOverview()
{
AddressId = ad.AddressId,
Customer = cn.CompanyName,
Location = ad.LocationName,
Addresses = ad,
CompanyId = ad.CompanyId,
State = st.StateName,
City = cit.CityName
};
var result = qCus.FirstOrDefault();
if (result != null)
{
result.AddressDetail = db.Addresses.Where(a => a.CompanyId == result.CompanyId);
};
return result;
}
public int AddressId { get; set; }
public string Customer { get; set; }
public string Location { get; set; }
public int CompanyId { get; set; }
public string City { get; set; }
public string State { get; set; }
public virtual Addresses Addresses { get; set; }
public virtual CompanyNames CompanyName { get; set; }
public virtual Cities Cities { get; set; }
public virtual States States { get; set; }
public virtual IEnumerable<Addresses> AddressDetail { get; set; }
}
And here is the controller:
public ActionResult Index(int id)
{
//Trying to get a view model for customer from the received UserId.
AddressOverview modelInstance = AddressOverview.GetAddressByCompany(id, db);
if (modelInstance == null)
{
//return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
return RedirectToAction("/Add/", new { id });
}
return View(modelInstance);
}
I am wondering if maybe there is a way to do this in the controller using the modelInstance? Please forgive me, I am trying to clean up some things that someone else did and my skills are a little weak.
If you need to see something else let me know.
Thanks for your help!
Update:
I just had a smack in the head moment.. Looking at View from question posted below I noticed that I was calling it from the Addresses Model instead of the ViewModel. So I corrected that but it still does not work..
Changed from this:
#model MyProject.GenerDTO.Entities.Addresses
To:
#model MyProject.Models.AddressOverview
Update:
Here is the solution I came up with. Probably not the best, but certainly a quick fix..
I decided to create a view on the MSSQL side and bring all of this together and then used that in the view and the ViewModel. If you decide to do something like this. Be sure to make sure that it is [NotMapped] in the entities. We are only using this to view and it is not meant to be used for editing, creating, etc.
Here is the new View Model:
public partial class AddressOverview
{
public static AddressOverview GetAddressByCompany(int id, GeneralEntities db)
{
var qCus = from ad in db.AddressView
join cn in db.CompanyNames on ad.CompanyId equals cn.CompanyId
where (ad.CompanyId == id)
select new AddressOverview()
{
AddressId = ad.AddressId,
Customer = cn.CompanyName,
Location = ad.LocationName,
AddressView = ad,
CompanyId = ad.CompanyId
};
var result = qCus.FirstOrDefault();
if (result != null)
{
result.AddressDetail = db.AddressView.Where(a => a.CompanyId == result.CompanyId);
};
return result;
}
public int AddressId { get; set; }
public string Customer { get; set; }
public string Location { get; set; }
public int CompanyId { get; set; }
public virtual AddressView AddressView { get; set; }
public virtual CompanyNames CompanyName { get; set; }
public virtual IEnumerable<AddressView> AddressDetail { get; set; }
}
I believe that creating a view in SQL is clean and the query is done server side. It can be treated like a table in the project. I am open to alternatives.

How to combine Join with an aggregate function and group by

I am trying to get the average rating of all restaurants and return the names of all resteraunts associated with that id, I was able to write a sql statement to get the average of restaurants along with the names of the restaurants however I want to only return the name of the restaurant once.
Select t.Average, Name from [dbo].[Reviews] as rev
join [dbo].[Resteraunts] as rest
on rest.ResterauntId = rev.ResterauntId
inner join
(
SELECT [ResterauntId],
Avg(Rating) AS Average
FROM [dbo].[Reviews]
GROUP BY [ResterauntId]
)
as t on t.ResterauntId = rest.ResterauntId
resteraunt class
public int ResterauntId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public virtual ICollection<Review> Reviews { get; set; }
public virtual Review reviews{ get; set; }
Review class
public int ReviewId { get; set; }
public double Rating { get; set; }
[ForeignKey("ResterauntId")]
Resteraunt Resteraunt { get; set; }
public int ResterauntId { get; set; }
public DateTime DateOfReview { get; set; }
If possible I would like to have the answer converted to linq.
Resteurants.Select(r => new {
Average = r.Reviews.Average(rev => rev.Rating),
r.Name
})
This should give you a set of objects that have Average (the average of all reviews for that restaurant) and the Name of the restaurant.
This assumes that you have correctly setup the relationships so that Restaurant.Reviews only refers to the ones that match by ID.
If you don't have that relationship setup and you need to filter it yourself:
Resteurants.Select(r => new {
Average = Reviews.Where(rev => rev.ResteurantId == r.Id).Average(rev => rev.Rating),
r.Name
})
Firstly your models seems to have more aggregation than required, I have taken the liberty to trim it and remove extra fields, ideally all that you need a Relation ship between two models RestaurantId (Primary Key for Restaurant and Foreign Key (1:N) for Review)
public class Restaurant
{
public int RestaurantId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public virtual ICollection<Review> Reviews { get; set; }
}
public class Review
{
public int ReviewId { get; set; }
public double Rating { get; set; }
public int RestaurantId { get; set; }
public DateTime DateOfReview { get; set; }
}
If these are the models, then you just need List<Restaurant> restaurantList, since that internally contains the review collection, then all that you need is:
var result =
restaurantList.Select(x => new {
Name = x.Name,
Average = x.Reviews.Average(y => y.Rating)
}
);
In case collection aggregation is not there and you have separate ReviewList as follows: List<Review> reviewList, then do the following:
var result =
reviewList.GroupBy(x => x.RestaurantId, x => new {x.RestaurantId,x.Rating})
.Join(restaurantList, x => x.Key,y => y.RestaurantId,(x,y) => new {
Name = y.Name,
AvgRating = x.Average(s => s.Rating)
});
Also please note this will only List the Restaurants, which have atleast one review, since we are using InnerJoin, otherwise you need LeftOuterJoin using GroupJoin, for Restaurants with 0 Rating
I see your Restaurant class already has an ICollection<Review> that represents the reviews of the restaurant. This is probably made possible because you use Entity Framework or something similar.
Having such a collection makes the use of a join unnecessary:
var averageRestaurantReview = Restaurants
.Select(restaurant => new
.Where(restaurant => ....) // only if you don't want all restaurants
{
Name = restaurant.Name,
AverageReview = restaurants.Reviews
.Select(review => review.Rating)
.Average(),
});
Entity Framework will do the proper joins for you.
If you really want to use something join-like you'd need Enumerable.GroupJoin
var averageRestaurantReview = Restaurants
.GroupJoin(Reviews, // GroupJoin Restaurants and Reviews
restaurant => restaurant.Id, // From every restaurant take the Id
review => review.RestaurantId, // From every Review take the RestaurantId
.Select( (restaurant, reviews) => new // take the restaurant with all matching reviews
.Where(restaurant => ....) // only if you don't want all restaurants
{ // the rest is as before
Name = restaurant.Name,
AverageReview = reviews
.Select(review => review.Rating)
.Average(),
});

How to query a flatten sub collection in RavenDb? Index needed?

I am using RavenDb in C# web project. I have an object that I need to query its child collection with 1 row per child object and some of the root/parent object properties.
Note: This is not the actual design, just simplified for this question.
public class OrderLine
{
public string ProductName { get; set; }
public int Quantity { get; set; }
public DateTime? ShipDate { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public string CustomerName { get; set; }
public DateTime OrderDate { get; set; }
public List<OrderLine> OrderLines { get; set; }
}
The order with the orderlines is one single document. ShipDate will be updated on each line because not all products are always in stock.
I need to be able to create a list of the last 10 products sent with the following columns:
OrderId
Customer
ProductName
ShipDate
This doesn't work because SelectMany is not supported:
var query = from helper in RavenSession.Query<Order>()
.SelectMany(l => l.OrderLines, (order, orderline) =>
new { order, orderline })
select new
{
helper.order.OrderId,
helper.order.CustomerName,
helper.orderline.ProductName,
helper.orderline.ShipDate
};
var result = query.Where(x => x.ShipDate.HasValue)
.OrderByDescending(x => x.ShipDate.Value).Take(10);
I believe the right thing to do isto create an Index that will flatten out the list but I haven't had any success. I don't believe a Map-Reduce situation will work because as I understand it will effectively does a group by which Reduces the number of documents to less rows (in the index). But in this case, I am trying to expand the number of documents to more rows (in the index).
I would rather not put each OrderLine in a separate document but I do not know what my options are.
Since you want to filter and sort by fields in the subclass, you'll need to make sure all the fields you want are indexed and stored.
public class ShippedItemsIndex
: AbstractIndexCreationTask<Order, ShippedItemsIndex.Result>
{
public class Result
{
public int OrderId { get; set; }
public string CustomerName { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
public DateTime ShipDate { get; set; }
}
public ShippedItemsIndex()
{
Map = orders =>
from order in orders
from line in order.OrderLines
where line.ShipDate != null
select new
{
order.OrderId,
order.CustomerName,
line.ProductName,
line.Quantity,
line.ShipDate
};
StoreAllFields(FieldStorage.Yes);
}
}
Then you can project from the index into your results.
var query = session.Query<Order, ShippedItemsIndex>()
.ProjectFromIndexFieldsInto<ShippedItemsIndex.Result>()
.OrderByDescending(x => x.ShipDate)
.Take(10);
var results = query.ToList();
Here is a complete test demonstrating.

Linq to Entities, Master Detail into DataContracts Query

This is part of my WebAPI, and I'm having trouble getting this data out of Linq to entities into my Datacontract objects. I will be returning a custom data type to the caller, whether they want it XML or JSON, I don't care, I'm just handing this to WebAPI to take care of it.
I'm using VS2013 and EF5, ASP.NET 4.5 in C#
The structure is as follows:
ProductCategory
ID
CategoryName
(a million other things)
List<Products>
Products
ID
ProductName
(a million other things)
Category
I have set up a DataContract that looks like the following:
ProductCategoryDataContract
ProductCategoryId
ProductCategoryName
List<ProductDataContract> Products
ProductDataContract
ProductName
Basically, I want to get a Linq query to return ALL categories, and within it ALL products.
from prodcat in context.ProductCategories order by prodcat.ItemOrder
select new ProductCategoryDataContract
{
ProductCategoryId = prodcat.Id
Products = prodcat.Products // this obviously fails as ProductDataContract != Products
}
If I try
Products = new List<ProductDataContract> { //initializer stuff }
I don't have any of the intellisense things I would think I would have (ProductName, etc), because I'm guessing I'm in the list.
Basically, I have all the relationships set up and I can get everything in straight up EF, but because I'm putting these into new datacontracts, it's giving me a little grief (mostly due to my lack of linq knowledge).
My question is:
1.) how can I do this
and
2.) how can I do this with minimal database hits. Potentially I'm firing off thousands of items within tens of product groups.
Thanks much, and if I'm not clear on anything, please lmk. And, the above is pseudocodish, not the real deal so if I made stupid naming errors, that's unlikely 'it' :)
public interface IProducts
{
int ProductId { get; set; }
decimal Price { get; set; }
List<IProductCategories> Categorieses { get; set; }
}
public interface IProductCategories
{
int ProductId { get; set; }
string ProductCategoryName { get; set; }
}
internal class Products : IProducts
{
public int ProductId { get; set; }
public decimal Price { get; set; }
public List<IProductCategories> Categorieses { get; set; }
}
internal class ProductCategories : IProductCategories
{
public int ProductId { get; set; }
public string ProductCategoryName { get; set; }
public ProductCategories(int productId, string productCategoryName)
{
ProductId = productId;
ProductCategoryName = productCategoryName;
}
}
public class ProductDataContract
{
public int ProductId { get; set; }
public List<IProductCategories> Categorieses { get; set; }
}
//Here is how you get your data:
// your retun objects
var products = new List<ProductDataContract>();
using (
var db =
new DataClassesDataContext(
ConfigurationManager.ConnectionStrings["TestConnectionString"].ConnectionString))
{
foreach (var prod in db.Products.Select(p => new Products {ProductId = p.ProductId}))
{
prod.Categorieses = new List<IProductCategories>();
foreach (var category in db.ProductCategories.Where(c => c.ProductId == prod.ProductId))
{
prod.Categorieses.Add(new ProductCategories(category.ProductId, category.ProductCategoryName));
}
products.Add(new ProductDataContract {Categorieses = prod.Categorieses, ProductId = prod.ProductId});
}
}

I'm trying to find the subtotal of the selected items

I have the prices of the items in an array:
double[] productPriceArray = { 1162.99, 399.99, 329.99, 199.99, 149.99 };
I am trying to find the total of the ones that a user puts in a "cart". I'm not sure how to go about that. I know I can use the line of code:
subtotal = productCostArray[lbxCart.SelectedIndex];
lblSubtotal.Text = subtotal.ToString("c");
to find the total of one of the indices, but how can I find the total of multiple indices?
Thanks!
There is no way in the current design , You need atleast a list of product and product price mapping then you need a list of products in the cart and in the cart class you can define a function to get the subtotal of all products.
public class Product
{
public string ProductId { get; set; }
public string ProductName { get; set; }
public double Price { get; set; }
}
public class ShoppingCart
{
public string CartId { get; set; }
public List<Product> Products { get; set; }
public void AddProductToCart(Product p)
{
if(Products==null)
Products = new List<Product>();
if(p!=null)
Products.Add(p);
}
public double CartPrice()
{
return Products != null ? Products.Sum(p => p.Price):0D;
}
}
and usage
var shopCart = new ShoppingCart();
shopCart.AddProductToCart(new Product {ProductId = "1",Price = 12.09, ProductName = "P1"});
shopCart.AddProductToCart(new Product {ProductId = "2",Price = 11.09, ProductName = "P2"});
MessageBox.Show(shopCart.CartPrice().ToString());
If you have a list of selected item indices (as your example appears to suggest), you could do:
var subtotal = SelectedIndices.Select(idx => productPriceArray[idx]).Sum();

Categories

Resources