Recursively (?) compose LINQ predicates into a single predicate

(EDIT: I have asked the wrong question. The real problem I'm having is over at Compose LINQ-to-SQL predicates into a single predicate - but this one got some good answers so I've left it up!)

Given the following search text:

"keyword1 keyword2 keyword3 ... keywordN"

I want to end up with the following SQL:

SELECT [columns] FROM Customer WHERE (Customer.Forenames LIKE '%keyword1%' OR Customer.Surname LIKE '%keyword1%') AND (Customer.Forenames LIKE '%keyword2%' OR Customer.Surname LIKE '%keyword2%') AND (Customer.Forenames LIKE '%keyword3%' OR Customer.Surname LIKE '%keyword3%') AND ... AND (Customer.Forenames LIKE '%keywordN%' OR Customer.Surname LIKE '%keywordN%')

Effectively, we're splitting the search text on spaces, trimming each token, constructing a multi-part OR clause based on each token, and then AND'ing the clauses together.

I'm doing this in Linq-to-SQL, and I have no idea how to dynamically compose a predicate based on an arbitrarily-long list of subpredicates. For a known number of clauses, it's easy to compose the predicates manually:

dataContext.Customers.Where( (Customer.Forenames.Contains("keyword1") || Customer.Surname.Contains("keyword1") && (Customer.Forenames.Contains("keyword2") || Customer.Surname.Contains("keyword2") && (Customer.Forenames.Contains("keyword3") || Customer.Surname.Contains("keyword3") );

but I want to handle an arbitrary list of search terms. I got as far as

Func<Customer, bool> predicate = /* predicate */; foreach(var token in tokens) { predicate = (customer => predicate(customer) && (customer.Forenames.Contains(token) || customer.Surname.Contains(token)); }

That produces a StackOverflowException - presumably because the predicate() on the RHS of the assignment isn't actually evaluated until runtime, at which point it ends up calling itself... or something.

In short, I need a technique that, given two predicates, will return a single predicate composing the two source predicates with a supplied operator, but restricted to the operators explicitly supported by Linq-to-SQL. Any ideas?

Answer1:

I would suggest another technique

you can do:

var query = dataContext.Customers;

and then, inside a cycle do

foreach(string keyword in keywordlist) { query = query.Where(Customer.Forenames.Contains(keyword) || Customer.Surname.Contains(keyword)); }

Answer2:

If you want a more succinct and declarative way of writing this, you could also use Aggregate extension method instead of foreach loop and mutable variable:

var query = keywordlist.Aggregate(dataContext.Customers, (q, keyword) => q.Where(Customer.Forenames.Contains(keyword) || Customer.Surname.Contains(keyword));

This takes dataContext.Customers as the initial state and then updates this state (query) for every keyword in the list using the given aggregation function (which just calls Where as Gnomo suggests.

人吐槽 人点赞

Recommend

Comment

用户名: 密码:
验证码: 匿名发表

你可以使用这些语言

查看评论:Recursively (?) compose LINQ predicates into a single predicate