Q:

EF filtering/searching with multiple words

I have a simple custom table with a search/filter field. I leave the implementation of the search up to each use of the table.
So let's say I have users in my table and I want to search for them. I want to search both in users firstname, lastname and also any role they are in.
This would probably do the trick
            searchString = searchString.ToLower();

            query = query.Where(
                x =>
                    x.FirstName.ToLower().Contains(searchString)
                    ||
                    x.LastName.ToLower().Contains(searchString)
                    ||
                    x.Roles.Any(
                        role =>
                            role.Name.ToLower().Contains(searchString)
                        )
                );

But now I want to search/filter on multiple words. First I get an array of all separate words.
            var searchStrings = searchString.ToLower().Split(null);

I tried the following but it does not fulfill my requirements listed further down as it returns any user where any word is matched in any field. I need that all words are matched (but possibly in different fields). Se below for more details.
            query = query.Where(
                x =>
                    searchStrings.Any(word => x.FirstName.ToLower().Contains(word))
                    ||
                    searchStrings.Any(word => x.LastName.ToLower().Contains(word))
                    //snipped away roles search for brevity
                );

First let me produce some data
Users (data)
Billy-James Carter is admin and manager
James Carter is manager
Billy Carter has no role
Cases
If my search string is "billy car" I want Billy-James and Billy returned but not James Carter (so all words must match but not on same field).
If my search string is "bil jam" or even "bil jam car" I only want Billy-James returned as he is the only one matching all terms/words. So in this the words bil and jam were both found in the FirstName field while the car term was found in the LastName field. Only getting the "car" part correct is not enough and James is not returned.
If I search for "car man" Billy-James and James are both managers (man) and named Carter and should show up. should I search for "car man admi" then only Billy-James should show up.
I am happy to abandon my current approach if better is suggested.

A:

I cannot think of a way to wrap what you're looking for up into a single LINQ statement. There may be a way, but I know with EF the options are more limited than LINQ on an object collection. With that said, why not grab a result set from the database with the first word in the split, then filter the resulting collection further?
var searchWords = searchString.ToLower().split(' ');

var results = dataBase.Where(i => i.FirstName.ToLower().Contains(searchWords[0])
                  || i.LastName.ToLower().Contains(searchWords[0])
                  || i.Role.ToLower().Contains(searchWords[0]));

if(searchWords.Length > 1) {
    for(int x = 1; x < searchWords.Length; x++) {
        results = results.Where(i => i.FirstName.ToLower().Contains(searchWords[x])
                  || i.LastName.ToLower().Contains(searchWords[x])
                  || i.Role.ToLower().Contains(searchWords[x]));
    }
}

Your final content of the results collection will be what you're looking for.
Disclaimer: I didn't have a setup at the ready to test this, so there may be something like a .ToList() needed to make this work, but it's basically functional.
Update: More information about EF and deferred execution, and string collection search
Given we have the schema:
Employee:
    FirstName - String
    Last Name - String
    Roles - One to Many
        Role:
            Name - String

The following will build a query for everything you want to find
var searchTerms = SearchString.ToLower().Split(null);

var term = searchTerms[0];
var results = from e in entities.Employees
              where (e.FirstName.Contains(term) 
                  || e.LastName.Contains(term) 
                  || e.Roles.Select(r => r.Name).Any(n => n.Contains(term)))
              select e;

if (searchTerms.Length > 1)
{
    for (int i = 1; i < searchTerms.Length; i++)
    {
        var tempTerm = searchTerms[i];
        results = from e in results
                  where (e.FirstName.Contains(tempTerm) 
                      || e.LastName.Contains(tempTerm) 
                      || e.Roles.Select(r => r.Name).Any(n => n.Contains(tempTerm)))
                  select e;
    }
}

At this point the query still has not been executed. As you filter the result set in the loop, this is actually adding additional AND clauses to the search criteria. The query doesn't execute until you run a command that does something with the result set like ToList(), iterating over the collection, etc. Put a break point after everything that builds the query and take a look at it. LINQ to SQL is both interesting and powerful.
More on deferred execution
The one thing which needs explanation is the variable tempTerm. We need a variable which is scoped within the loop so that we don't end up with one value for all the parameters in the query referencing the variable term.

