首页 > 解决方案 > 条件过滤 IQueryable通过表达式和第二个对象

问题描述

我想要实现的是IQueryable<T>在我的表达式选择类型对象的地方创建一个自定义扩展方法AgentEntity,第二个参数属于同一类型,但用于进行条件过滤。

这是我的代码,它不起作用,但建议我想做什么。

public static IQueryable<T> Where<T>(this IQueryable<T> profiles, Func<AgentEntity, AgentEntity> agentSelector, AgentEntity agent)
{
    if (string.IsNullOrEmpty(agent.Mbox))
    {
        return profiles.Where(agentSelector.Mbox == agent.Mbox);
    }

    if (string.IsNullOrEmpty(agent.Mbox_SHA1SUM))
    {
        return profiles.Where(agentSelector.Mbox_SHA1SUM == agent.Mbox_SHA1SUM);
    }

    if (string.IsNullOrEmpty(agent.OpenId))
    {
        return profiles.Where(agentSelector.OpenId == agent.OpenId);
    }

    if (string.IsNullOrEmpty(agent.Account.HomePage))
    {
        return profiles.Where(agentSelector.Account.HomePage == agent.Account.HomePage && agentSelector.Account.Name == agent.Account.Name);
    }

    return profiles;
}

用法

AgentEntity agent = new AgentEntity(){
  Mbox = "mailto:lorem@example.com"
}
_dbContext.OtherEntity.Where(x=> x.Agent, agent);
_dbContext.ThirdEntity.Where(x=> x.Object.Agent, agent);

如何转换agentSelector为以下表达式x=> x.Mbox == agent.Mbox或其他条件之一,以在 Where 子句中使用来过滤配置文件。

profiles.Where条款预计Expression<Func<T, bool>> predicate

更新

在测试下面的答案后,我发现 EntityFramework 无法将以下表达式转换为 SQL。并抛出以下错误:

The LINQ expression 'Where<AgentEntity>(\r\n    source: DbSet<AgentEntity>, \r\n    predicate: (a) => (int)Invoke(__agentSelector_0, a[AgentEntity])\r\n    .ObjectType == (int)(Unhandled parameter: __agent_ObjectType_1))' could not be translated.
Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

Summary:
### Explicit client evaluation
You may need to force into client evaluation explicitly in certain cases like following

The amount of data is small so that evaluating on the client doesn't incur a huge performance penalty.
The LINQ operator being used has no server-side translation.
In such cases, you can explicitly opt into client evaluation by calling methods like `AsEnumerable` or `ToList` (`AsAsyncEnumerable` or `ToListAsync` for async). 
By using `AsEnumerable` you would be streaming the results, but using ToList would cause buffering by creating a list, which also takes additional memory. Though if you're enumerating multiple times, then storing results in a list helps more since there's only one query to the database. Depending on the particular usage, you should evaluate which method is more useful for the case.

标签: c#entity-frameworklinq

解决方案


可以IQueryable使用该Where方法过滤。此方法还返回一个IQueryable,因此,如果您愿意(我经常这样做),您可以将它们链接在一起以进行多次过滤 - 我发现这会导致代码更具可读性,并且您还可以在这些过滤器之间分支代码(以添加条件是否你过滤与否)。这可能看起来像这样(未经测试的代码):

IQueryable<Foo> foos = _dbContext.Foos;
foos = foos.Where(f => f.Bar == myBar);

if(!string.IsNullOrNothing(myBaz)){
    foos = foos.Where(f => f.Baz == myBaz)
}

因此,在这段代码中,Foo对象集总是在其Bar属性等​​于时进行过滤myBar,但第二次过滤仅在为空且不myBar为空时应用(注意使这些不是,这是 dotNet 的一件事,两者都认为是您的原始代码中缺少)!

现在让我尝试将其应用于您尝试创建的扩展方法。复杂之处在于,有不同的映射可以从OtherEntityorThirdEntity和 the中获取,AgentEntity并且我们想使用 aFunc<T, AgentEntity>来定义该映射(请注意,我们是从泛型类型 'T' 映射的)

public static IQueryable<T> Where<T>(this IQueryable<T> profiles, Func<T, AgentEntity> mapping, AgentEntity agent)
{
    if (!string.IsNullOrEmpty(agent.MBox))
    {
        profiles = profiles.Where(p => mapping(p).MBox == agent.MBox);
    }

    return profiles;
}

请注意,我们使用传入的映射函数将每个配置文件转换为我们用于过滤器的代理。它就像您原来的问题一样被调用:

_dbContext.OtherEntity.Where(x=> x.Agent, agent);

另请注意,在函数结束之前我不会返回 - 这可能是也可能不是你实际想要的 - 一旦你找到一个可以过滤的标准,你可能真的想返回!


推荐阅读