Entity Framework过滤嵌套集合

3
我有一个实体关系图,如下所示。
ClassEntity:
public int id
public int std
public virtual ICollection<StudentEntity> students

"StudentEntity":
public int id
public string name
public string gender
public virtual ClassEntity class
public virtual StudentAddressEntity studentAddress

学生地址实体:
public int id
public string address

我需要获取该类及其男性子级。
var classEntity = dbContext.Set<ClassEntity>().Where(t => t.id == classId);
var query = classEntity.Include(c => c.students.Select(s => s.studentAddress))
           .FirstOrDefault(c => c.students.Any(s => s.gender == GenderEnum.Male));

但它返回了所有学生的班级。如何筛选出只有男性学生?

1
可能是如何过滤嵌套集合实体框架对象?的重复问题。 - DavidG
是的,我已经参考过了。但它并没有给我一个答案。 - Praveen Prasannan
在EF Core 5中支持:https://dev59.com/7FcP5IYBdhLWcg3w6-Op#61147681 - Gert Arnold
4个回答

3

过去我曾使用连接(joins)来实现类似的结果。例如,我有一个嵌套地址(1:M)的账户。如果我想要获取属于特定国家的所有账户,我会像下面这样使用连接:

(from a in accountRepo.GetAll()
      join aa in accountAddressRepo.GetAll() on a.AccountId equals aa.AccountId
      join ad in addressRepo.GetAll() on aa.AddressId equals ad.AddressId
      where ad.CountryId == codeCountryId
      select a).ToList();

如果您没有使用仓库模式,您可以简单地将accountRepo.GetAll()替换为DbContext.Set()。
在您的情况下,您应该能够加入学生、地址和班级实体并获得类似的结果。以下内容应该适用于您:
(from s in DbContext.Set<StudentEntity>
  join a in DbContext.Set<StudentAddressEntity> on s.studentAddress.id equals a.id
  join c in DbContext.Set<ClassEntity> on s.class.id equals c.id
  where c.std == classId && s.gender== GenderEnum.Male
  select s).ToList();

请注意,这只是根据我对您的数据库和实体名称的理解所做的简单表示。您可能需要微调此查询,以使其可以编译,但基本思想应该适用于您。请告诉我它对您有什么作用。


3

您无法直接使用 EF 代理进行此操作。例如,考虑在调用 SaveChanges() 时,ClassEntity.Students 中所有女学生都消失了会发生什么情况!

如果您只是显示数据,则通常的做法是投影到匿名类型或 DTO 上,例如:

var classOnlyMale = dbContext.Set<ClassEntity>()
    .Where(x => x.Id == classId)
    .Select(x => new // I'm using an anonymous type here, but you can (and usually should!) project onto a DTO instead
    {
        // It's usually best to only get the data you actually need!
        Id = x.Id
        Students = x.Students
            .Where(y => y.Gender == GenderEnum.Male)
            .Select(y => new { Name = y.Name, ... })
    });

或者,如果你迫切需要进行更改并保存它们:

var classOnlyMale = dbContext.Set<ClassEntity>()
    .Where(x => x.Id == classId)
    .Select(x => new
    {
        Class = x,
        MaleStudents = x.Students.Where(y => y.Gender == GenderEnum.Male)
    });

我强烈建议采用前者,除非没有其他选择。如果您对过滤后的数据进行更改并尝试保存,很容易引入错误。


2
以下应该仅加载每个班级的男学生。
var classEntity = testContext.Set<ClassEntity>().Where(t => t.Id == classId);
var classes = classEntity.ToList().Select(c =>
{
    testContext.Entry(c)
    .Collection(p => p.Students)
    .Query()
    .Where(s => s.Gender == GenderEnum.Male)
    .Load();

    return c;
});

请记住,这种方法将在客户端过滤学生,所有学生仍将通过数据库传输,然后才会选择男性。 - Bradley Uffner
根据http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx的说法,这似乎是在数据库端应用的。 - Chris Wyatt
调用 .ToList 将始终将实体置于本地。对 .ToList 结果执行的任何操作都不会在数据库上执行。我已经扫描了您链接的页面,但我不知道您具体指的是哪个部分。如果我对此行为有误,我很想知道。 - Bradley Uffner
啊,没关系,我最开始读你的代码的时候理解错了。你是对的,学生操作将在服务器端进行。尽管它会为每个班级执行单独的学生查询。我道歉。 - Bradley Uffner
虽然这是一个公正的观点。重新阅读我的代码,看起来这将执行 N 个单独的查询,在其他情况下并不理想。在这种情况下,t.Id 可能是唯一的,并且类应该只包含 0 或 1 个元素,因此在这种情况下是可以接受的。 - Chris Wyatt

0

我认为加入是正确的方法,正如Manish Kumar所建议的那样。

(from s in DbContext.Set<StudentEntity>
  join a in DbContext.Set<StudentAddressEntity> on s.studentAddress.id equals a.id
  join c in DbContext.Set<ClassEntity> on s.class.id equals c.id
  where c.std == classId && s.gender== GenderEnum.Male
  select s).ToList();

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