在EF Include中过滤

时间:2017-12-01 09:43:30

标签: entity-framework linq

我有这个LINQ查询,它在Include中的过滤器上给出了错误。 在搜索我的朋友Google时,我发现无法在“包含”中进行过滤。我已经找到了一些方法来实现这个目的,但我不能让它适用于我的具体案例。

return context.Timesheets.Where(t => t.UserId == userId && t.SubjectDate == date && t.WorkingDaySchedules.Count() > 0)
    .Include(t => t.Project)
    .Select(t => t.Project)
        .Include(p => p.Account)
        .Include(pc => pc.ProjectConsultants.Where(c => c.UserId == userId));

这是让我头疼的最后一个包括:) 有人知道怎么做吗?

2 个答案:

答案 0 :(得分:1)

我觉得有些事情需要改进。

包含不适用于过滤

这不是正确的地方。

Include旨在自动检索所有链接的实体。由于您不希望所有实体(您只想要一个子集),因此您不应该使用Include。 或者,您仍然可以使用Include,只要您乐意删除不需要的条目在内存中(即在加载后)。但我认为你不想要那个。

相反,您可以使用显式Select语句。举个简单的例子:

context.Projects
             .Where(p => p.Id == projectId)
             .Select(p => new ConsultantSearchResult() {
                   Project = p,
                   ConsultantsNamedBob = p.Consultants.Where(c => c.FirstName == "Bob")
              }).ToList();

请注意缺少Include 。正如我之前所说,Include用于自动(并隐式)加载相关数据。但是因为你明确地Select中陈述了你想要的数据,所以不再需要隐式包含。 EF将为您提供您所要求的。

您的Select不直观

我认为你期待的不同于你所获得的东西。看代码:

return context.Timesheets    //1
    .Where(...)              //2
    .Select(t => t.Project)  //3

看看会发生什么:

  1. 您选择所有时间表。
  2. 您过滤时间表并留下时间表的子集
  3. 您会看到每个时间表的项目列表。
  4. 如果您的过滤(第2步)为您提供了来自同一项目的多个时间表,那么.Select(t => t.Project)将为您提供同一项目的多个实例。那并不好。

    这里有两个例外:

    • 知道您将总共找到一张时间表。但是,您应该使用FirstSingleFirstOrDefaultSingleOrDefault。如果您获得多个结果,则应该只使用Where
    • 您需要多个时间表,但是知道您永远不会从同一个项目中找到两个时间表(因此在调用Select时永远不会创建重复项)。我假设(通过阅读实体名称)特定顾问可能有多个时间表用于同一个项目,但可能这不是真的。
      • 如果我的推断是正确的,那么在执行Select之后,您将遇到重复项目的问题。
      • 如果我的推断不正确,那么我希望时间表和顾问之间的关系更加紧密,因为每个项目顾问都会有1个(或没有)时间表,从不超过1个。但是您当前的数据结构缺乏任何真实的时间表和顾问之间的关系。

    快速解决方案是使用Distinct

    return context.Timesheets    
        .Where(...)              
        .Select(t => t.Project)  
        .Distinct()
    

    但我个人认为更好的解决方案是反转查询:从项目开始,在他们的时间表上过滤项目(而不是过滤时间表):

    return context.Projects    
        .Include(p => p.Timesheets)  
        .Where(p => p.Timesheets.Any(t => t.UserId == userId && ...))              
        .ToList();  
    

    这排除了重复项目的问题。请注意,这还没有解决您的过滤包含"问题

    在同一上下文中单独查询

    评论中也提到了这一点。这是一个可行的选择,但我发现它是一种肮脏的方法,会在线下创建不直观的代码。

    A simple example

    context.Configuration.LazyLoadingEnabled = false;
    var parent = context.Set<Entity>().First(e => e.Name = "ABC");
    // Load relations in separate query
    context.Set<Child>()
           .Where(c => c.Parent.Name == "ABC")
           .OrderBy(c => c.Name) // You can at least try it but as mentioned above it may not work in all scenarios
           .Load();
    // Now parent.Children collection should be filled 
    

    该示例使用OrderBy代替Where,但两者的工作方式相同。

    即使您分别查询了孩子和父母,他们的导航属性也会不断更新,因为您在同一个环境中运行查询。

    这对你来说是一个可行的选择,但是我对这段代码感到有点担心,因为第二个查询改变第一个查询的结果绝不是可读
    对我来说,这对于在属性的getset中拥有业务逻辑。它有效,但它会导致意外的行为,并且会使调试变得非常困难。

    请注意,您可能很清楚 幕后发生的事情,但是在查看代码时,不同的开发人员很容易掩饰它。

    我个人不喜欢这样,但你的意见可能有所不同。

    您的不完整数据结构使其变得复杂。

    查看您的代码示例,我认为您的数据一致性存在一些问题。您在两个地方使用userId过滤器:

    • 时间表:t => t.UserId == userId
    • 顾问:c => c.UserId == userId

    如果时间表与顾问相关联,那么这两个实体之间应该存在关系。按照目前的情况,您的项目有一份时间表列表和一份顾问列表,时间表和顾问之间没有明显的关系。
    这就是您的查询复杂的原因。你试图嘲笑那里没有的关系。

    如果这种关系确实存在,那么查找所有内容会容易得多:

    return context.Timesheets
                       .Include(t => t.Project)
                       .Include(t => t.Project.Account)
                       .Include(t => t.Consultant)
                       .Where(t => t.Consultant.UserId == userId && t.SubjectDate == date && t.WorkingDaySchedules.Count() > 0)
                       .ToList()
    

    然后你得到了你正在寻找的东西。您不再需要执行两次单独的userId检查,您不再需要&#34;手动同步&#34;假的关系,查找过程更加简化和可读。

    次要评论

    也许你还不知道的事情。你可以重写

    t.WorkingDaySchedules.Count() > 0
    

    作为

    t.WorkingDaySchedules.Any() //is there at least one item in the collection?
    

    如果您需要,可以添加过滤器的附加好处:

    t.WorkingDaySchedules.Any(wds => wds.IsActive)  //is there at least one item in the collection that meets the condition?
    

答案 1 :(得分:0)

有两种方法可以过滤包含实体。

  • 使用投影(参见@Flater答案)
  • 使用第三方库

免责声明:我是该项目的所有者Entity Framework Plus

EF + Query IncludeFilter允许轻松过滤包含的实体。

return context.Timesheets.Where(t => t.UserId == userId && t.SubjectDate == date && t.WorkingDaySchedules.Count() > 0)
    .Include(t => t.Project)
    .Select(t => t.Project)
        .IncludeFilter(p => p.Account)
        .IncludeFilter(pc => pc.ProjectConsultants.Where(c => c.UserId == userId));

在引擎盖下,图书馆完全是一个投影。

一个限制是全部Include,但现在即使未指定过滤器(例如帐户),也会使用IncludeFilter进行调用。

维基:EF+ Query Include Filter