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

5

我有以下两个类:

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查询来获取所有“活动的”规则,并且我需要为每个规则(如果有).Include 排除项,但仅包括已分配指定实例ID排除项。因此,过滤是针对排除项属性进行的,而不是过滤掉规则

在构建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时。

2
有时候执行正确的 SQL 更容易。 - Callum Linington
@VinodKumar 不,还没有。我认为Vitaliy在下面提供的解决方案不会起作用,因为它不会返回“所有”规则,它已经根据排除条件过滤了一些规则。 - marcusstarnes
有一次我在看这个。我模糊地记得我认为它是不可能的,但无法引用来源。也许最好通过SQL或单独查询加载数据。在我的情况下,编写另一个查询以满足条件的相关实体是可以的。 - Stilgar
3个回答

2

Include 方法无法像您尝试的那样使用过滤器。

解决方案 #1

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

EF+ 查询 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 ...

维基: EF+ 查询 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();

感谢提供的解决方案。有一个问题,对于第二种解决方案,我该如何将 .Skip、.Take 和 .OrderBy 结合到查询中,以便在一次访问数据库时执行筛选操作,因为事实证明我需要这样做。 - marcusstarnes
另外,我该如何使用Await / Async实现解决方案#2? - marcusstarnes
您想在哪个实体(规则或排除)上执行跳过、取出和排序操作? - Jonathan Magnan
规则。我总是希望返回每个规则的所有筛选排除项,但我可能一次只请求10个规则(为跳过和获取提供偏移量和限制)。如果这10个规则中有一个规则有50个筛选排除项,我将希望获取所有50个排除项。我还将传递某些东西(枚举)来标识要应用的请求排序顺序。 - marcusstarnes
谢谢,正是我所需要的。 - marcusstarnes

0

编辑 根据您的评论,您需要执行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();
    }
}

如你现在所见,如果你需要排除的规则,就不能只返回“Rule”列表。你必须返回一个新类。而且不要从结果中使用“Rule.Exclusions”,因为它会向数据库发出延迟请求并加载所有相关的排除内容。
.Include( r => r.Exclusions )

不再需要了。


这难道不会过滤掉我的某些规则吗(只返回与实例匹配的排除项的规则)?我需要“所有”规则,无论它们是否有排除项(以及它们是什么)。只需过滤包含的排除项(如果有)。 - marcusstarnes
@marcusstarnes 我更新了代码。请你检查一下是否适合你。 - Vitaliy Smolyakov

0

EF团队尚未实现“条件包含”功能。这仍然是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;
    }

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