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

Sim*_*ons 5 .net c# linq expression linq-to-sql

我正在使用LINQ exprssion来查询客户并按州名称过滤它们。我有以下查询,在我的statesArray中有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

}
Run Code Online (Sandbox Code Playgroud)

我想动态创建以下表达式:

(ST.Name == statesArray[0] ||ST.Name ==statesArray[1] ||    
 ST.Name== statesArray[2]||ST.Name =statesArray[3])
Run Code Online (Sandbox Code Playgroud)

例如,创建如下的dynamicQuery

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

然后像下面这样使用它,

//Psuedo code
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  && Expression(dynamicQuery)
Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 4

To do that via dynamic expressions basically means building a tree of:

(x.Foo == val0 || x.Foo == val1 || x.Foo == val2)
Run Code Online (Sandbox Code Playgroud)

You can do that like this:

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);
}
Run Code Online (Sandbox Code Playgroud)

with example usage:

string[] names = { "a", "c" };
var predicate = Where<Customer, string>(c => c.Name, names);
Run Code Online (Sandbox Code Playgroud)

You can then use this predicate in the IQueryable<T>.Where extension method.

To combine it in your case, first do your regular LINQ:

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;
Run Code Online (Sandbox Code Playgroud)

Now as a separate step apply the extra filter:

customers = customers.Where(predicate);
Run Code Online (Sandbox Code Playgroud)

What this does is accept an input lambda of the form c => c.Name, then reuses the c.Name body for each c.Name == {val}, and reuses the c parameter as the parameter for the lambda we're creating. Each value from values becomes a typed constant. The Expression.Equal gives us the == test. The OrElse is the ||, noting that if result is null, this is the first item, so we just use the match expression itself.