Как сделать подзапрос в LINQ?

Вот пример запроса, который я пытаюсь преобразовать в LINQ:

SELECT *
FROM Users
WHERE Users.lastname LIKE '%fra%'
    AND Users.Id IN (
         SELECT UserId 
         FROM CompanyRolesToUsers 
         WHERE CompanyRoleId in (2,3,4) )

Между CompanyRolesToUsers и Users существует отношение FK, но это отношение «многие ко многим», а CompanyRolesToUsers - это таблица соединений.

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

Код простых фильтров выглядит примерно так:

 if (!string.IsNullOrEmpty(TextBoxLastName.Text))
 {
     predicateAnd = predicateAnd.And(c => c.LastName.Contains(
                                     TextBoxLastName.Text.Trim()));
 }

e.Result = context.Users.Where(predicateAnd);

Я пытаюсь добавить предикат для подзапуска в другую таблицу. (CompanyRolesToUsers)

Я бы хотел добавить что-то, что делает следующее:

int[] selectedRoles = GetSelectedRoles();
if ( selectedRoles.Length > 0 )
{
    //somehow only select the userid from here ???:
    var subquery = from u in CompanyRolesToUsers
                   where u.RoleID in selectedRoles
                   select u.UserId;

    //somehow transform this into an Expression ???:
    var subExpression = Expression.Invoke(subquery);

    //and add it on to the existing expressions ???:
    predicateAnd = predicateAnd.And(subExpression);
}

Есть какой-либо способ сделать это? Это расстраивает, потому что я могу легко написать хранимую процедуру, но я новичок в этой вещи LINQ, и у меня есть крайний срок. Мне не удалось найти подходящий пример, но я уверен, что он где-то есть.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
74
0
195 717
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Вот как я делал подзапросы в LINQ, я думаю, это должно получить то, что вы хотите. Вы можете заменить явный CompanyRoleId == 2 ... другим подзапросом для различных ролей, которые вам нужны, или присоединиться к нему.

from u in Users
join c in (
    from crt in CompanyRolesToUsers
    where CompanyRoleId == 2
    || CompanyRoleId == 3
    || CompanyRoleId == 4) on u.UserId equals c.UserId
where u.lastname.Contains("fra")
select u;

Он просил подзапрос! Но я уверен, что LINQ все равно меняет все на тот же SQL ...

Noah 07.01.2009 07:57

Мне пришлось поставить «select crt» после «== 4», чтобы избежать ошибки «тело запроса должно заканчиваться предложением select или предложением group»

Danny Rancher 19.11.2014 13:10

@ Нет, я бы не был так уверен. По моему опыту, производительность запросов LINQ сильно зависит от того, как вы пишете запрос (как и следовало ожидать от любого запроса SQL).

Dan Bechard 22.12.2016 19:51

Для этого оператора не требуется подзапрос, его лучше записать как

select u.* 
from Users u, CompanyRolesToUsers c
where u.Id = c.UserId        --join just specified here, perfectly fine
and u.lastname like '%fra%'
and c.CompanyRoleId in (2,3,4)

или же

select u.* 
from Users u inner join CompanyRolesToUsers c
             on u.Id = c.UserId    --explicit "join" statement, no diff from above, just preference
where u.lastname like '%fra%'
  and c.CompanyRoleId in (2,3,4)

При этом в LINQ это будет

from u in Users
from c in CompanyRolesToUsers 
where u.Id == c.UserId &&
      u.LastName.Contains("fra") &&
      selectedRoles.Contains(c.CompanyRoleId)
select u

или же

from u in Users
join c in CompanyRolesToUsers 
       on u.Id equals c.UserId
where u.LastName.Contains("fra") &&
      selectedRoles.Contains(c.CompanyRoleId)
select u

Что опять же, оба респектабельных способа представить это. Я лично предпочитаю явный синтаксис "соединения" в обоих случаях, но вот он ...

Очень хороший ответ, но все эти методы ведут себя по-разному и обычно создают очень разные sql, sql, которые могут работать хорошо или плохо, обычно использование прямого соединения дает лучший sql, но не всегда ...

Pop Catalin 07.01.2009 02:59

не "очень разные sql", а просто "разные sql". Я не буду спорить с различиями, но, по моему опыту, их нет в отношении производительности. Любой оптимизатор может определить соединения из предложения where.

TheSoftwareJedi 07.01.2009 03:05

По моему опыту работы с достаточно сложными запросами, способ написания запросов linq имеет значение. Sql server 2005 иногда не создает оптимальный план запроса для запросов с глубокими уровнями вложенности подзапросов ... Мне пришлось оптимизировать некоторые сложные запросы linq более чем несколько раз ...

Pop Catalin 07.01.2009 14:52

Спасибо за помощь. Хотя это действительно помогает мне понять, как переводить SQL в запросы LINQ, ваши начальные запросы возвращают разные результаты -> если у пользователя несколько ролей, их запись будет возвращаться несколько раз, поэтому я в первую очередь избегал соединения.

marcel_g 07.01.2009 17:17

На самом деле, если я добавлю к SQL «отдельный», то получу правильные записи. Добавлю еще пост с рабочим стыком.

marcel_g 07.01.2009 17:22

Хм, зачем «фильтровать», когда можно «объединять и разделять». Это потому, что «соединить и разделить» - это естественный и захватывающий sql, в то время как «фильтр» недостаточно захватывающий?

Amy B 07.01.2009 18:00

Что ж, раз уж меня пригласили ... Факт: ни один оптимизатор не может превратить Distinct (n ^ 2) в фильтр (n).

Amy B 07.07.2011 02:50

В 99% случаев запрос, написанный с использованием "отличного", написан неправильно. Если вы хотите присоединиться к таблице для фильтрации, но не хотите дублировать записи в левой части соединения, используйте полусоединение (т. Е. Подзапрос).

WCWedin 18.01.2012 02:27

Вы можете сделать что-то подобное для своего случая - (синтаксис может быть немного неправильным). Также посмотрите этот ссылка на сайт

subQuery = (from crtu in CompanyRolesToUsers where crtu.RoleId==2 || crtu.RoleId==3 select crtu.UserId).ToArrayList();

finalQuery = from u in Users where u.LastName.Contains('fra')  && subQuery.Contains(u.Id) select u;

Я просто хотел показать, что он хочет это сделать .. не навязывать свое мнение

Perpetualcoder 07.01.2009 08:08

@TheSoftwareJedi Я вижу, что вы спамили этот комментарий по нескольким ответам здесь, но имейте в виду, что эту ветку, вероятно, найдут люди, которые ищут помощь при выполнении подзапросов в linq, чьи обстоятельства, вероятно, не совсем такие же, как у OP.

bwerks 22.09.2016 22:13

Вот версия SQL, которая возвращает правильные записи:

select distinct u.* 
from Users u, CompanyRolesToUsers c
where u.Id = c.UserId        --join just specified here, perfectly fine
and u.firstname like '%amy%'
and c.CompanyRoleId in (2,3,4)

Также обратите внимание, что (2,3,4) - это список, выбранный из списка флажков пользователем веб-приложения, и я забыл упомянуть, что я просто жестко запрограммировал его для простоты. На самом деле это массив значений CompanyRoleId, поэтому он может быть (1), (2,5) или (1,2,3,4,6,7,99).

Еще одна вещь, которую я должен указать более четко, - это то, что PredicateExtensions используются для динамического добавления предложений предиката в Where для запроса, в зависимости от того, какие поля формы заполнил пользователь веб-приложения. Таким образом, сложная часть для меня заключается в том, как чтобы преобразовать рабочий запрос в выражение LINQ, которое я могу присоединить к динамическому списку выражений.

Я попробую несколько примеров запросов LINQ и посмотрю, смогу ли я интегрировать их с нашим кодом, а затем опубликую свои результаты. Спасибо!

Марсель

Ответ принят как подходящий

Вот вам подзапрос!

List<int> IdsToFind = new List<int>() {2, 3, 4};

db.Users
.Where(u => SqlMethods.Like(u.LastName, "%fra%"))
.Where(u =>
    db.CompanyRolesToUsers
    .Where(crtu => IdsToFind.Contains(crtu.CompanyRoleId))
    .Select(crtu =>  crtu.UserId)
    .Contains(u.Id)
)

Относительно этой части вопроса:

predicateAnd = predicateAnd.And(c => c.LastName.Contains(
                                TextBoxLastName.Text.Trim()));

Я настоятельно рекомендую извлечь строку из текстового поля перед созданием запроса.

string searchString = TextBoxLastName.Text.Trim();
predicateAnd = predicateAnd.And(c => c.LastName.Contains( searchString));

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

Спасибо за полезный код. Это, безусловно, показывает, как вещи могут быть связаны друг с другом. Вы также, вероятно, правы, выполняя строковую операцию перед тем, как передать ее Linq, конечно, не помешало бы держать вещи разделенными.

marcel_g 07.01.2009 18:25

Хорошо, вот базовый запрос соединения, который получает правильные записи:

   int[] selectedRolesArr = GetSelectedRoles();
    if ( selectedRolesArr != null && selectedRolesArr.Length > 0 ) 
    {

    //this join version requires the use of distinct to prevent muliple records
        //being returned for users with more than one company role.
    IQueryable retVal = (from u in context.Users
                        join c in context.CompanyRolesToUsers
                          on u.Id equals c.UserId
                        where u.LastName.Contains( "fra" ) &&
                            selectedRolesArr.Contains( c.CompanyRoleId )
                        select  u).Distinct();
}

Но вот код, который легче всего интегрируется с уже существующим алгоритмом:

int[] selectedRolesArr = GetSelectedRoles(); 
if ( useAnd ) 
       { 
          predicateAnd = predicateAnd.And( u => (from c in context.CompanyRolesToUsers 
                       where selectedRolesArr.Contains(c.CompanyRoleId) 
                       select c.UserId).Contains(u.Id)); 
        } 
        else 
        { 
           predicateOr = predicateOr.Or( u => (from c in context.CompanyRolesToUsers 
                          where selectedRolesArr.Contains(c.CompanyRoleId) 
                         select c.UserId).Contains(u.Id) ); 
        } 

что благодаря плакату на Форум LINQtoSQL

Другие вопросы по теме