views:

183

answers:

7

I'm using LINQ to Entities.

I have a table called Student; it has ID and Name as it's columns. ID is a primary key.

I'd like to be able select the name of the Student and get the amount of Students with the same Name.

So for example I'd have this as my table data.

ID  Name  
1   Bob
2   Will
3   Bob

After performing the query I'd return a List of Student objects looking like this.

Name    Quantity
Bob     2
Will    1

I guess it is kind of similar to how the Tags page of stackoverflow works; It has the name and the quantity.

Anyways, I created a partial class called Student.cs in which I added a Quantity property like this.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace MySite.Models
{
    public partial class Student
    {
        private int _quantity;

        public int Quantity
        {
            get { return _quantity; }
            set { _quantity = value; }
        }
    }
}

I came up with this but I'm getting an error..

    public IQueryable<Student> FindStudentsDistinctWithQuantity()
    {
        /*SELECT Name, COUNT(Name) AS Quantity
        FROM Student
        GROUP BY Name*/

        var students= (from s in db.Students
                    group s by s.Name into g
                    select new {Name = g.Key, Quantity = g.Count()});            

        return students;
    }

The error i'm getting says something like Can't convert from type Anonymous to the Student list. Does it have something to do with it not recognizing the quantity field I added in the partial class?

Thanks!

+2  A: 

The problem is that you are not returning students - you are trying to return an anonymous type from your function. This is not allowed.

Create a class to represent your result and use new MyClass { ... } instead of new { ... } in your query, and change the method to return IQueryable<MyClass> instead of IQueryable<Student>.

You could for example make a class called StudentNameAndResults.

class StudentNameAndResults
{
    public string Name { get; set; }
    public int Quantity { get; set; }
}

Alternatively, you could just return the result as a Dictionary or an IEnumarable of IGrouping. For example:

public IDictionary<string, int> FindStudentsDistinctWithQuantity()
{
    Database db = new Database();
    var students= (from s in db.Students
                group s by s.Name into g
                select new {Name = g.Key, Quantity = g.Count()});

    return students.ToDictionary(s => s.Name, s => s.Quantity);
}

Also, the property you created is using the verbose syntax from pre-C# 3.0 days. Now you can use auto-implemented properties if you don't need any special logic:

public int Quantity { get; set; }
Mark Byers
+4  A: 

Change your Student type to look like this:

public partial class Student
{
    public Int32 Quantity { get; set; }
    public String Name { get; set; }
}

And your query to look like this:

var students = from s in db.Students
               group s by s.Name into g
               select new Student { 
                   Name = g.Key, 
                   Quantity = g.Count() };

Your method returns an IQueryable<Student> but you are currently returning an IQueryable<T> of a projected anonymous type.

You need to refactor your Student type to have a Name property of type String and then project new instances of your Student type from the expression so that the return type of your expression will match the return type of your method.

Andrew Hare
It's not "refactoring" to fix a bug. And Student as he has it defined above has a Name, because it's a partial class extending from his Linq object which has Name and ID. Now extending Student to add a quantity is definitely questionable but....
Russell Steen
Is it a bad idea to extend the Student class to add a quantity property? I don't have a quantity column in the database for student. I'd like to return a list of Student objects so in my View (which inherits the Student object) I could easily display the Quantity. Is there a different/better way of doing it?
hanesjw
Yes, that is a bad idea - I was looking at the code and not the whole picture.
Andrew Hare
The `Student` type really shouldn't have a quantity property, there is a better solution but I will have to get back to you! :)
Andrew Hare
Great, thanks for the help!
hanesjw
What was the better solution?
Mark Byers
A better solution would be to create a separate object, lets call it "NameCount" and to load a list that with your names and counts. You can probably come up with a better name, and if you're doing a lot of different slices, you can get more fancy with it. Really, all we are talking about here is a dictionary lookup, with a key (Name) and a value (Count) that is being loaded from the aggregate return of a db query.
Russell Steen
+1  A: 
 var students= (from s in db.Students
                    group s by s.Name into g
                    select new {Name = g.Key, Quantity = g.Count()}); 

This is an anonymous type, not IQueryable<Student>. Either you need to return System.Object, or you need to return IQueryable<Student> as follows...

return from s in db.Students
       group s by s.Name into g
      select new Student{Name = g.Key, Quantity = g.Count()};

Where Student defines the properties used in the initializtion.

Paul Creasey
While you can return an anonymous type as System.Object, it doesn't help much because you can't use the fields unless you cast it to the correct subtype, and you don't know what that type is. I wouldn't recommend doing this.
Mark Byers
+2  A: 

The select new keywords are causing the form of the data to change, which means the LINQ query will not return an IQueryable<Student>, but rather an anonymous type containing the "Name" and "Quantity" properties. If you change it to return a concrete type rather than an anonymous one you will be able to retrieve the data in the form you want.

public class StudentGrouping {
    public string Name { get; set; }
    public int Quantity { get; set; }
}

public IQueryable<StudentGrouping> FindStudentsDistinctWithQuantity()
{
    /*SELECT Name, COUNT(Name) AS Quantity
    FROM Student
    GROUP BY Name*/

    var students= (from s in db.Students
                group s by s.Name into g
                select new StudentGrouping {
                   Name = g.Key, 
                   Quantity = g.Count()
                }).AsQueryable();            

    return students;
}
Nathan Taylor
Not sure about that AsQueryable() on the end- will that work?
Nathan Taylor
+2  A: 

Your function returns Student

public IQueryable<Student> FindStudentsDistinctWithQuantity(){ ... }

But your Linq query returns a new type that contains a Name and an Int (count)

               >>> select new {Name = g.Key, Quantity = g.Count()});            

y-try select new Student{Name = g.Key, Quantity = g.Count()}

Russell Steen
+1  A: 

You are doing a projection in your linq query. If you'd hover the cursor over var students inside vs you'll see its a collection of an anonymous type.

If you want to return an IQueryabley<Student> you need to do:

 var students= from s in db.Students
                    group s by s.Name into g
                    select s.Key; 

There is no way outside methods can know about the anonymous type you have created in your previous example, so you won't be able to return a typed collection.

With the method I suggested you will still be able to do a projection on the return value of your method later on, since IQueryable is composable until the first enumeration:

var students = FindStudentsDistinctWithQuantity();
var namesAndQunatity = from s in students select new {s.Name, s.Quantity};
Johannes Rudolph
I think the problem is that the `Student` doesn't have a `Quantity` in the database and he's trying to add it to the class and set it inside this query. So if you used your suggestion, I would guess that `Quantity` would always be set to 0.
Mark Byers
+2  A: 

The method's return value ties the "students" collection to IQueryable<Student> but... the Linq expression is creating an IQueryable<some anonymous type>, and there is no conversion between the two.

You may get a baby step further by modifying your select part to be:

select new Student() {....}

Hopefully this helps,

Tyler

Tyler