【问题标题】:Narrowing a result set using LINQ使用 LINQ 缩小结果集
【发布时间】:2011-08-24 11:46:43
【问题描述】:

假设我有一个名为GetCatsByColor 的方法将颜色作为字符串,GetCatsByName 方法将名称作为字符串,GetCatsByBirthDate 将两个DateTimes 用作时间范围.

现在假设我有一个CatFilter 类,它包含一个名称List、颜色List 和两个DateTimes,代表时间跨度的“开始”日期和“结束”日期。我要做的是创建一个GetFilteredCats 方法,该方法采用其中一个Filter 对象并返回满足给定Filter 规范的Cats 集合。

我很难想出一种有效的方法来获得所需的结果,最好是使用 LINQ/lambda 表达式。

进行这种连接的最佳方法是什么?我应该查看哪些扩展方法?在foreach 循环中修改集合通常是不可取的/不可能的,那么我的策略应该是什么?

【问题讨论】:

  • 高效 = 性能或清洁度和易于编码?
  • 我正在寻找一种高性能的解决方案,但如果您有一个优雅的解决方案,但在时间复杂度方面不一定是最有效的,我会全力以赴。
  • 说你有过滤器{ { 'Garfield' }, { 'yellow' } }。你想要所有命名为 Garfield AND Yellow 的猫还是命名为 Garfield OR yellow 的猫?
  • @svick:我想要一只名叫加菲猫和黄色的猫。

标签: c# linq collections join lambda


【解决方案1】:

我通常做的是检查 where 子句,在执行实际过滤器之前检查是否需要过滤器。当运行时需要评估过滤器时,如果不需要,则完全跳过它。

public class CatFilter
{
    public List<string> Names = new List<string>();
    public List<string> Colors = new List<string>();
    public DateTime? BirthDateStartRange = null;
    public DateTime? BirthDateEndRange = null;
}

public List<Cat> GetFilteredCats(CatFilter filter)
{
    List<Cat> result = new List<Cat>();

    var query = cats
        .Where(a => !filter.Names.Any() || filter.Names.Contains(a.Name))
        .Where(a => !filter.Colors.Any() || filter.Colors.Contains(a.Color))
        .Where(a => filter.BirthDateStartRange == null || a.DateOfBirth >= filter.BirthDateStartRange)
        .Where(a => filter.BirthDateEndRange == null || a.DateOfBirth <= filter.BirthDateEndRange);

    result.AddRange(query);
    return result;
}

然后这样称呼它

cats.Add(new Cat("Felix", "Black", DateTime.Today.AddDays(-1)));
cats.Add(new Cat("Garfield", "Orange", DateTime.Today.AddDays(-10)));

CatFilter filter = new CatFilter();
filter.Names.Add("Garfield");

List<Cat> result = GetFilteredCats(filter);

【讨论】:

    【解决方案2】:

    您可以使用表达式树。当 CatFilter 对象传递给您的 GetFilteredCats 方法时,您会根据在该对象上设置的属性生成表达式(即在下面的伪代码中看到),您可以将其连接起来并用于构建完整的 LINQ 查询。

    类似:

    Expression catFilter =
    from cat in Cats
        where <Expression> and <Expression> and ...
    select cat
    

    然后简单地编译(Expression.Compile)并执行。

    【讨论】:

      【解决方案3】:

      这样的东西应该可以工作,请注意它没有经过测试

      List<string> names = new List<string>();
                  List<Color> colors = new List<Color>();
                  List<DateTime> dobs = new List<DateTime>();
      
                  List<cat> cats = new List<cat>();
      
      
                  var filtered = from c in cats
                                 join n in names on c.name equals n
                                 join cl in colors on c.color equals cl
                                 join db in dobs on c.dob equals db
      
                                 select c;
      

      您还可以有一些包含两个日期的列表,在这种情况下,您需要放置一个 where 条件,其中 c.dob = date2 或类似的东西。 希望这会有所帮助。

      【讨论】:

        【解决方案4】:

        这样做的正确方法是制作方法GetFilteredCats,接受您的过滤器并通过LINQ组合返回正确的猫:

        IEnumerable<Cat> cats = //.. get all cats here
        
        if (filter.FilterByColor)
           cats = cats.Where(c=>c.Color = filter.Color);
        
        if (filter.FilterByName)
           cats = cats.Where(c=>c.Name = filter.Name);
        
        if (filter.FilterByDate)
           cats = cats.Where(c=>c.Date > filter.FromDate && c.Date < filter.ToDate)
        
        return cats.ToList(); // finally filter data and return them.
        

        在性能方面。我认为这在逻辑上不能使用不同的方法来完成。但是,当您开始攻击数百万只猫时,这将成为问题。在这一点上,应该使用数据库。为方便起见,它们具有巧妙的索引和聚类。

        【讨论】:

        • 我已经想到了这一点,但我想避免整个“获得所有猫”部分。这是一个数据库存储库。
        • 那就这么说吧。然后你有完全相同的方法,但不是 IEnumerable 你使用 IQueryable :stackoverflow.com/questions/5881107/…
        猜你喜欢
        • 2023-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-01-11
        • 2022-01-23
        • 2017-09-16
        相关资源
        最近更新 更多