Запрос Linq, построенный в цикле foreach, всегда принимает значение параметра из последней итерации

У меня есть список, содержащий несколько ключевых слов. Я прошу через них построить мой linq-запрос с ними так (свалился, чтобы удалить кодовый шум):

List<string> keys = FillKeys()
foreach (string key in keys){
    q = q.Where(c => c.Company.Name.Contains(key));
}

Когда мои ключи теперь содержат 2 клавиши, которые возвращают результаты отдельно, но никогда не могут встречаться вместе (каждый элемент в q является либо "xyz", либо "123", никогда "123" и "xyz" ), я все равно получаю Результаты. Результат - то же самое, что и последняя строка.

Я посмотрел на запрос linq, и кажется, что он создает правильный sql, но он заменяет @p1 AND @p2 как тем же (последним итерированным) значением.

Что я делаю неправильно?

Ответы

Ответ 1

Вы повторно используете одну и ту же переменную (key) в выражении лямбда.

Более подробную информацию см. в моей статье о анонимных методах, а также есть ряд связанных вопросов SO:

Простое исправление состоит в том, чтобы сначала скопировать переменную:

List<string> keys = FillKeys()
foreach (string key in keys){
    string copy = key;
    q = q.Where(c => c.Company.Name.Contains(copy));
}

Ответ 2

Возможно, проблема с захваченной переменной; попробуйте добавить:

List<string> keys = FillKeys()
foreach (string key in keys){
    string tmp = key;
    q = q.Where(c => c.Company.Name.Contains(tmp));
}

Ответ 3

он был исправлен в С# 5.0, а пример выше в С# 5.0 работает, но не работает в более ранних версиях С#.

Но будьте осторожны, это не касается цикла for

  static void Main()
        {
            IEnumerable<char> query = "aaa bbb ccc";
            string lettersToRemove = "ab";

            Console.WriteLine("\nOK with foreach:");
            foreach (var item in lettersToRemove)
            {
                query = query.Where(c => c != item);   
            }   
            foreach (char c in query) Console.Write(c);

            //OK:
            Console.WriteLine("\nOK with foreach and local temp variable:");
            query = "aaa bbb ccc";

            foreach (var item in lettersToRemove)
            {
                var tmp = item;
                query = query.Where(c => c != tmp);
            }            
            foreach (char c in query) Console.Write(c);


            /*
             An IndexOutOfRangeException is thrown because:
             firstly compiler iterates the for loop treating i as an outsite declared variable  
             when the query is finnaly invoked the same variable of i is captured (lettersToRemove[i] equals 3) which generates IndexOutOfRangeException

             The following program writes aaa ccc instead of writing ccc:
             Each iteration gets the same variable="C", i (last one frome abc). 
             */

            //Console.WriteLine("\nNOK with for loop and without temp variable:");
            //query = "aaa bbb ccc";

            //for (int i = 0; i <  lettersToRemove.Length; i++)
            //{
            //    query = query.Where(c => c != lettersToRemove[i]);
            //}
            //foreach (char c in query) Console.Write(c);

            /*
             OK
             The solution is to assign the iteration variable to a local variable scoped inside the loop
             This causes the closure to capture a different variable on each iteration.
            */
            Console.WriteLine("\nOK with for loop and with temp variable:");
            query = "aaa bbb ccc";

            for (int i = 0; i < lettersToRemove.Length; i++)
            {
                var tmp = lettersToRemove[i];
                query = query.Where(c => c != tmp);
            }
            foreach (char c in query) Console.Write(c);
        }