C# Linq to 实体 - 如何仅选择具有数组中所有引用的记录



我有 2 个实体:

Students
Id     Name
1      John Doe
2      Jack Daniels
3      Peter Green
Languages
Language    Student    Result
English     1          A
Spanish     2          A
Italian     2          B
English     3          B
Spanish     1          A

我有一个查询,要按语言结果选择学生

string[] langIds = new string[] { "English", "Italian" };
var result = (from students in context.Students
where students.Languages.Where(s => langIds.Contains(s.Language)).Count > 0
orderby students.Languages.Where(s => langIds.Contains(s.Language)).Sum(m => m.Result) descending
select students);

该查询为我提供了按语言结果总和排序的学生列表。

但是此列表包括从指定数组中至少通过一种语言结果的学生,我只需要选择那些从该数组中获得所有语言结果的学生。

任何建议如何解决问题?

学生和语言之间似乎存在多对多关系:每个学生说零种或多种语言,每种语言都由零名或多名学生使用。

如果您遵循了实体框架编码约定,您将拥有类似于以下内容的内容:

class Student
{
public int Id {get; set;}
... // other properties
// Every student speaks zero or more Languages (many-to-many)
public virtual ICollection<Language> Languages {get; set;}
}
class Language
{
public int Id {get; set;}
... // other properties
// Every Language is spoken by zero or more Students(many-to-many)
public virtual ICollection<Student> Students {get; set;}
}

为了完整起见,数据库上下文:

class MyDbContext : DbContext
{
public DbSet<Student> Students {get; set;}
public DbSet<Language> Languages {get; set;}
}

由于您坚持约定,因此实体框架会检测多对多关系。您不必为此指定联结表,可以改用virtual ICollection

给我所有在对象 LangIds 中至少会说所有语言的学生

每当要检查集合 A 是否至少包含集合 B 中的所有元素时,请考虑检查B.Except(A).Any()。如果为 true,那么显然 B 中的某些元素不在 A 中。

因此,如果LangIds.Except(Student.Languages).Any()是真的,那么你就知道有些语言不是学生说的。你不想要这个学生。你只希望那些学生 任何结果都是假的。

一旦你知道这一点,代码就非常简单了:

var result = dbContext.Students
.Where(student => !langIds.Except(student.Languages).Any() // note the "!"
.ToList();

这可能会获取比您实际计划使用的属性更多的属性,因此请考虑添加 Select:

var result = dbContext.Students
.Where(student => !langIds.Except(student.Languages).Any()
.Select(student => new
{
// Select only the Student properties that you plan to use
Id = student.Id,
Name = student.Name,
...
// Only if you plan to use all languages that the Student speaks:
Languages = student.Languages.Select(language => new
{
// again: only the language properties that you plan to use:
Id = language.Id,
Code = language.Code,
Abbreviation = language.Abbreviation,
...
// no need to fill language.Students
})
.ToList(),
});

Jonas Høgh在评论中陈述的绝对简单明了的解决方案: 而不是

where students.Languages.Where(s => langIds.Contains(s.Language)).Count > 0

where students.Languages.Where(s => langIds.Contains(s.Language)).Count == langIds.Length

非常感谢你,乔纳斯

最新更新