c# MVC Pass Group Data to View - c#

I have a view where I display a model, but at the model I want to also display a small table of data from another model that comes from the following linq in the controller
var StudentRatings = db.LearnerRatings
.Where(i => i.LessonId == id)
.GroupBy(i => i.Rating)
.Select(group => new { Rating = group.Key, TotalCount = group.Count() });
ViewBag.Ratings = StudentRatings;
I'm already returning another model from the controller, so have added this to the ViewBag.
I thought I would be able to iterate through these with a foreach
foreach (var ratings in ViewBag.Ratings){
#ratings.TotalCount
}
but get an error - Additional information: 'object' does not contain a definition for 'TotalCount'
I take it this has to be cast? If so what does it get casted to?
Is this the best approach for the above?

Anonymous types should not traverse method boundaries: that is, your View doesn't know what type ratings is.
Change your anonymous type to a named struct or class, then you can do it like so:
class Rating {
public Int32 Rating { get; set; }
public Int32 TotalCount { get; set; }
}
...
.Select(group => new Rating { Rating = group.Key, TotalCount = group.Count() });
...
foreach(Rating rating in ViewBad.Ratings) {
#rating.TotalCount
}

Related

Only primitive types do I need to cast?

I am trying to use anonymous types in Entity Framework, but I am getting an error about
Unable to create a constant value
MinQty and MaxQty are int so I don't know if I need to add to Convert.ToInt32?
Unable to create a constant value of type 'Anonymous type'. Only primitive types or enumeration types are supported in this context.
This builds a list object
var listOfLicense = (from l in db.License
select new
{
l.ProductId,
l.MinLicense,
l.MaxLicense
}).tolist();
This is the larger EF object where I am getting the error am I missing a casting?
var ShoppingCart = (from sc in db.ShoppingCarts
Select new model.Shoppingchart{
ShoppingCartId= sc.Id,
MinQty = (int)listOfLicense
.Where(mt => (int)mt.ProductId == sc.ProductId)
.Select(mt => (int)mt.MinLicense)
.Min(mt => mt.Value),
MaxQty = (int)listOfLicense
.Where(mt => (int)mt.ProductId == p.ProductId)
.Select(mt =>(int) mt.MaxQty)
.Max(mt => mt.Value)}.tolist();
This builds a list object
var listOfLicense = (from l in db.License
select new
{
l.ProductId,
l.MinLicense,
l.MaxLicense
})
The above example does not build a list of objects. It builds a query to return objects of that anonymous type.
This builds an in-memory list of objects of that type:
var listOfLicense = (from l in db.License
select new
{
l.ProductId,
l.MinLicense,
l.MaxLicense
}).ToList();
Using .ToList() here will execute the query and return a materialized list of the anonymous types. From there, your code may work as expected without the exception. However, this is effectively loading the 3 columns from all rows in your database table, which may be a problem as the system matures and rows are added.
The error you are getting isn't a casting issue, it is a translation issue. Because your initial query is still just an EF Query, (IQueryable) any further querying against it will need to conform to EF limitations. EF has to be able to translate what your expressions are trying to select back into SQL. In your case, what your real code is trying to do is breaking those rules.
Generally it is better to let EF work with the IQueryable rather than materializing an entire list to memory. Though to accomplish that we'd need to either see the real code, or a minimum reproducible example.
This code:
MinQty = (int)listOfLicense
.Where(mt => (int)mt.ParentProductId == p.ProductId)
.Select(mt => (int)mt.MinLicense)
.Min(mt => mt.Value),
... does not fit with the above anonymous type as there is no correlation between what mt.ParentProductId is in relation to the anonymous type. (p seems to be associated with that type, not mt so there looks to be a lot of Query code missing from your example.)
Edit: based on your updated example:
var ShoppingCart = (from sc in db.ShoppingCarts
Select new model.Shoppingchart{
ShoppingCartId= sc.Id,
MinQty = (int)listOfLicense
.Where(mt => (int)mt.ProductId == sc.ProductId)
.Select(mt => (int)mt.MinLicense)
.Min(mt => mt.Value),
MaxQty = (int)listOfLicense
.Where(mt => (int)mt.ProductId == p.ProductId)
.Select(mt =>(int) mt.MaxQty)
.Max(mt => mt.Value)}.ToList();
It may be possible to build something like this into a single query expression depending on the relationships between ShoppingCart, Product, and Licence. It almost looks like "Licence" really refers to a "Product" which contains a min and max quantity that you're interested in.
Assuming a structure like:
public class Product
{
[Key]
public int ProductId { get; set; }
public int MinQuantity { get; set; }
public int MaxQuantity { get; set; }
// ...
}
// Here lies a question on how your shopping cart to product relationship is mapped. I've laid out a many-to-many relationship using ShoppingCartItems
public class ShoppingCart
{
[Key]
public int ShoppingCartId { get; set; }
// ...
public virtual ICollection<ShoppingCartItem> ShoppingCartItems { get; set; } = new List<ShoppingCartItem>();
}
public class ShoppingCartItem
{
[Key, Column(0), ForeignKey("ShoppingCart")]
public int ShoppingCartId { get; set; }
public virtual ShoppingCart ShoppingCart{ get; set; }
[Key, Column(1), ForeignKey("Product")]
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
With something like this, to get shopping carts with their product min and max quantities:
var shoppingCarts = db.ShoppingCarts
.Select(sc => new model.ShoppingCart
{
ShoppingCartId = sc.Id,
Products = sc.ShoppingCartItems
.Select(sci => new model.Product
{
ProductId = sci.ProductId,
MinQuantity = sci.MinQuantity,
MaxQuantity = sci.MaxQuantity
}).ToList()
}).ToList();
This would provide a list of Shopping Carts with each containing a list of products with their respective min/max quantities.
If you also wanted a Lowest min quantity and highest max quantity across all products in a cart:
var shoppingCarts = db.ShoppingCarts
.Select(sc => new model.ShoppingCart
{
ShoppingCartId = sc.Id,
Products = sc.ShoppingCartItems
.Select(sci => new model.Product
{
ProductId = sci.ProductId,
MinQuantity = sci.MinQuantity,
MaxQuantity = sci.MaxQuantity
}).ToList(),
OverallMinQuantity = sc.ShoppingCartItems
.Min(sci => sci.MinQuantity),
OverallMaxQuantity = sc.ShoppingCartItems
.Max(sci => sci.MaxQuantity),
}).ToList();
Though I'm not sure how practical a figure like that might be in relation to a shopping cart structure. In any case, with navigation properties set up for the relationship between your entities, EF should be perfectly capable of building an IQueryable query for the data you want to retrieve without resorting to pre-fetching lists. One issue with pre-fetching and re-introducing those lists into further queries is that there will be a maximum # of rows that EF can handle. Like with SQL IN clauses, there is a maximum # of items that can be parsed from a set.
In any case it sounds like it's provided you with some ideas to try and get to the figures you want.

Cannot implicit convert type List to IEnumerable while grouping with Linq

New to MVC and Linq.
I'm was able to display all records just fine but now I was trying to get a count of records by name and just select 2 fields:Name and Count
I thought I should create a new ViewModel, fill the Name,Count and send it to the view.
public ActionResult Index()
{
var load =
db.loadingPPHs.Where(s => s.WORKDAY == db.loadingPPHs.Max(x => x.WORKDAY))
.GroupBy(fu => fu.TMNAME)
.Select(g => new {Name = g.Key, Count = g.Count()}).ToList();
var viewModel = new loadingViewModel
{
LoadingListCount = load
};
return View(viewModel);
}
The linq above works as expected.
ViewModel:
public class loadingViewModel
{
public IEnumerable<LoadingListCount> LoadingListCount { get; set; }
}
public class LoadingListCount
{
public string Name{ get; set; }
public int Count { get; set; }
}
However, I'm getting an error. Cannot implicitly convert type 'System.Collections.Generic.List<>' to 'System.Collections.Generic.IEnumerable'. An explicit conversion exists (are you missing a cast?)
I have trying converting the query to list and to IEnumerable but no luck. I've searched around other posts but I have not have luck with them.
You are getting the error because of two main things, first one is the Linq produces a collection of an anonymous type, and the attached .ToList() gives you the result as List of anonymous type objects. But the expected result would be of type IEnumerable<LoadingListCount> so here we need to do few changes, First of all, we have to create an object of type LoadingListCount now the Linq will give you the output as expected, But the attached .ToList() will convert them to List<LoadingListCount> to avoid that we have to remove them as well. Finally, the query will look like the following:
var load = db.loadingPPHs.Where(s => s.WORKDAY == db.loadingPPHs.Max(x => x.WORKDAY))
.GroupBy(fu => fu.TMNAME)
.Select(g => new LoadingListCount {Name = g.Key, Count = g.Count()})
.Select(g => new {Name = g.Key, Count = g.Count()})
produces objects of anonymous type, and puts them into a list. Since you need IEnumerable<LoadingListCount>, not IEnumerable<SomeAnonymousType> create instances of LoadingListCount instead by specifying the type in the invocation of new operator:
.Select(g => new LoadingListCount {Name = g.Key, Count = g.Count()})
Your query is creating a collection of anonymous objects - you need to project your query into your model
IEnumerable<LoadingListCount> load =
db.loadingPPHs.Where(s => s.WORKDAY == db.loadingPPHs.Max(x => x.WORKDAY))
.GroupBy(fu => fu.TMNAME)
.Select(g => new LoadingListCount { Name = g.Key, Count = g.Count() })
var viewModel = new loadingViewModel
{
LoadingListCount = load
};

Passing table join results to a view?

I have the following ASP.Net MVC controller action which joins 2 tables:-
public ActionResult PersonNotes()
{
var model = db.Notes
.Join(db.People, p => p.NotesListId, n => n.NotesListId,
((note, person) => new { note, person })).ToList();
return View(model);
}
And in my view I have the following model declaration:-
#model IEnumerable<Tuple<Models.Note, Models.Person>>
I get the following error:-
System.InvalidOperationException: The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[<>f__AnonymousTypef`2[Models.Note,Models.Person]]', but this dictionary requires a model item of type System.Collections.Generic.IEnumerable`1[System.Tuple`2[Models.Note,Models.Person]]'.
I realise that I can just use a ViewModel and a Select() within my join, but it would far more convenient to just have access to all items without having to create a ViewModel.
What is the correct declaration in my view, or is what I am trying to achieve not possible this way?
You are returning an anonymous object and your view wants a model with a Tuple. The anonymous type is generated by the compiler and is not available at the source code level.
Try changing your statement to create an IEnumerable<Tuple<Models.Note, Models.Person>> with Tuple.Create:
var model = db.Notes
.Join(db.People, p => p.NotesListId, n => n.NotesListId,
((note, person) => Tuple.Create(note, person))).ToList();
See Tuple.Create.
If you are using Linq to Entities or Entity Framework then you will need to either iterate the IQueryable into a Tuple or use a class.
var model = db.Notes
.Join(db.People, p => p.NotesListId, n => n.NotesListId,
((note, person) => new { note, person }))
.AsEnumerable()
.Select(x => Tuple.Create(x.note, x.person))
.ToList();
Or
Create a class to hold the Person and Note.
public class PersonNote
{
public Person Person { get; set; }
public Note Note { get; set; }
}
Change the statement to use the new PersonNote.
var model = db.Notes
.Join(db.People, p => p.NotesListId, n => n.NotesListId,
((note, person) => new PersonNote { Note = note, Person = person }))
.ToList();
Change the model.
#model IEnumerable<PersonNote>

Entity Filter child without include

i'm a C# developer and i have a trouble with Entity Framework 5.
I have mapped my database with Entity using the default code generation strategy. In particolar there are three classes: menus, submenus and submenuitems.
The relationships about three classes are:
one menu -> to many submenus
one submenu -> to many submenuitems.
All classes have a boolean attribute called "Active".
Now, i want to filter all the Menus with the SubMenus active, and the SubMenus with the SubMenuItems active.
To get this i've tried this:
var tmp = _model.Menus.Where(m => m.Active)
.Select =>
new
{
Menu = x,
SubMenu = x.SubMenus.Where(sb => sb.Active)
.Select(y =>
new
{
SubMenu = y,
SubMenuItem = y.SubMenuItems.Where(sbi => sbi.Active)
})
})
.Select(x => x.Menu).ToList();
But didn't work.
Someone can help me?
Thank you for your help!
Hi have you see this post? Entity Framework: Querying child entities. there are some difference from your code, maybe this helps you.
UPDATE: Projection is when the result of a query is output to a different type than the one queried, it can be to an anonymous type, but could also be to a concrete type. And so using Data transfer object can be usefull to pass data between processes you can read a full explaination here Data Transfer objects
public class MenuDto
{
public int MenuId { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public List<MenuDto> SubMenus { get; set; }
}
_model.Menus.Where(m => m.Active)
.Select(p => new MenuDto
{
MenuId = p.idField,
Name = p.NameField,
Url = p.UrlField,
SubMenus = p.SubMenus.Where(sb => sb.Active)
.Select(y => new MenuDto
{
MenuId = y.idField,
Name = y.NameField,
Url = y.UrlField,
SubMenuItem = y.SubMenuItems.Where(sbi => sbi.Active)
.Select(z => new MenuDto
{
MenuId = z.idField,
Name = z.NameField,
Url = z.UrlField
})
})
}).ToList();
I hope this can solve your problem
can you try this:
List<SubMenuItems> tmp = _model.menus.Where(a => a.active)
.SelectMany(b => b.SubMenus.Where(a => a.active)).ToList()
.SelectMany(c => c.SubMenuItems.Where(a => a.active)).ToList();
I hope it's helping.

Properties stuck one level down

I am not having a problem so much with the query as accessing the data or setting it up so I can pass it to the view.
Here's the expression
var distinctReplies = pd.Project.ProjectDoc
.SelectMany(i => i.Comment
.SelectMany(k => k.CommentReply
.Select(u => u.User)
).Distinct()
).Select(g => new {FirstName = g.FirstName, LastName = g.LastName, UserID = g.UserID})
.ToList();
After this expression I want to concat it with another one that is getting values from the same user model, I want to assign distinctReplies to a ViewBag variable and then be able to loop though it and do this
foreach (var user in #ViewBag.distinctReplies) in a razor view.
However, to actually get at the values I have to do distinctReplies.Select(i => i.FirstName). Not sure how to deal with this.
I would suggest to create some ViewModel object for populating the query result. To be able to access the properties inside the #foreach loop.
Model
public class UserVM
{
public int UserID{get; set;}
public string FirstName {get; set;}
public string LastName {get; set;}
}
Updated query
var distinctReplies = ....
.Select(g => new UserVM {FirstName = g.FirstName,
LastName = g.LastName,
UserID = g.UserID}).ToList();
Then, in the view, you will need to add cast to IEnumerable<UserVM>.
View
#foreach (var user in (IEnumerable<UserVM>) ViewBag.distinctReplies)
Use this:
#foreach (var reply in (IEnumerable)ViewBag.distinctReplies)
{
}
You cannot enumerate an instance of a dynamic. Cast it to an IEnumerable for the statement to be allowed.
See this question for a more thorough explanation.

Categories