我发现join
运算符does not allow the use of Contains
以及only performs equijoins。但是,我需要执行“不等分”。
我特别需要使用以下设置编写查询。给定两种类型的对象Class
和Student
public class Class
{
public string Name { get; set; } = "";
public List<Guid> Students { get; set; } = new List<Guid>();
}
public class Student
{
public Guid StudentId { get; set; } = Guid.NewGuid();
public int Grade { get; set; } = 0;
}
Class
通过其Student
引用其StudentId
的地方。我想写一个子句来查找所有Class
的平均成绩都高于某个值的所有Student
。
class Program
{
static void Main(string[] args)
{
// Create all of the students
var class1Students = new List<Student>()
{
new Student() {Grade = 70 },
new Student() {Grade = 70 }
};
var class2Students = new List<Student>()
{
new Student() {Grade = 80 },
new Student() {Grade = 80 }
};
var class3Students = new List<Student>()
{
new Student() {Grade = 90 },
new Student() {Grade = 90 }
};
var allStudents = new List<Student>();
allStudents.AddRange(class1Students);
allStudents.AddRange(class2Students);
allStudents.AddRange(class3Students);
// Create all of the classes
var class1 = new Class()
{
Name = "Class1",
Students = class1Students.Select(s => s.StudentId).ToList()
};
var class2 = new Class()
{
Name = "Class2",
Students = class2Students.Select(s => s.StudentId).ToList()
};
var class3 = new Class()
{
Name = "Class3",
Students = class3Students.Select(s => s.StudentId).ToList()
};
var allClasses = new List<Class>() { class1, class2, class3 };
// Get all classes where the average grade is above 70
var query = from cls in allClasses
join std in allStudents on
}
}
我想这样写查询
var query = from cls in allClasses
join std in allStudents on cls.Students.Contains(std.StudentId) into clsStds
where clsStds.Select(aStd => aStd.Grade).Average() > 70
select cls;
尽管这显然是无效的语法。 page linked above提供了一个非等值连接的示例,尽管我试图在此处应用它,但似乎无法正确地复制它(和/或我已经严重混淆了自己)。
如何模拟上面描述的join
的类型?
答案 0 :(得分:6)
首先,您的数据模型错误。学生没有一个年级。 他们在一个班级中有一个成绩,而您的模型不占该成绩。您需要第三个表,其中包含学生,班级和年级列。我强烈建议您解决此问题。
解决上述问题很简单,但是我不喜欢到目前为止提出的任何解决方案。它们基本上是合理的,但可能会更加高效和强大。
您遇到的基本问题是:您没有从学生ID到学生对象的快捷简便的方法。 首先解决该问题:
var idToStudent = allStudents.ToLookup(s => s.id);
太好了。现在解决方案很简单:
var query =
from cls in allClasses
let grades = from id in cls.Students select idToStudent(id).Grade
where grades.Any()
where grades.Average() > 70
select cls;
请注意,我们正在测试是否有Any
年级,因为可能有一个班级没有学生。如果Average
被要求平均取零物品,则会崩溃,因此进行检查是明智的。
修复数据模型以正确关联学生,班级和成绩后,它将是:
var query =
from cls in allClasses
let grades =
from grade in allGrades
where grade.Class == cls
select grade.grade
where grades.Any()
where grades.Average() > 70
select cls;
在一组经过适当设计的表格中,计算平均值时不会将学生ID纳入其中;您可以直接关联成绩和班级。
现在,通过指出您需要一种C#不支持的联接来开始这个问题。不,您需要修复数据关系,然后C#支持您需要的那种连接!上面的内容可以更有效地写为
var query =
from cls in allClasses
join g in allGrades on cls equals g.Class into grades
where grades.Average() > 70
select cls;
由于C#不会产生空组,因此检查“任何”的需求消失了。
这就是您需要的联接;正确设计表格,然后使用它!
答案 1 :(得分:1)
您使用的是Entity Framework还是类似的?实体之间是否具有导航属性?如果是这样,也许您可以GroupBy
在学生身上,然后导航到父对象。像这样:
var query = allStudents
.GroupBy(i=>i.Class)
.Select(i=>new{
Class = i,
Average = i.Average(j=>j.Grade)
})
.Where(i=>i.Average > 70)
.ToList();
答案 2 :(得分:0)
我找到了一种天真的解决方案。首先,我们将学生中的Guid
引用“解析”为实际的Student
对象
var clsStd =
from cls in allClasses
select new
{
cls,
stdObjs = allStudents.Where(aStd => cls.Students.Contains(aStd.StudentId))
};
然后我们可以查询该匿名对象集合
var classes =
from clsStdObj in clsStd
where clsStdObj.stdObjs.Select(stdObj => stdObj.Grade).Average() > 70
select clsStdObj.cls;
然后解析为“平均学生成绩高于70的所有班级的集合”。
但是我仍然对不太天真的解决方案持开放态度。
答案 3 :(得分:0)
这可能是一个解决方案(感谢@EricLippert提供有关使用Any()
的建议,以避免在classGrades
为空时可能发生的崩溃):
var query = from _class in allClasses
let classGrades = from std in allStudents
where _class.Students.Contains(std.StudentId)
select std.Grade
where classGrades.Any()
where classGrades.Average() > 70
select _class;
基本上,对于每个Class
,我都会创建一个新的子查询,在其中选择该Student
中的所有Class
,并且仅投影Grade
,因此结果是IEnumerable<string>
。下一部分很容易:在年级上Average()
。
出于好奇,我已经使用Stopwatch
来比较您的解决方案与我的解决方案,即使它只是指示性的,延迟时间之间的比率(我/您的)约为0.02。