C# 基于数组生成动态LINQ表达式

C# 基于数组生成动态LINQ表达式,c#,.net,linq,linq-to-sql,expression,C#,.net,Linq,Linq To Sql,Expression,我使用LINQ Exprsion查询客户并按州名称筛选他们。我有下面的查询,在我有4个项目在我的状态之前,它运行良好 public void GetCustomersForSelectedStates(string[] statesArray) { var customers = _repo.GetAllCustomers(); var filteredCustomers = from CUST in customers join ST in States on CT.T

我使用LINQ Exprsion查询客户并按州名称筛选他们。我有下面的查询,在我有4个项目在我的状态之前,它运行良好

public void GetCustomersForSelectedStates(string[] statesArray)
{
    var customers = _repo.GetAllCustomers();
    var filteredCustomers = from CUST in customers
    join ST in States on CT.Tag_Id equals ST.Id                       
    where CUST.ID == customer.ID  && (ST.Name == statesArray[0] ||ST.Name ==statesArray[1] || ST.Name== statesArray[2]||ST.Name =statesArray[3])

    //Do something with customers

}
我希望动态创建以下表达式:

(ST.Name == statesArray[0] ||ST.Name ==statesArray[1] ||    
 ST.Name== statesArray[2]||ST.Name =statesArray[3])
例如,创建如下所示的dynamicQuery

var dynamicQuery = "(";
var dynamicQuery = "(";
for (int i = 0; i < statesArray.Count(); i++)
    {
        dynamicQuery += "ST.Name =="+statesArray[0];
        if(i==statesArray.Count())
        dynamicQuery+=")"
    }

您可以添加另一个联接

var customers = _repo.GetAllCustomers();

var filteredCustomers = from CUST in customers
    join ST in States on CT.Tag_Id equals ST.Id  
    join SA in statesArray on ST.Name equals SA // No need dynamic expression now          
where CUST.ID == customer.ID 

//Do something with customers
要通过动态表达式实现这一点,基本上意味着构建以下内容的树:

(x.Foo == val0 || x.Foo == val1 || x.Foo == val2)
您可以这样做:

static Expression<Func<T, bool>> Where<T, TVal>(Expression<Func<T, TVal>> selector,
    IEnumerable<TVal> values)
{
    Expression result = null;
    foreach (var val in values)
    {
        var match = Expression.Equal(
            selector.Body,
            Expression.Constant(val, typeof(TVal)));

        result = result == null ? match : Expression.OrElse(result, match);
    }
    if (result == null) return x => true; // always match if no inputs

    return Expression.Lambda<Func<T, bool>>(result, selector.Parameters);
}
现在,作为一个单独的步骤,应用额外的过滤器:

customers = customers.Where(predicate);


这样做的目的是接受形式为
c=>c.Name
的输入lambda,然后为每个
c.Name=={val}
重用
c.Name
主体,并将
c
参数作为我们正在创建的lambda的参数。
values
中的每个值都成为一个类型化常量。
表达式.Equal
为我们提供了
=
测试。
OrElse
|
,注意如果
结果
null
,这是第一项,所以我们只使用匹配表达式本身。

为什么不直接使用
statesArray.Contains(ST.NAME)
?@JohnWu,谢谢,这项工作,使它变得不必要的复杂。但是,为了让问题保持开放(知道如何动态生成表达式),您也可以使用递归方法概念。我认为这要么失败(不好),要么通过基于_repo.GetAllCustomers()的@MarcGravel在本地执行内存连接(更差)-我们已经在本地了。但是,是的,如果我们通过EF以这种连接方式连接到DB,我们将失败;我猜回购协议有点像<代码>公共IQueryable GetAllCustomers()=>db.Customers-这并不意味着我们正在获取任何东西。另外,这个问题被标记为linq to sql,强烈地表明了这一点approach@MarcGravell这是一种我们“视情况而定”的情况。如果我们已经是本地的,那么join将是简短而简单的。在某些情况下(回购协议中的数据量很小),这很好。如果我们有EF(针对.NET的标准),我们将失败。如果我们有其他IQueryable-我们将依赖供应商。你的答案更加普遍和优雅,而且它与一个问题的关联性更好。
string[] names = { "a", "c" };
var predicate = Where<Customer, string>(c => c.Name, names);
var customers = _repo.GetAllCustomers();
var filteredCustomers = from CUST in customers
join ST in States on CT.Tag_Id equals ST.Id                       
where CUST.ID == customer.ID;
customers = customers.Where(predicate);