C# C LINQ反复执行相同的工作
遇到一些遗留代码,其中逻辑试图阻止对昂贵的查询GetStudentsOnCourse进行不必要的多次调用,但由于对延迟执行的误解而失败C# C LINQ反复执行相同的工作,c#,linq,C#,Linq,遇到一些遗留代码,其中逻辑试图阻止对昂贵的查询GetStudentsOnCourse进行不必要的多次调用,但由于对延迟执行的误解而失败 var students = studentsToRemoveRecords.Select(x => x.CourseId) .Distinct() .SelectMany(c => studentRepository.GetStudentsOnCourse(c.Valu
var students = studentsToRemoveRecords.Select(x => x.CourseId)
.Distinct()
.SelectMany(c => studentRepository.GetStudentsOnCourse(c.Value));
var studentsToRemove = new List<Student>();
foreach (var record in studentsToRemoveRecords)
{
studentsToRemove.Add(
students.Single(s => s.Id == record.StudentId));
}
在这里,如果studentsToRemoveRecords中有两条相同课程的记录,那么不需要使用相同的课程id调用两次查询GetStudentsOnCourse,而不是一次
您可以通过提前将学生转换为列表并强制将其存储在内存中以防止延迟执行来解决此问题。或者简单地把逻辑改写成更简单的东西
但我后来意识到,我实际上很难用语言确切解释为什么在上述场景中两次调用GetStudentsOn课程。。。是不是每次迭代studentsToRemoveRecords时LINQ都在重复相同的工作,即使每次生成的输入值都相同
是不是每次迭代studentsToRemoveRecords时LINQ都在重复相同的工作,即使每次生成的输入值都相同
是的,这就是林克的本性。某些VisualStudio扩展(如ReSharper)在创建可能导致LINQ查询多次迭代的代码时会发出警告
如果要避免,请执行以下操作:
var students = studentsToRemoveRecords.Select(x => x.CourseId)
.Distinct()
.SelectMany(c => studentRepository.GetStudentsOnCourse(c.Value))
.ToList();
使用ToList,查询立即执行,结果实体存储在列表中。现在,您可以对学生进行多次迭代,而不会出现性能问题
编辑以包含评论:
下面是一些关于它的好文档的链接谢谢你Sergio:
关于您的问题,以及如何在大型代码库中处理此问题的一些想法:
这两种情况都有原因——直接执行并将结果存储到新列表中,以及延迟执行。
如果您熟悉SQL数据库,您可以考虑像视图或存储过程这样的LINQ查询。您可以定义要在基表上执行的筛选/更改,以获取结果实体。每次查询该视图/执行该存储过程时,它都基于基表中的当前数据运行
林克也是。没有ToList的查询就像视图的定义一样。每次迭代时,该定义都会根据studentsToRemoveRecords中当前的实体执行。
也许那是你的遗言。也许您知道这个基本列表正在改变,并且您希望多次执行查询,期望得到不同的结果。那就不用托利斯特了
但是,如果您只想执行一次查询,然后希望得到一个可以多次迭代的不可变结果列表,请使用ToList
所以这两种情况都是有效的。当您只迭代一次时,这两种情况都是相同的免责声明:当您在定义查询后直接迭代时。也许这就是你这么多次看到它的原因。这取决于你想要什么。不清楚你的课程是如何完成的,但是:
public class Student
{
public int Id { get; set; }
}
public class StudentCourse
{
public int StudentId { get; set; }
public int? CourseId { get; set; }
}
public class StudentRepository
{
public StudentCourse[] StudentCourses = new[]
{
new StudentCourse { CourseId = 1, StudentId = 100 },
new StudentCourse { CourseId = 2, StudentId = 200 },
new StudentCourse { CourseId = 3, StudentId = 300 },
new StudentCourse { CourseId = 4, StudentId = 400 },
};
public Student[] GetStudentsOnCourse(int courseId)
{
Console.WriteLine($"{nameof(GetStudentsOnCourse)}({courseId})");
return StudentCourses.Where(x => x.CourseId == courseId).Select(x => new Student { Id = x.StudentId }).ToArray();
}
}
然后
static void Main(string[] args)
{
var studentRepository = new StudentRepository();
var studentsToRemoveRecords = studentRepository.StudentCourses.ToArray();
var students = studentsToRemoveRecords.Select(x => x.CourseId)
.Distinct()
.SelectMany(c => studentRepository.GetStudentsOnCourse(c.Value));
//.ToArray();
var studentsToRemove = new List<Student>();
foreach (var record in studentsToRemoveRecords)
{
studentsToRemove.Add(
students.Single(s => s.Id == record.StudentId));
}
}
然后做:
.SelectMany(c => studentRepository.GetStudentsOnCourse(c.Value))
.DebugEnumeration();
这将在枚举SelectMany时向您显示。我看不出在此查询中每个课程id如何多次调用GetStudentsOnCourse。你核实过情况吗?是ReSharper调用的itIt不会被调用两次,它可能会被称为studentsToRemoveRecords.Count*studentsToRemoveRecords.Distinct.Count,因此示例中给出了两条记录的lile x^2,在那个场景中,它被调用了两次是的-用一个test@FBryant87尝试增加记录的数量…文档到延迟和中间执行方法,非常有用。事实上,这对我来说是一个很大的打击,多年来我显然误解了DE的一部分。所以每次我们迭代一个IEnumerable时,LINQ都会重复它执行的工作,以在每次迭代中获得该集合?人们是如何处理这一点的,通过添加。托利斯在无数的地方?谢谢你让我大开眼界,至少我见过的每一个代码库似乎都误解了这一点。@FBryant87我又写了一些关于何时使用ToList和何时不使用ToList的句子。这两种情况都是有效的,非常好,谢谢。
.SelectMany(c => studentRepository.GetStudentsOnCourse(c.Value))
.DebugEnumeration();