Как сделать подзапрос в LINQ? - PullRequest
63 голосов
/ 07 января 2009

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

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

Существует отношение FK между CompanyRolesToUsers и Users, но это отношение многие ко многим, и 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 и у меня есть крайний срок. Мне не удалось найти подходящий пример, но я уверен, что он где-то есть.

Ответы [ 6 ]

76 голосов
/ 07 января 2009

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

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));

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

22 голосов
/ 07 января 2009

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

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

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

5 голосов
/ 07 января 2009

Вот как я делал подзапросы в 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;
2 голосов
/ 07 января 2009

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

   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

2 голосов
/ 07 января 2009

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

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;
1 голос
/ 07 января 2009

Вот версия 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 и посмотрю, смогу ли я интегрировать их с нашим кодом, а затем опубликую свои результаты. Спасибо!

Marcel

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...