如何在LINQ中进行子查询?


73

这是我要转换为LINQ的查询示例:

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

CompanyRolesToUsers和之间存在FK关系Users,但它是多对多关系,并且CompanyRolesToUsers是联结表。

我们已经构建了大部分站点,并且已经通过使用PredicateExtensions类构建Expressions来完成了大部分过滤工作。

简单过滤器的代码如下所示:

 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并不陌生,而且我有一个截止日期。我找不到匹配的示例,但我确定它在某处。

Answers:


81

这是您的子查询!

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

您希望对发送到数据库的内容保持良好的控制。在原始代码中,一种可能的读法是将未修剪的字符串发送到数据库中进行修整-这对于数据库来说不是一件好事。


2
感谢有用的代码。这肯定说明了如何将事物链接在一起。在将字符串交给Linq之前执行字符串操作也可能是正确的,将各个部分分隔开当然不会有任何伤害。
marcel_g 2009年

24

该语句不需要子查询,最好写成

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”语法,但是它有...


一个很好的答案,但是所有这些方法的行为都不同,通常会产生完全不同的sql,可能执行的好坏的sql通常使用直接连接会产生最佳的sql,但并不总是如此……
Pop Catalin 09年

1
以我对相当复杂的查询的经验,您编写linq查询的方式有所不同sql server 2005有时无法为子查询嵌套深度较高的查询提供最佳的查询计划。我不得不优化一些复杂的linq查询,几次...
Pop Catalin 09年

2
嗯,当您可以“加入并与众不同”时,为什么要“过滤”。是因为“联接和不同”是自然而又令人兴奋的sql,而“过滤器”还不够令人兴奋吗?
Amy B

2
好吧,自从我被邀请...事实:没有优化器可以将Distinct(n ^ 2)变成过滤器(n)。
Amy B

1
在99%的时间内,使用'distinct'编写的查询被写入错误。当您要联接表进行过滤,但又不想在联接的左侧重复记录时,请使用半联接(即子查询)。
WCWedin 2012年

8

这就是我在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;

7
他要求一个子查询!但是我敢肯定,LINQ还是将它们全部更改为相同的SQL ...
Noah,2009年

2
我必须在“ == 4”之后加上“ select crt”,以避免出现错误“查询主体必须以select子句或group子句结尾”
Danny Rancher 2014年

@诺亚,我不确定。以我的经验,LINQ查询性能会根据您编写查询的方式而发生很大变化(这是任何SQL查询所期望的)。
丹·贝查德

2

您可以针对自己的情况做类似的事情-(语法可能会有些偏离)。也看这个链接

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;

3
我只是想表明他想这样做。.不执行我自己的意见
Perpetualcoder

@TheSoftwareJedi我看到您在此处对多个响应发送了此评论,但请记住,在linq中进行子查询搜索的人们很可能会发现此线程,其情况可能与OP并不完全相同。
bwerks

2

好的,这是一个获取正确记录的基本联接查询:

   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论坛上的海报


1

这是返回正确记录的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)是Web应用程序用户从复选框列表中选择的列表,而我忘了提到我只是为了简单起见对其进行了硬编码。实际上,它是CompanyRoleId值的数组,因此它可以是(1)或(2,5)或(1,2,3,4,6,7,99)。

我还要明确指出的另一件事是,PredicateExtensions用于将谓词子句动态添加到查询的“位置”,具体取决于Web应用程序用户填写的表单字段。因此,对我来说棘手的部分是如何将工作查询转换为LINQ表达式,我可以将其附加到动态表达式列表中。

我将给一些示例LINQ查询一个镜头,看看是否可以将它们与我们的代码集成在一起,然后发布结果。谢谢!

马塞尔

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.