Почему это выражение linq не работает?

Я использую LINQ to Entities.

У меня есть таблица под названием «Студент»; у него есть идентификатор и имя в качестве столбцов. ID — это первичный ключ.

Я хотел бы иметь возможность выбрать имя студента и получить количество студентов с тем же именем.

Так, например, у меня было бы это как данные моей таблицы.

ID  Name  
1   Bob
2   Will
3   Bob

После выполнения запроса я бы вернул список объектов Student, выглядящий следующим образом.

Name    Quantity
Bob     2
Will    1

Я предполагаю, что это похоже на то, как работает страница тегов stackoverflow; У него есть название и количество.

В любом случае, я создал частичный класс под названием Student.cs, в который я добавил свойство Quantity, подобное этому.

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; }
        }
    }
}

Я придумал это, но я получаю сообщение об ошибке ..

    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;
    }

Ошибка, которую я получаю, говорит что-то вроде «Невозможно преобразовать тип Anonymous в список Student». Это как-то связано с тем, что я не распознал поле количества, которое я добавил в разделяемый класс?

Спасибо!


person hanesjw    schedule 28.01.2010    source источник
comment
Несколько общих советов: прекратите использовать var, пока не наберетесь опыта. Если бы вы попытались использовать IQueryable<Student> = (from ..., вы бы гораздо быстрее сориентировались в проблеме.   -  person    schedule 28.01.2010


Ответы (7)


Измените свой тип Student, чтобы он выглядел следующим образом:

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

И ваш запрос будет выглядеть так:

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

Ваш метод возвращает IQueryable<Student>, но в настоящее время вы возвращаете IQueryable<T> проецируемого анонимного типа.

Вам нужно провести рефакторинг вашего типа Student, чтобы иметь свойство Name типа String, а затем спроецировать новые экземпляры вашего типа Student из выражения, чтобы возвращаемый тип вашего выражения соответствовал возвращаемому типу вашего метода.

person Andrew Hare    schedule 28.01.2010
comment
Это не рефакторинг для исправления ошибки. И Student, как он определил выше, имеет имя, потому что это частичный класс, расширяющийся от его объекта Linq, который имеет имя и идентификатор. Теперь расширение Student для добавления количества определенно сомнительно, но.... - person Russell Steen; 28.01.2010
comment
Плохо ли расширять класс Student, чтобы добавить свойство количества? У меня нет столбца количества в базе данных для студента. Я хотел бы вернуть список объектов Student, чтобы в моем представлении (которое наследует объект Student) я мог легко отобразить количество. Есть ли другой/лучший способ сделать это? - person hanesjw; 28.01.2010
comment
Да, это плохая идея - я смотрел код, а не всю картину. - person Andrew Hare; 28.01.2010
comment
Тип Student действительно не должен иметь свойства количества, есть лучшее решение, но мне придется вернуться к вам! :) - person Andrew Hare; 28.01.2010
comment
Что было лучшим решением? - person Mark Byers; 29.01.2010
comment
Лучшим решением было бы создать отдельный объект, давайте назовем его NameCount и загрузим список с вашими именами и количеством. Вы, вероятно, можете придумать имя получше, и если вы делаете много разных фрагментов, вы можете сделать его более фантазийным. На самом деле все, о чем мы здесь говорим, — это поиск по словарю с ключом (Имя) и значением (Количество), которые загружаются из совокупного результата запроса к БД. - person Russell Steen; 29.01.2010

Ваша функция возвращает Студент

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

Но ваш запрос Linq возвращает новый тип, который содержит Name и Int (количество)

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

y-попробуйте выбрать нового студента {имя = g.Key, количество = g.Count()}

person Russell Steen    schedule 28.01.2010

Возвращаемое значение метода связывает коллекцию "students" с IQueryable<Student>, но... выражение Linq создает IQueryable<some anonymous type>, и между ними нет преобразования.

Вы можете сделать еще один шаг вперед, изменив выбранную часть так:

select new Student() {....}

Надеюсь, это поможет,

Тайлер

person Tyler    schedule 28.01.2010

Ключевые слова select new вызывают изменение формы данных, а это означает, что запрос LINQ вернет не IQueryable‹Student›, а скорее анонимный тип, содержащий свойства «Имя» и «Количество». Если вы измените его, чтобы он возвращал конкретный тип, а не анонимный, вы сможете получать данные в нужной вам форме.

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;
}
person Nathan Taylor    schedule 28.01.2010
comment
Не уверен, что AsQueryable() в конце будет работать? - person Nathan Taylor; 29.01.2010

Проблема в том, что вы не возвращаете студентов — вы пытаетесь вернуть анонимный тип из своей функции. Это не разрешено.

Создайте класс для представления результата и используйте в запросе new MyClass { ... } вместо new { ... }, а также измените метод, чтобы он возвращал IQueryable<MyClass> вместо IQueryable<Student>.

Например, вы можете создать класс с именем StudentNameAndResults.

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

В качестве альтернативы вы можете просто вернуть результат в виде словаря или IEnumarable из IGrouping. Например:

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);
}

Кроме того, свойство, которое вы создали, использует подробный синтаксис, существовавший до появления C# 3.0. Теперь вы можете использовать автоматически реализуемые свойства, если вам не нужно какая-то специальная логика:

public int Quantity { get; set; }
person Mark Byers    schedule 28.01.2010

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

Это анонимный тип, а не IQueryable<Student>. Либо вам нужно вернуть System.Object, либо вам нужно вернуть IQueryable<Student> следующим образом...

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

Где Student определяет свойства, используемые при инициализации.

person Paul Creasey    schedule 28.01.2010
comment
Хотя вы можете вернуть анонимный тип как System.Object, это мало поможет, потому что вы не сможете использовать поля, если не приведете их к правильному подтипу, а вы не знаете, что это за тип. Я бы не рекомендовал этого делать. - person Mark Byers; 28.01.2010

Вы делаете проекцию в своем запросе linq. Если вы наведете курсор на var students внутри vs, вы увидите, что это коллекция анонимного типа.

Если вы хотите вернуть IQueryabley<Student>, вам нужно сделать:

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

Внешние методы никак не могут узнать об анонимном типе, созданном вами в предыдущем примере, поэтому вы не сможете вернуть типизированную коллекцию.

С помощью метода, который я предложил, вы все равно сможете сделать проекцию возвращаемого значения вашего метода позже, поскольку IQueryable компонуется до первого перечисления:

var students = FindStudentsDistinctWithQuantity();
var namesAndQunatity = from s in students select new {s.Name, s.Quantity};
person Johannes Rudolph    schedule 28.01.2010
comment
Я думаю, проблема в том, что у Student нет Quantity в базе данных, и он пытается добавить его в класс и установить внутри этого запроса. Поэтому, если бы вы воспользовались своим предложением, я бы предположил, что Quantity всегда будет установлено на 0. - person Mark Byers; 28.01.2010