【问题标题】:Entity Framework filter nested collection实体框架过滤器嵌套集合
【发布时间】:2016-02-15 02:04:20
【问题描述】:

我有一个实体关系图如下。

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

StudentAddressEntity:

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));

但它正在与所有学生一起返回课堂。如何只过滤男学生?

【问题讨论】:

标签: c# entity-framework linq entity-framework-6


【解决方案1】:

下面应该只加载每个班级的男学生。

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;
});

【讨论】:

  • 请记住,此方法将在客户端过滤学生,在只选择男性之前,所有学生仍会从数据库中获取。
  • 根据blogs.msdn.com/b/adonet/archive/2011/01/31/… 看来这是在数据库端应用的。
  • 调用.ToList 将始终将实体带到本地。对.ToList 的结果执行的任何操作都不会在数据库上发生。我已经扫描了您链接的页面,但我不知道您具体指的是哪个部分。如果我对这种行为有误,我很想知道。
  • 啊,没关系,我最初读错了你的代码。您是正确的,学生操作将发生在服务器端。虽然它会为每个班级做一个单独的学生查询。我很抱歉。
  • 这是一个公平的观点。重新阅读我自己的代码,看起来这将执行 N 个单独的查询,这在其他情况下并不理想。在这种情况下,t.Id 可能是唯一的,并且类应该只包含 0 或 1 个元素,因此在这种情况下它是可以接受的。
【解决方案2】:

您故意“不能”直接使用 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)
    });

我强烈推荐前者,除非没有办法。如果您对过滤后的数据进行更改并尝试保存它,那么很容易引入错误。

【讨论】:

    【解决方案3】:

    我过去曾使用联接来完成类似的结果。例如,我的帐户具有嵌套的地址 (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()。

    在您的情况下,您应该能够加入 Student、Address 和 Class 实体并获得类似的结果。像下面这样的东西应该适合你:

    (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();
    

    请注意,这是基于我对您的数据库和实体名称的理解的简单表示。您可能需要稍微调整此查询以使其可编译,但基本思想应该适合您。请让我知道它是如何为您工作的。

    【讨论】:

      【解决方案4】:

      我认为加入是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();
      

      【讨论】:

        猜你喜欢
        • 2011-10-28
        • 2011-10-20
        • 2016-12-27
        • 1970-01-01
        • 1970-01-01
        • 2017-08-03
        • 2018-03-18
        • 2018-05-17
        • 2014-07-16
        相关资源
        最近更新 更多