如何在构建EF查询时将WHERE条件应用于EF.Include()



我有以下两个类:

public class Rule
{
public int Id { get; set; }
public string RuleValue { get; set; }
public bool IsActive { get; set; }
public SharedRuleType RuleType { get; set; }
public List<Exclusion> Exclusions { get; set; }
}
public class Exclusion
{
public int Id { get; set; }
public int InstanceId { get; set; }
public int SiteId { get; set; }
[ForeignKey( "RuleId" )]
public int RuleId { get; set; }
public Rule Rule { get; set; }
}

然后,我有一个EF查询,它会返回"所有活动的"Rules,我需要它。为每个Rule(如果有)包括Exclusions,但仅为已分配指定InstanceIdExclusions。因此,过滤是针对Exclusions属性进行的,而不是过滤掉Rules

在建立EF查询时,我也有一些条件需要考虑。

这是我目前的疑问:

public async Task<List<Rule>> GetRules(int instanceId, SharedRuleType ruleType, string searchTerm)
{
using ( var context = new MyDbContext() )
{
var query = context.Set<Rule>()
.Include( r => r.Exclusions ) // *** Currently returns ALL exclusions but I only want ones where InstanceId == instanceId(param) ***
.Where( r => r.IsActive );
if ( !string.IsNullOrEmpty( searchTerm ) )
{
query = query.Where( r => r.RuleValue.Contains( searchTerm ) );
}
if ( ruleType != SharedRuleType.None )
{
query = query.Where( r => r.RuleType == ruleType );
}
return await query.ToListAsync();
}
}

我尝试在.Include()中应用.Where,试图只包括相关的Exclusions(基于instanceId),但发现无法做到这一点。我四处寻找,发现了一些人们使用匿名类型的例子,但在像我在这里这样逐个构建查询时,我无法做到这一点。

所以,我不知道如何才能做到这一点,因为我真的不想为每个Rule返回"每个"Exclusion,而我不需要返回每个Exclusion

Include方法不能像您尝试的那样使用筛选器。

解决方案#1

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

EF+Query IncludeFilter功能允许过滤相关实体。

public async Task<List<Rule>> GetRules(int instanceId, SharedRuleType ruleType, string searchTerm)
{
using ( var context = new MyDbContext() )
{
var query = context.Set<Rule>()
.IncludeFilter( r => r.Exclusions.Where(x => x.InstanceId == instanceId))
.Where( r => r.IsActive );
// ... code ...

Wiki:EF+Query IncludeFilter

解决方案#2

另一种技术是使用投影(这是我的图书馆在引擎盖下所做的)

public async Task<List<Rule>> GetRules(int instanceId, SharedRuleType ruleType, string searchTerm)
{
using ( var context = new MyDbContext() )
{
var query = context.Set<Rule>()
.Where( r => r.IsActive );
if ( !string.IsNullOrEmpty( searchTerm ) )
{
query = query.Where( r => r.RuleValue.Contains( searchTerm ) );
}
if ( ruleType != SharedRuleType.None )
{
query = query.Where( r => r.RuleType == ruleType );
}

// ToListAsync has been removed to make the example easier to understand
return  query.Select(x => new { Rule = x,
Exclusions = x.Exclusions.Where(e => e.InstanceId == instanceId)
})
.ToList()
.Select(x => x.Rule)
.ToList();
}
}

编辑:回答子问题#1

如何在前面的示例中使用ToListAsync

您只需等待第一个列表

return  (await query.Select(x => new { Rule = x,
Exclusions = x.Exclusions.Where(e => e.InstanceId == instanceId)
})
.ToListAsync())
.Select(x => x.Rule)
.ToList();

编辑:回答子问题#2

如何在规则上执行Skip,Take,OrderBy

你做的和你通常做一样

return  (await query.Take(15)
.Skip(5)
.OrderBy(x => x.RuleId)
.Select(x => new { Rule = x,
Exclusions = x.Exclusions.Where(e => e.InstanceId == instanceId)
})
.ToListAsync())
.Select(x => x.Rule)
.ToList();
EF团队尚未实现Conditional include这仍然是EF团队的一个工作项目,你可以在这里投票

请注意,当前无法筛选加载了哪些相关实体。Include将始终引入所有相关实体

如果你想过滤你的include语句,你需要在EF上使用投影。

using ( var context = new MyDbContext() )
{
Expression<Func<Rules, bool>> whereCondition;
if (!string.IsNullOrEmpty( searchTerm ) )
{
whereCondition= x.RuleValue.Contains(searchTerm));
}
var query = context.Rules
.Where(whereCondition)
.Select(x=> new 
{
rules = x,
exclustions = x.Exclusions.Where(secondCondition).ToList()
}.ToList();
}

如果你想让表达式像IQuerable 一样工作

你可以试试这个,但没有测试

if ( !string.IsNullOrEmpty( searchTerm ) )
{
whereCondition= x.RuleValue.Contains( searchTerm);
}
if ( ruleType != SharedRuleType.None )
{
whereCondition= x.RuleType ==ruleType;
}
//Ugly work around is 
if ( !string.IsNullOrEmpty( searchTerm ) && ruleType != SharedRuleType.None)
{
whereCondition= x.RuleValue.Contains( searchTerm) && x.RuleType ==ruleType;
}

编辑根据您的注释,您需要执行请求,为表进行LEFT JOIN。

这是你的新方法的变体

public class RuleModel
{
public Rule Rule { get; set; }
public IEnumerable<Exclusion> Exclusions { get; set; }
}   
public async Task<List<RuleModel>> GetRules(int instanceId, SharedRuleType ruleType, string searchTerm)
{
using ( var context = new MyDbContext() )
{
var query = context.Set<Rule>()
.Where( r => r.IsActive );
if ( !string.IsNullOrEmpty( searchTerm ) )
{
query = query.Where( r => r.RuleValue.Contains( searchTerm ) );
}
if ( ruleType != SharedRuleType.None )
{
query = query.Where( r => r.RuleType == ruleType );
}
// That statement do LEFT JOIN like:
// FROM  Rules
// LEFT OUTER JOIN Exclusions ON ([Rules].[Id] = [Exclusions].[RuleId]) AND ([Exclusions].[InstanceId] = @instanceId)
var ruleExclusionQuery = query.Select(rule => new RuleModel { Rule = rule, Exclusions = rule.Exclusions.Where(e => e.InstanceId == instanceId) });
var ruleList = await ruleExclusionQuery.ToListAsync();
}
}

正如您现在看到的,如果您也需要Exclusions,您不能只返回规则列表。您必须为此返回新类。并且不要从结果中使用Rule.Exclusions,因为它会向DB发出延迟请求并加载所有相关的排除。

.Include( r => r.Exclusions )

不再需要。

最新更新