I\'ve been wondering about this one for a while now, so I thought it would be worth using my first Stack Overflow post to ask about it.
Imagine I have a discussion with
I know this is an old question but it seems to be an ongoing problem and none of the answers above provide a good way to deal with SQL aggregates in list views.
I am assuming straight POCO models and Code First like in the templates and examples. While the SQL View solution is nice from a DBA point of view, it re-introduces the challenge of maintaining both code and database structures in parallel. For simple SQL aggregate queries, you won't see much speed gain from a View. What you really need to avoid are multiple (n+1) database queries, as in the examples above. If you have 5000 parent entities and you are counting child entities (e.g. messages per discussion), that's 5001 SQL queries.
You can return all those counts in a single SQL query. Here's how.
Add a placeholder property to your class model using the [NotMapped]
data annotation from the System.ComponentModel.DataAnnotations.Schema
namespace. This gives you a place to store the calculated data without actually adding a column to your database or projecting to unnecessary View Models.
...
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace MyProject.Models
{
public class Discussion
{
[Key]
public int ID { get; set; }
...
[NotMapped]
public int MessageCount { get; set; }
public virtual ICollection Messages { get; set; }
}
}
In your Controller, get the list of parent objects.
var discussions = db.Discussions.ToList();
Capture the counts in a Dictionary. This generates a single SQL GROUP BY query with all parent IDs and child object counts. (Presuming DiscussionID
is the FK in Messages
.)
var _counts = db.Messages.GroupBy(m => m.DiscussionID).ToDictionary(d => d.Key, d => d.Count());
Loop through the parent objects, look up the count from the dictionary, and store in the placeholder property.
foreach (var d in discussions)
{
d.MessageCount = (_counts.ContainsKey(d.ID)) ? _counts[d.ID] : 0;
}
Return your discussion list.
return View(discussions);
Reference the MessageCount
property in the View.
@foreach (var item in Model) {
...
@item.MessageCount
...
}
Yes, you could just stuff that Dictionary into the ViewBag and do the lookup directly in the View, but that muddies your view with code that doesn't need to be there.
In the end, I wish EF had a way to do "lazy counting". The problem with both lazy and explicit loading is you're loading the objects. And if you have to load to count, that's a potential performance problem. Lazy counting wouldn't solve the n+1 problem in list views but it sure would be nice to be able to just call @item.Messages.Count
from the View without having to worry about potentially loading tons of unwanted object data.
Hope this helps.