为什么这个LINQ表达式不能工作?

5

我正在使用LINQ to Entities。

我有一张名为Student的表格,它有ID和Name两列。ID是主键。

我想选择学生的姓名,并得到具有相同姓名的学生数量。

例如,我的表格数据如下所示:

ID  Name  
1   Bob
2   Will
3   Bob
执行查询后,我将返回一个类似于以下的学生对象列表。
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;
    }
我收到的错误信息大致是“无法将匿名类型转换为学生列表类型”。这是否与我在部分类中添加的数量字段未被识别有关呢? 谢谢!

一些通用建议:在您获得更多经验之前,请停止使用var。如果您尝试使用IQueryable<Student> = (from ...,您会更快地找到问题所在。 - user24359
7个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
4

将您的学生类型更改为以下内容:

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类型的实例,以便表达式的返回类型与方法的返回类型相匹配。


修复错误并不是“重构”。而且,正如他在上面定义的那样,学生有一个名称,因为它是扩展自他的 Linq 对象的部分类,该对象具有名称和 ID。现在将学生扩展以添加数量绝对是值得怀疑的,但是... - Russell Steen
将Student类扩展以添加数量属性是一个不好的想法吗?我在数据库中没有数量列与学生相关。我想返回一个Student对象列表,这样在我的视图中(继承了Student对象),我可以轻松地显示数量。有没有不同/更好的方法来实现它? - hanesjw
是的,那是个糟糕的想法 - 我只看了代码,没有看整个情况。 - Andrew Hare
“Student”类型真的不应该有一个“quantity”属性,但是我会想出更好的解决方案并回复你! :) - Andrew Hare
什么是更好的解决方案? - Mark Byers
一个更好的解决方案是创建一个单独的对象,我们称之为“NameCount”,并加载一个包含名称和计数的列表。你可能可以想出一个更好的名称,如果你正在做很多不同的切片,你可以变得更加花哨。实际上,我们在这里谈论的只是一个字典查找,具有一个键(名称)和一个值(计数),它是从数据库查询的聚合返回中加载的。 - Russell Steen

2

问题在于你没有返回学生 - 你试图从函数返回匿名类型,这是不允许的。

创建一个类来表示你的结果,并在查询中使用 new MyClass { ... } 而不是 new { ... },并将该方法更改为返回 IQueryable<MyClass> 而不是 IQueryable<Student>

例如,你可以创建一个名为 StudentNameAndResults 的类。

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

或者,您可以将结果返回为字典或IGrouping的IEnumarable。例如:

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

2
select new 关键字会改变数据的形式,这意味着 LINQ 查询将不会返回一个 IQueryable<Student>,而是一个包含 "Name" 和 "Quantity" 属性的匿名类型。如果你把它改成返回一个具体的类型而不是匿名类型,你就能以你想要的形式检索数据了。
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;
}

不确定末尾的 AsQueryable() 是否有效? - Nathan Taylor

2
你的函数返回一个学生对象。
public IQueryable<Student> FindStudentsDistinctWithQuantity(){ ... }
但是您的Linq查询返回了一个新类型,其中包含名称和整数(计数)。
               >>> select new {Name = g.Key, Quantity = g.Count()});            

y-try选择新的学生{Name = g.Key,数量= g.Count()}

(注意:这句话可能需要根据上下文进行更准确的翻译)

2
该方法的返回值将“students”集合与IQueryable<Student>绑定,但是… Linq表达式正在创建一个IQueryable<some anonymous type>,两者之间没有转换。您可以通过修改选择部分来取得一小步进展:
select new Student() {....}
希望这能有所帮助, 泰勒

1
 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 定义了在初始化中使用的属性。


虽然你可以将匿名类型作为System.Object返回,但这并没有什么帮助,因为你不能使用字段,除非你将其强制转换为正确的子类型,而你不知道那个类型是什么。我不建议这样做。 - Mark Byers

1

你正在进行一个linq查询中的投影。如果你在vs中将光标悬停在var students上,你会看到它是一个匿名类型的集合。

如果你想返回一个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};

我认为问题在于数据库中的Student没有Quantity,他试图将其添加到类中并在此查询中设置它。因此,如果您使用了您的建议,我猜Quantity将始终被设置为0。 - Mark Byers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,