如何将 LINQ 用于循环内的多个过滤器?

How to use LINQ for multiple filters inside a loop?

我正在使用 linq 来过滤我的数据,即如果数据是数字类型,我必须使用比较器,否则我需要字符串匹配。

即使我的 linq 查询在循环内,我目前也只能使用一个过滤器。

List<(string col, string opr, string value)> ps = new List<(string col, string opr, string value)>();
Func<ShoeModels, string, string, float, bool> comparer = (ShoeModels a, string column, string op, float value) =>
{
    switch (op)
    {
        case "lt": return a[column] < value;
        case "gt": return a[column] > value;
        case "gte": return a[column] >= value;
        case "lte": return a[column] <= value;
        case "ne": return a[column] != value;
        default:
            break;
    }
    return true;
};
for(i=5;i<query.count;i++)
{
    if (ps[i].value.All(char.IsDigit))
    {
        query.numValue = float.Parse(ps[i].value);
    }
    else
    {
        query.filterValue = ps[i].value;
    }
    query.filterColumn = ps[i].col;
    query.opr = ps[i].opr;
    query.opr = query.opr.ToLower();
    IEnumerable<ShoeModels> res = null;

    if (query.opr == "gt" || query.opr == "lt" || query.opr == "gte" || query.opr == "lte" || query.opr == "ne")
    {
        IEnumerable<ShoeModels> tuyo = from p in loopData.Where(a => comparer(a, query.filterColumn, query.opr, query.numValue))
        select p;
        res = tuyo.ToList();
    }
    else if (query.opr.ToLower() == "like")
    {
        IEnumerable<ShoeModels> tuyo = from p in loopData.Where(a => a[query.filterColumn].Contains(query.filterValue))
        select p;
        res = tuyo.ToList();
    }
    finalResult = res.ToList();
}

正如您在代码中看到的那样,我使用 LHS 方括号运算符来过滤我的数据。现在,如果我在比较之前使用我的 like 运算符,我会得到一个异常 "float does not contain definition for Contains"。 所以我通过阅读延迟执行然后在每次执行后将 ToList() 添加到结果来解决这个问题。所以现在有一个问题,每当我尝试过滤代码时,只有最后一个过滤器有效,但所有过滤器都应该有效。

您只有最后一个过滤器起作用,因为您在 for 循环的每次迭代中将过滤器应用于 loopData

from p in loopData.Where()...

因此,每次您过滤初始 loopData 而不是上次过滤产生的数据。

您需要将每次迭代的初步过滤结果保存在一些其他变量中,这些变量应该在 for 循环之外声明(在我的代码示例中,这个变量是 res 变量)并应用每次迭代中该 res 变量的其他过滤器。

List<ShoeModels> res = loopData;   // here I declare and assign res variable, it should be outside of the loop

for (int i = 5; i < query.count; i++)
{
    if (ps[i].value.All(char.IsDigit))
    {
        query.numValue = float.Parse(ps[i].value);
    }
    else
    {
        query.filterValue = ps[i].value;
    }
    query.filterColumn = ps[i].col;
    query.opr = ps[i].opr;
    query.opr = query.opr.ToLower();
    IEnumerable<ShoeModels> tuyo = null;

    if (query.opr == "gt" || query.opr == "lt" || query.opr == "gte" || query.opr == "lte" || query.opr == "ne")
    {
        // apply filtering to the res variable
        tuyo = res.Where(a => comparer(a, query.filterColumn, query.opr, query.numValue));
    }
    else if (query.opr.ToLower() == "like")
    {
        // apply filtering to the res variable
        tuyo = res.Where(a => a[query.filterColumn].Contains(query.filterValue));
    }
    // update res variable, so on the next iteration we will operate on updated (filtered) list
    res = tuyo.ToList();
}

使用 float 来表达价格或金钱是一种不好的做法。使用 decimal 类型。

我在 LinqPad5 中创建了一个演示(如果您不了解 LinqPad,请安装并使用它 - 它非常有用,尤其是对于编写 LINQ 查询):

void Main()
{
    var ps = new List<(string col, string opr, string value)>();
    ps.Add(("0", "gt", "6"));
    ps.Add(("1", "like", "30.0"));
    ps.Add(("2", "ne", "60"));

    var loopData = new List<ShoeModels> {
        new ShoeModels { Range = new[] { 5.0m, 10.0m, 15.0m }}, // leaves out on first filter ps[0]
        new ShoeModels { Range = new[] { 10.0m, 20.0m, 30.0m }},// leaves out on second filter ps[1]
        new ShoeModels { Range = new[] { 10.0m, 30.0m, 30.0m }},// this is the final result of all three filters
        new ShoeModels { Range = new[] { 15.0m, 30.0m, 60.0m }},// leaves out on third filter ps[2]
    };

    loopData.Dump("initial data");

    List<ShoeModels> res = loopData;

    var query = new Query();
    for (int i = 0; i < ps.Count; i++)
    {
        decimal number;
        if (Decimal.TryParse(ps[i].value, out number))
        {
            query.numValue = number;
        }
        else
        {
            query.filterValue = ps[i].value;
        }
        query.filterColumn = ps[i].col;
        query.opr = ps[i].opr.ToLower();
        IEnumerable<ShoeModels> tuyo = null;

        if (query.opr == "gt" || query.opr == "lt" || query.opr == "gte" || query.opr == "lte" || query.opr == "ne")
        {
            tuyo = res.Where(a => comparer(a, query.filterColumn, query.opr, query.numValue));
        }
        else if (query.opr.ToLower() == "like")
        {
            tuyo = res.Where(a => (a[query.filterColumn]).ToString(CultureInfo.InvariantCulture).Contains(query.filterValue));
        }
        res = tuyo.ToList();
        res.Dump("after " + i + " iteration");
    }
    res.Dump("final result");
}

private Func<ShoeModels, string, string, decimal, bool> comparer = (ShoeModels a, string column, string op, decimal value) =>
{
    switch (op)
    {
        case "lt": return a[column] < value;
        case "gt": return a[column] > value;
        case "gte": return a[column] >= value;
        case "lte": return a[column] <= value;
        case "ne": return a[column] != value;
        default:
            break;
    }
    return true;
};

class Query {
    public decimal numValue { get; set;}
    public string filterValue { get; set;}
    public string filterColumn {get; set;}
    public string opr {get; set;}
}

class ShoeModels {  
    public decimal[] Range = new decimal[3];
    public decimal this[string index] {
        get => Range[int.Parse(index)];
    }

    public override string ToString() => 
        $"{Range[0].ToString(CultureInfo.InvariantCulture)} " +
        $"{Range[1].ToString(CultureInfo.InvariantCulture)} " +
        $"{Range[2].ToString(CultureInfo.InvariantCulture)}";
}