Linq multiple, где запросы

У меня проблема с созданием довольно внушительного запроса linq. В основном у меня есть ситуация, когда мне нужно выполнить подзапрос в цикле, чтобы отфильтровать количество совпадений, возвращаемых из базы данных. Пример кода находится в этом цикле ниже:

        foreach (Guid parent in parentAttributes)
        {
            var subQuery = from sc in db.tSearchIndexes
                           join a in db.tAttributes on sc.AttributeGUID equals a.GUID
                           join pc in db.tPeopleIndexes on a.GUID equals pc.AttributeGUID
                           where a.RelatedGUID == parent && userId == pc.CPSGUID                             
                           select sc.CPSGUID;

            query = query.Where(x => subQuery.Contains(x.Id));
         }

Когда я затем вызываю ToList() в переменной запроса, появляется, что только один из подзапросов был выполнен, и я остался с ведром данных, которые мне не нужны. Однако этот подход работает:

       IList<Guid> temp = query.Select(x => x.Id).ToList();

        foreach (Guid parent in parentAttributes)
        {
            var subQuery = from sc in db.tSearchIndexes
                           join a in db.tAttributes on sc.AttributeGUID equals a.GUID
                           join pc in db.tPeopleIndexes on a.GUID equals pc.AttributeGUID
                           where a.RelatedGUID == parent && userId == pc.CPSGUID                             
                           select sc.CPSGUID;

            temp = temp.Intersect(subQuery).ToList();
        }

        query = query.Where(x => temp.Contains(x.Id));

К сожалению, этот подход неприятен, так как он приводит к нескольким запросам к удаленной базе данных, благодаря чему первоначальный подход, если я могу заставить его работать, приведет только к одному результату. Любые идеи?

Ответ 1

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

Попробуйте следующее:

   foreach (Guid parentLoop in parentAttributes)
    {
        var parent = parentLoop;
        var subQuery = from sc in db.tSearchIndexes
                       join a in db.tAttributes on sc.AttributeGUID equals a.GUID
                       join pc in db.tPeopleIndexes on a.GUID equals pc.AttributeGUID
                       where a.RelatedGUID == parent && userId == pc.CPSGUID                             
                       select sc.CPSGUID;

        query = query.Where(x => subQuery.Contains(x.Id));
     }

Проблема заключается в захвате переменной parent в замыкании (с которой преобразуется синтаксис LINQ), что приводит к запуску всех subQuery es с одним и тем же родительским идентификатором.

Что происходит, так это компилятор, создающий класс для хранения делегата и локальных переменных, к которым обращается делегат. Компилятор повторно использует один и тот же экземпляр этого класса для каждого цикла; и поэтому, как только запрос выполняется, все Where выполняются с тем же parent Guid, а именно последним для выполнения.

Объявление parent внутри области цикла приводит к тому, что компилятор должен сделать копию переменной с правильным значением.

Сначала это может быть сложно понять, поэтому, если это первый раз, когда он ударил вас; Я бы рекомендовал эти две статьи для фона и подробное объяснение:

Ответ 2

Может, так?

var subQuery = from sc in db.tSearchIndexes
               join a in db.tAttributes on sc.AttributeGUID equals a.GUID
               join pc in db.tPeopleIndexes on a.GUID equals pc.AttributeGUID
               where parentAttributes.Contains(a.RelatedGUID) && userId == pc.CPSGUID                             
               select sc.CPSGUID;