Here is some sample data:
List<Book> books = new List<Book>()
{
new Book(){Title = "artemis fowl: the time paradox", Pages = 380},
new Book(){Title = "the lieutenant", Pages = 258},
new Book(){Title = "the wheel of time", Pages = 1032},
new Book(){Title = "ender's game", Pages = 404},
new Book(){Title = "the sphere", Pages = 657}
};
Background:
The above uses a simplified version of a Book class. It would, of course, contain many fields. My end goal is to allow the user to perform an 'advanced' search allowing the user to specify any field and further allow the user to specify keywords using boolean algebra for a particular field.
eg: In a title search text box: the + (cake | pastry) + ~demon
The above would mean: Find all books that, in the title, have the words 'the', either of 'cake' or 'pastry', and does not have the word 'demon'.
Problem:
Baby steps will lead to the final solution. So I initially had the following code:
List<Func<Book, bool>> fs = new List<Func<Book, bool>>()
{
b => b.Title.Contains("me"),
b => b.Title.Contains("the")
};
var q2 = from b in books select b;
foreach (var f in fs)
q2 = q2.Where(f);
foreach (Book b in q2)
{
Console.WriteLine("Title:\t\t{0}\nPages:\t\t{1}\n",
b.Title, b.Pages);
}
The above code works fine. It looks for books that contain 'the' AND 'me' in the title.
Phase 2
Now the above filter is of type Func<Book, bool>. That class will be an Entity Framework generated class and I don't want to use in my UI layer where the search phrase will be input and search filters will be generated to be passed on to the BLL.
So I have the following three attempts:
var q = from b in books select b;
List<Func<string, bool>> filters = new List<Func<string, bool>>()
{
s => s.Contains("me"),
s => s.Contains("the"),
};
//This works...
for (int i = 0; i != filters.Count; ++i)
{
Func<string, bool> daF = filters[i];
q = q.Where(b => (daF(b.Title)));
}
//This produces an exception...
//Due to index in query?
// for (int i = 0; i != filters.Count; ++i)
// {
// q = q.Where(b => ((filters[i])(b.Title)));
// }
//This runs but doesn't produce the proper output
// foreach (Func<string, bool> filter in filters)
// q = q.Where(b => filter(b.Title));
foreach (Book b in q)
{
Console.WriteLine("Title:\t\t{0}\nPages:\t\t{1}\n",
b.Title, b.Pages);
}
The first commented-out piece fires an indexer out of range exception stating that the value of i is 2.
The second commented-out piece runs and produces output, but it prints out FOUR of the 5 books... all EXCEPT the book titled "ender's game". That's not right...
So, reading over my post, I see that I could not reign in my bad habit of explaining every little detail...
So there you go. Please explain why the differing outputs. And I guess you could hint on likely improvements to my current 'solution'.
Since we're using LINQ to Objects here, you should be able to use All()
. Then you won't need to loop.
var query = books.Where(book => filters.All(filter => filter(book.Title)));
Which is equivalent to:
var query = from book in books
where filters.All(filter => filter(book.Title))
select book;
As for why the other attempts doesn't work, you are closing over the loop variable. Generally, you should be careful when using lambda functions in loops due to this. The simple fix is to declare a separate variable that you use in your lambdas. Notice that you actually did this indirectly in your first query. However you shouldn't need loops at all and should use one of the queries above.
for (int i = 0; i != filters.Count; ++i)
{
var index = i;
q = q.Where(b => filters[index](b.Title));
}
foreach (Func<string, bool> f in filters)
{
var filter = f;
q = q.Where(b => filter(b.Title));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With