简体   繁体   English

使用LINQ缩小结果集

[英]Narrowing a result set using LINQ

Say I have a method called GetCatsByColor which takes a color as a string, a method GetCatsByName which takes a name as a string, and GetCatsByBirthDate which takes two DateTime s acting as a range of time. 假设我有一个名为GetCatsByColor的方法,该方法将颜色作为字符串,将GetCatsByName的方法以名称作为字符串,将GetCatsByBirthDate方法以两个DateTime作为时间范围。

Now say I have a CatFilter class which holds a List of names, List of colors, and two DateTime s, representing the "from" date and the "to" date of a timespan. 现在说我有一个CatFilter持有一类List的名字, List的颜色,和两个DateTime S,表示“从”日期和时间跨度的“到”日期。 What I am trying to do is create a GetFilteredCats method that takes one of these Filter objects and returns a collection of Cats that meet the specifications of the given Filter . 我要尝试做的是创建一个GetFilteredCats方法,该方法接受这些Filter对象之一,并返回满足给定Filter规格的Cat集合。

I'm having a hard time coming up with an efficient way of getting the desired results, ideally using LINQ/lambda expressions. 我很难想出一种有效的方法来获得期望的结果,最好是使用LINQ / lambda表达式。

What is the best way to go about doing this sort of join? 进行这种加入的最佳方法是什么? What extension methods should I be looking at? 我应该看什么扩展方法? Modifying a collection in a foreach loop generally isn't advisable/possible, so what should my strategy be? 通常不建议/不可能在foreach循环中修改集合,那么我的策略应该是什么?

What i normally do is to put a check in the where clause which checks if the filter is needed before doing the actual filter. 我通常要做的是在where子句中进行检查,该子句在执行实际过滤器之前检查是否需要过滤器。 When the runtime needs to evaluate the filter, it is skipped entirely if it isn't needed. 当运行时需要评估过滤器时,如果不需要它会被完全跳过。

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;
}

And then calling it like this 然后这样称呼它

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);

Correct way to do this is to make method GetFilteredCats , accept your filter and return correct cats throught LINQ composition: 正确的方法是使方法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.

In case of performance. 以防万一。 I don't think this can logicaly be done using different aproach. 我认为使用不同的方法无法在逻辑上做到这一点。 But this will become problem when you start hitting milions of cats. 但是,当您开始击打数百万只猫时,这将成为问题。 And at this point, database should be used instead. 在这一点上,应该改为使用数据库。 Those have clever indexing and clustering for your convenience. 这些为您提供方便的索引编制和群集。

Something like this should work, please note it's not tested 像这样的东西应该可以工作,请注意它未经测试

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;

You can also have some list with two dates, in which case you'll need to put a where condition, where c.dob <= date1 && c.dob >= date2, or something similar. 您还可以具有一些带有两个日期的列表,在这种情况下,您需要放置一个where条件,其中c.dob <= date1 && c.dob> = date2或类似的东西。 Hope this helps. 希望这可以帮助。

You could use Expression trees. 您可以使用表达式树。 When a CatFilter object is passed to your GetFilteredCats method, based on the properties that are set on this object, you generate Expressions (ie seen in pseudo-code below) which you concatenate and use to build out a full LINQ query. 当CatFilter对象传递到GetFilteredCats方法时,根据在此对象上设置的属性,您可以生成表达式(即在下面的伪代码中看到的),将其连接起来并用于构建完整的LINQ查询。

Something like: 就像是:

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

Then simply compile (Expression.Compile) and execute. 然后只需编译(Expression.Compile)并执行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM