【问题标题】:Want to convert c# multiple Linq into one Linq query?想要将 c# 多个 Linq 转换为一个 Linq 查询?
【发布时间】:2020-01-22 14:52:37
【问题描述】:

我编写了包含多个 if 条件的代码,并从每个 if 语句中获取了 Id 列表。如果可能的话,我想将整个代码写入一个 linq 查询,然后告诉我。

问题是当我们运行这段代码时,可以看到我们的编译器在输出窗口上运行了数百行查询。我只想优化。如果你们能帮助我。 详细代码如下:

if (queryObj.AgeCategory.Contains((int)AgeList._0to5))
{
    var list = queryRecord.Where(v => v.BuildYear <= yearNow && v.BuildYear > year5).Select(s => s.Id);
    Ids = Ids.Concat(list);
}
if (queryObj.AgeCategory.Contains((int)AgeList._6to10))
{
    var list = queryRecord.Where(v => v.BuildYear <= year5 && v.BuildYear > year10).Select(s => s.Id);
    Ids = Ids.Concat(list);
}
if (queryObj.AgeCategory.Contains((int)AgeList._11to15))
{
    var list = queryRecord.Where(v => v.BuildYear <= year10 && v.BuildYear > year15).Select(s => s.Id);
    Ids = Ids.Concat(list);
}
if (queryObj.AgeCategory.Contains((int)AgeList._16to20))
{
    var list = Ids.Concat(queryRecord.Where(v => v.BuildYear <= year15 && v.BuildYear > year20).Select(s => s.Id));
    Ids = Ids.Concat(list);
}
if (queryObj.AgeCategory.Contains((int)AgeList._21to25))
{
    var list = Ids.Concat(queryRecord.Where(v => v.BuildYear <= year20 && v.BuildYear > year25).Select(s => s.Id));
    Ids = Ids.Concat(list);
}
if (queryObj.AgeCategory.Contains((int)AgeList._26to30))
{
    var list = Ids.Concat(queryRecord.Where(v => v.BuildYear <= year25 && v.BuildYear > year30).Select(s => s.Id));
    Ids = Ids.Concat(list);
}
if (queryObj.AgeCategory.Contains((int)AgeList._31to35))
{
    var list = Ids.Concat(queryRecord.Where(v => v.BuildYear <= year30 && v.BuildYear > year35).Select(s => s.Id));
    Ids = Ids.Concat(list);
}
if (queryObj.AgeCategory.Contains((int)AgeList._36to40))
{
    var list = Ids.Concat(queryRecord.Where(v => v.BuildYear <= year35 && v.BuildYear > year40).Select(s => s.Id));
    Ids = Ids.Concat(list);
}
if (queryObj.AgeCategory.Contains((int)AgeList._Over41Years))
{
    var list = Ids.Concat(queryRecord.Where(v => v.BuildYear <= year40).Select(s => s.Id));
    Ids = Ids.Concat(list);
}
queryRecord = queryRecord.Where(v => Ids.Contains(v.Id));

我使用以下代码进行了测试,但由于同一列它满足第一个条件。

                    Ids = queryRecord.Where(st => ((queryObj.AgeCategory.Contains((int)AgeList._11to15)) ? st.BuildYear <= year10 && st.BuildYear > year15 : true) &&
                        ((queryObj.AgeCategory.Contains((int)AgeList._6to10)) ? st.BuildYear <= year5 && st.BuildYear > year10 : true))
                        .Select(s => s.Id);

【问题讨论】:

  • if..else if... 替换if 语句怎么样?
  • @UweKeim 不,实际上我不能,因为这两个条件都可能满足并且还需要其他条件记录。
  • @canton7 你上面提到的这个参考与我无关。
  • 那是真实的代码吗?每个条件都会导致相同的操作:收集 ID。只需将代码简化为单个选择查询。

标签: c# performance entity-framework linq output-window


【解决方案1】:

让我们从 Marc 的 AndAlso 方法从 this linked answer 开始,将 AndAlso 更改为 OrElse

public static Expression<Func<T, bool>> OrElse<T>(
    this Expression<Func<T, bool>> expr1,
    Expression<Func<T, bool>> expr2)
{
    var parameter = Expression.Parameter(typeof(T));

    var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
    var left = leftVisitor.Visit(expr1.Body);

    var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
    var right = rightVisitor.Visit(expr2.Body);

    return Expression.Lambda<Func<T, bool>>(
        Expression.OrElse(left, right), parameter);
}

private class ReplaceExpressionVisitor
    : ExpressionVisitor
{
    private readonly Expression _oldValue;
    private readonly Expression _newValue;

    public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
    {
        _oldValue = oldValue;
        _newValue = newValue;
    }

    public override Expression Visit(Expression node)
    {
        if (node == _oldValue)
            return _newValue;
        return base.Visit(node);
    }
}

我们可以这样写:

Expression<Func<Whatever, bool>> query = t => false;

if (queryObj.AgeCategory.Contains((int)AgeList._0to5))
{
    query = query.OrElse(v => v.BuildYear <= yearNow && v.BuildYear > year5);
}
if (queryObj.AgeCategory.Contains((int)AgeList._6to10))
{
    query = query.OrElse(v => v.BuildYear <= year5 && v.BuildYear > year10);
}
... and so on...

queryRecord = whatever.Where(query);

这会生成一个查询,如下所示:

WHERE (BuildYear <= yearNow AND BuildYear > year5) OR (BuildYear < year5 AND BuildYear > year10) etc

【讨论】:

    【解决方案2】:

    假设您所指的AgeList 是这样的枚举:

    public enum AgeList
    {
        _0to5   = 0,
        _6to10  = 1,
        _11to15 = 2,
        _16to20 = 3,
        _21to25 = 4,
        _26to30 = 5,
        _31to35 = 6,
        _36to40 = 7,
    }
    

    我认为您想将BuildYears 映射到上述其中之一,我们可以观察到它们成对出现。 所以我们可以这样做:

    var yearNow = 2020;
    var buildYear = 2019;
    
    var ageGroupNumber = (yearNow - buildYear) / 5; // 0
    var ageGroup = (AgeList) ageGroupNumber;        // _0to5 
    

    那么我们可以做如下的事情吗?

    Ids = queryObj.AgeCategory
        .SelectMany(category => // For every category :
            queryRecord.Where(v => // Select the items in this category :
                (DateTime.Now.Year - v.BuildYear) / 5 == (int) category 
            )
            .Select(s => s.Id)
        );
    

    我不知道这是否会对您有所帮助,或者我是否正确理解了您的问题,但也许这会 给你一些想法。

    【讨论】:

      【解决方案3】:

      另一种解决方案是创建一个转换器,将BuildAge 转换为年龄类别。这样您就可以简单地让 LINQ 进行过滤:

      private void FilterQueryRecord(IEnumerable<Record> queryRecord, ? queryObj)
      {
        this.Ids = queryRecord
          .Where(item => queryObj.AgeCategory.Contains(ConvertBuildYearToAgeCategory(item.BuildYear)))
          .Select(item => item.Id)
          .ToList();
      }
      
      private int ConvertBuildYearToAgeCategory(int buildYear) 
      {
        switch (buildYear)
        {
          case int value when value <= yearNow && value > year5: return (int) AgeList._0to5;
          case int value when value <= year5 && value > year10: return (int) AgeList._6to10;
          case int value when value <= year10 && value > year15: return (int) AgeList._11to15;
          case int value when value <= year15 && value > year20: return (int) AgeList._16to20;
          case int value when value <= year20 && value > year25: return (int) AgeList._21to25;
          case int value when value <= year25 && value > year30: return (int) AgeList._26to30;
          case int value when value <= year30 && value > year35: return (int) AgeList._31to35;
          case int value when value <= year35 && value > year40: return (int) AgeList._36to40;
          case int value when value < year40: return (int) AgeList._Over41Years;
          default: return (int) AgeList.Undefined
        }
      }
      

      【讨论】:

        【解决方案4】:

        我只是在每种情况下都添加了 false 并添加 ||在每个语句之间并修复此问题。

        Ids = queryRecord.Where(st => 
                            ((queryObj.AgeCategory.Contains((int)AgeList._11to15)) ? st.BuildYear <= year10 && st.BuildYear > year15 : false) ||
                            ((queryObj.AgeCategory.Contains((int)AgeList._6to10)) ? st.BuildYear <= year5 && st.BuildYear > year10 : false)
                            ).Select(s => s.Id);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-06-08
          • 2020-12-10
          • 2013-05-25
          • 1970-01-01
          • 2021-11-29
          • 1970-01-01
          相关资源
          最近更新 更多