views:

1052

answers:

1

The All method is supposed to evaluate the argument against all elements in the list. It works ok in regular Linq but when I try to use it with EF it throws an error ("Unable to create a constant value of type 'Closure type'. Only primitive types (for instance Int32, String and Guid) are supported in this context. ")

Example:

var myList = from person in entities.People
             where searchList.All(arg => arg == arg).ToList();

(arg == arg here is just to illustrate my question)

In my scenario, searchList is a List containing search items, such as "John", "Accounting", "75". In my EF query I want to retrieve all records in People which John, Accounting and 75 appear in some specified searchable fields. A more realistic example would be something like this:

where SearchList.All((person.FirstName + " " + person.LastName + " " + person.DepartmentName + " " + person.Phone).Contains)

This second example also works ok with Linq, in memory, but EF doesn't like it.

Please help! What can I do to make it work?

This is a more specific question derived from another question of mine.

Sample code:

IEnumerable<string> searchList = ParseSearchText(searchText); //search text is broken into search tokens - each token is an element in searchList. For instance "John", "Sales", "654"

var peopleQuery = from person in entities.vSearchPeople
where upperSearchList.All((person.FirstName + " " + person.Lastname + " " + person.Phone).ToUpperInvariant().Contains)
select person;
+4  A: 

The Entity Framework does not support all queries. This becomes obviouse if you think of something like the following

dataContext.Persons.Where(person => MyMethod(person));

with MyMethod() returning a boolean value. The method might do everything and you cannot translate everything into SQL. The solution is to get all entites into local memory using ToList() and then use LINQ to Object.

dataContext.Persons.ToList().Where(person => MyMethod(person));

It depends on you actual query if it can be rewritten so that it can be transformed into SQL by the Entity Framework or if you have to do the query in local memory using LINQ to Object.

The exception you mentioned sound like you are trying something like the following.

Company company = datacontext.Companies.Where(company.Name == "ACME").Single();

dataContext.Employees.Where(employee => employee.Company == company);

LINQ to Entity does not support expressions containing entities, hence the comparison of the Company entities is not valid. In this case you can rewrite it as follows.

dataContext.Employees.Where(employee => employee.Company.Id == company.Id);

This compares only the ids - a primitive type like a integer or a GUID - and this can be transformed into SQL.

Example for search word by word (see also the comments)

IQueryable<People> result = entities.People;

foreach (String item in searchList)
{
    // This copy is important in order not to modify the closure.
    String itemCopy = item;

    result = result.Where(p =>
        p.FirstName.ToUpper().Contains(itemCopy) ||
        p.LastName.ToUpper().Contains(itemCopy) ||
        p.Phone.ToUpper().Contains(itemCopy));
}

This will construct the query word by word. Noted that the Entity Framework recognizes ToUpper(), ToLower(), and Contains() (and some more) - so I was to strict when I said that the Entity Framework does not regonize method calls. It does, but not many and not ToUpperInvariant() and ToLowerInvariant(). Further this query translates into CHARINDEX() function calls using the collation of the column, hence the search can be case insensitive without explicit ToUpper() or ToLower() calls.

Daniel Brückner
Thanks Daniel. I appreciate your answer. The error is thrown because of the All method. Even if I have something like this it errors-out:from person in entities.vSearchPeople where searchList.All(f => true).I don't how can I rewrite it since I have to match all items in my "searchList" to the evaluating function. I guess I need to do that in a stored procedure than... Do you agree?
Gustavo Cavalcanti
The error is because of searchlist.All() inside Where(). You can rewrite it as entities.People.ToList().Where(people => searchList.All(arg => whatever(arg)));
Daniel Brückner
If you can provide the whole code - type and construction of searchlist and the query - I can have a look if it can be rewritten to be executed inside the database.
Daniel Brückner
Daniel, I've added some code to the question.
Gustavo Cavalcanti
ToUpperInvariant() causes the problem - the Entity Framework is unable to convert this method call into SQL. How should it do that? It would have to know what the method does. There are probably some more problems, but I have no IDE here to test it. If you need the method call, you can only call ToList() first and then use LINQ to Object. var peopleQuery = entities.vSearchPeople.ToList().Where(person => upperSearchList.All(item => (person.FirstName + " " + person.Lastname + " " + person.Phone).ToUpperInvariant().Contains(item)))
Daniel Brückner
Further I suggest the following modification (I leave the ToUpper() call out). entities.vSearchPeople.ToList().Where(person => upperSearchList.All(item => person.FirstName.Contains(item) ||person.Lastname.Contains(item) || person.Phone.Contains(item))) This seems to be more natural than your version with the string concatanation.
Daniel Brückner
The ToUpper() may be a problem but is not causing the error, since if I remove it I still get them same thing. About your suggestion, if I use ToList().Where... I will get the entire unfiltered data and filter in memory, correct? This is not an option for me since my goal is to filter on the server. This is potentially a very wide search (on employees, departments, phones, positions, contractors, etc., thus, a select without restrictions is not an option
Gustavo Cavalcanti
You are right ... I overlooked a point. Your searchlist is a problem, too. The Entity Framework knows only how to handle primitive types like ints, strings, and GUIDs, but cannot handle this list. ToList() will fetch the unfiltered list. If this is no option, you need to redesign the search. You could try to search for the words one by one and then intersect the results. I am going to add an example to my answer. But this leaves you still with the casing problem.
Daniel Brückner
Thanks a lot Daniel. I appreciate your effort in trying to help me.I think I will either follow your suggestion on iterating through the searchList words, or just have a stored procedure that takes a comma-delimited string of search words and returns a result set.Again thanks!
Gustavo Cavalcanti