C# DbQuery在foreach循环中的行为不同。为什么?

C# DbQuery在foreach循环中的行为不同。为什么?,c#,entity-framework,linq-to-entities,C#,Entity Framework,Linq To Entities,如果我使用以下代码,我会得到学习课程1和课程2的学生列表。这几乎就是我想要的 IQueryable filteredStudents=context.Students; filteredStudents=filteredStudents .Wheres=>s.Courses.Selectc=>c.CourseID.Contains1; filteredStudents=filteredStudents .Wheres=>s.Courses.Selectc=>c.CourseID.Contains

如果我使用以下代码,我会得到学习课程1和课程2的学生列表。这几乎就是我想要的

IQueryable filteredStudents=context.Students; filteredStudents=filteredStudents .Wheres=>s.Courses.Selectc=>c.CourseID.Contains1; filteredStudents=filteredStudents .Wheres=>s.Courses.Selectc=>c.CourseID.Contains2; List studentList=filteredStudents.ToList; 但是,如果我尝试在foreach循环中这样做,如下面的代码所示,那么我会得到一个列表,其中列出了所有注册学习循环中最后一门课程的学生

IQueryable filteredStudents=context.Students; 过滤器中的每个课程。课程{ 如果课程!=null{ filteredStudents=filteredStudents .Wheres=>s.Courses.Selectc=>c.CourseID.containsUsers.CourseID; } } List studentList=filteredStudents.ToList;
这种行为使我困惑。有人能解释为什么会这样吗?如何绕过它?谢谢。

因为filteredStudents=filteredStudents。在哪里。。。是对变量的直接赋值,每次通过循环,您都会完全替换以前的变量。您需要附加到它,而不是替换它。尝试搜索c AddRange

如果您试图获取在筛选器中的每门课程中注册的每个学生。课程,您可以尝试:

var courseIDs=filter.Courses.Selectc=>c.CourseID; var filteredStudents=context.Students .Where=>!courseIDs.Excepts.Courses.Selectc=>c.CourseId.Any 它根据CourseID是否是学生课程ID的一部分进行过滤

编辑


并给出一个很好的解释,解释为什么上一门课的所有学生都被检索到。

我认为这与实体框架无关。这不是一个真正的错误,而是c语言中一个愚蠢的设计,变量在循环外声明

在这种情况下,这意味着由于IEnumerable是惰性计算的,因此它将使用变量的最后一个值。在循环中使用一个临时变量来解决它

foreach (Course course in filter.Courses) {
    if (course != null) {
        var cId = course.CourseID;       
        filteredStudents = filteredStudents
            .Where(s => s.Courses.Select(c => c.CourseID).Contains(cId))
                .Select(s => s);
    }
}
如果正确定义了导航属性,则效果更好。只要做:

var studentList = filter.Courses.SelectMany(c => c.Students).ToList()

请参阅此处的更多信息:

问题在于foreach循环仅为所有循环迭代创建一个单独的过程变量,然后将该单独变量捕获到闭包中。还要记住,过滤器直到循环结束后才真正执行。将它们放在一起,当过滤器执行此单个课程变量时,它已前进到课程过滤器中的最后一项;你只检查最后一道菜

我认为有四种方法可以解决这个问题

第一 为循环的每个迭代创建一个新变量这可能是最好的快速修复方法

IQueryable<Student> filteredStudents = context.Students;
foreach (Course course in filter.Courses) {
    if (course != null) {  
        int CourseID = course.CourseID;            
        filteredStudents = filteredStudents
            .Where(s => s.Courses.Select(c => c.CourseID).Contains(CourseID));
    }
}
List<Student> studentList = filteredStudents.ToList<Student>();
或者以更可读的方式:

IQueryable<Student> filteredStudents = context.Students;
var courses = filter.Courses.Select(c => c.CourseID);
var studentList = filteredStudents
       .Where(s => s.Courses.Select(c => c.CourseID)
                       .Intersect(courses)
                       .Any()
       ).ToList();
如果您稍微考虑一下这个问题,那么通过巧妙地在内部使用hashset,或者(如果您非常幸运的话)通过向数据库发送连接查询,性能应该达到或远远超过foreach循环。只是要小心,因为这里很容易写一些东西,对Intersect或任何方法中的DB进行大量额外调用。即使如此,这也是我倾向于选择的,除了我可能不会费心打电话给的例外。最后是ToList

这也说明了为什么我对EntityFramework、LINQtoSQL、甚至NHibernate或ActiveRecord等ORM没有太多的用处。如果我只是在编写SQL,我就知道我得到了正确的联接查询。我也可以用ORM实现这一点,但现在我仍然需要知道我正在创建的特定SQL,并且我还必须知道如何让ORM实现我想要的功能

第四
使用C 5.0,因此for/foreach循环的每次迭代都是它自己的变量。

顺便说一句,.Selects=>s是不需要的,Student已经被default选中了。我刚刚尝试了第三种方法,但它没有给出期望的结果。它返回在筛选器中有任何课程的学生。我希望它返回所有课程都在过滤器中的学生。
var studentList = context.Students.Where(s => s.Courses.Select(c => c.CourseID).Intersect(filter.Courses.Select(c => c.CourseID)).Any()).ToList();
IQueryable<Student> filteredStudents = context.Students;
var courses = filter.Courses.Select(c => c.CourseID);
var studentList = filteredStudents
       .Where(s => s.Courses.Select(c => c.CourseID)
                       .Intersect(courses)
                       .Any()
       ).ToList();