Linq to Entities - SQL中的“IN”子句

262

在 T-SQL 中,您可以拥有如下查询:

SELECT * FROM Users WHERE User_Rights IN ("Admin", "User", "Limited")

如何在 LINQ to Entities 查询中复制它?这是否可能?

10个回答

390
你需要转变一下对此问题的思考方式。不要用 "in" 来查找当前项目在预定义的应用用户权限集合中的用户权限,而是询问预定义的用户权限集合是否包含当前项目的适用值。这与在 .NET 中查找常规列表项的方式完全相同。
使用 LINQ 有两种方法来实现这一点,一种使用查询语法,另一种使用方法语法。本质上它们是相同的,可以根据个人偏好互换使用。 查询语法:
var selected = from u in users
               where new[] { "Admin", "User", "Limited" }.Contains(u.User_Rights)
               select u

foreach(user u in selected)
{
    //Do your stuff on each selected user;
}

方法语法:

var selected = users.Where(u => new[] { "Admin", "User", "Limited" }.Contains(u.User_Rights));

foreach(user u in selected)
{
    //Do stuff on each selected user;
}

在这种情况下,我的个人偏好可能是方法语法,因为我可以像这样对匿名调用进行foreach而不是赋值变量:

foreach(User u in users.Where(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}

从语法上看,这似乎更加复杂,并且您必须理解Lambda表达式或委托的概念才能真正弄清楚发生了什么,但是正如您所看到的,这将代码压缩了相当多。

这完全取决于您的编码风格和偏好 - 我的三个示例都略微以不同的方式执行相同的操作。

另一种替代方法甚至不使用LINQ,您可以使用相同的方法语法,将"where"替换为"FindAll",并获得相同的结果,这也适用于 .NET 2.0:

foreach(User u in users.FindAll(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}

1
对于我的名字“FailBoy”,我想出来了:P 我把它放进了一个string[]中,然后使用它,它起作用了。谢谢! - StevenMcD
抱歉,我忘记了实例化匿名数组 ;) 我已经修复了我的代码示例。不过很高兴你自己找到了解决办法。 - BenAlabaster
30
如果问题是关于Linq-to-SQL或Linq的一般性问题,那么此答案将是正确的。然而,由于它明确指出了"Linq-to-Entities",所以这个答案是错误的。array.Contains在Linq-to-Entities中还不被支持。 - KristoferA
7
对于 EF 的早期版本,@KristoferA 的说法可能是正确的,但对于我使用的 EF4 版本来说,它似乎没有问题。 - Drew Noakes
@BenAlabaster,我有一组需要在“IN”中检查的项目,如 var oIds = (List<int>) Session["OrderIds"];。我该如何查询这些项目,而不是新建它并检查硬编码值(例如,“Admin”,“Users”,“Limited”)? - Ron
显示剩余2条评论

24

这应该已经满足了你的需求。它比较了两个集合,并检查一个集合是否具有与另一个集合中相匹配的值。

fea_Features.Where(s => selectedFeatures.Contains(s.feaId))

10

在这种情况下,我会选择使用内连接(Inner Join)。如果我使用了 contains 函数,即使只有一个匹配项,它也会迭代6次。

var desiredNames = new[] { "Pankaj", "Garg" }; 

var people = new[]  
{  
    new { FirstName="Pankaj", Surname="Garg" },  
    new { FirstName="Marc", Surname="Gravell" },  
    new { FirstName="Jeff", Surname="Atwood" }  
}; 

var records = (from p in people join filtered in desiredNames on p.FirstName equals filtered  select p.FirstName).ToList(); 

Contains 方法的缺点

假设我有两个列表对象。

List 1      List 2
  1           12
  2            7
  3            8
  4           98
  5            9
  6           10
  7            6

使用 Contains 方法,将在 List 2 中搜索每个 List 1 的元素,这意味着迭代会发生 49 次!!!


5
完全忽略了这个语句被转换成 SQL 的事实。请参见此处 - Gert Arnold

6
这可能是您可以直接使用LINQ扩展方法来检查in子句的可能方法。
var result = _db.Companies.Where(c => _db.CurrentSessionVariableDetails.Select(s => s.CompanyId).Contains(c.Id)).ToList();

3

我也尝试使用类似于SQL-IN的东西 - 对实体数据模型进行查询。我的方法是使用字符串构建器来组合一个大的OR表达式。这样做非常丑陋,但我担心现在这是唯一的方法。

现在,看起来像这样:

Queue<Guid> productIds = new Queue<Guid>(Products.Select(p => p.Key));
if(productIds.Count > 0)
{
    StringBuilder sb = new StringBuilder();
    sb.AppendFormat("{0}.ProductId = Guid\'{1}\'", entities.Products.Name, productIds.Dequeue());
    while(productIds.Count > 0)
    {
        sb.AppendFormat(" OR {0}.ProductId = Guid\'{1}\'",
          entities.Products.Name, productIds.Dequeue());
    }
}

在这种情况下使用GUID:如上所述,查询字符串片段中始终在GUID之前加上单词“GUID”。如果不添加此项,则ObjectQuery<T>.Where会抛出以下异常:

The argument types 'Edm.Guid' and 'Edm.String' are incompatible for this operation., near equals expression, line 6, column 14.

在MSDN论坛中发现了这个问题,可能对您有帮助。期待着下一个版本的.NET和Entity Framework,一切都会变得更好。 :)

2

一种替代BenAlabaster答案的方法

首先,您可以像这样重写查询:

var matches = from Users in people
        where Users.User_Rights == "Admin" ||
              Users.User_Rights == "Users" || 
              Users.User_Rights == "Limited"
        select Users;

当然,这种写法可能会更啰嗦且编写起来比较麻烦,但是它仍然可以工作。

因此,如果我们有一些实用方法可以轻松创建这种LINQ表达式,那么我们就可以开始了。

有了一个实用方法,你可以像这样编写代码:

var matches = ctx.People.Where(
        BuildOrExpression<People, string>(
           p => p.User_Rights, names
        )
);

这将构建一个与以下表达式具有相同效果的表达式:

var matches = from p in ctx.People
        where names.Contains(p.User_Rights)
        select p;

但更重要的是,它可以对抗.NET 3.5 SP1。

以下是使此操作成为可能的管道功能:

public static Expression<Func<TElement, bool>> BuildOrExpression<TElement, TValue>(
        Expression<Func<TElement, TValue>> valueSelector, 
        IEnumerable<TValue> values
    )
{     
    if (null == valueSelector) 
        throw new ArgumentNullException("valueSelector");

    if (null == values)
        throw new ArgumentNullException("values");  

    ParameterExpression p = valueSelector.Parameters.Single();

    if (!values.Any())   
        return e => false;

    var equals = values.Select(value =>
        (Expression)Expression.Equal(
             valueSelector.Body,
             Expression.Constant(
                 value,
                 typeof(TValue)
             )
        )
    );
   var body = equals.Aggregate<Expression>(
            (accumulate, equal) => Expression.Or(accumulate, equal)
    ); 

   return Expression.Lambda<Func<TElement, bool>>(body, p);
}

我不会试图解释这个方法,除了说它基本上使用valueSelector(即p => p.User_Rights)为所有值构建一个谓词表达式,并将这些谓词一起使用OR运算符组合在一起,以创建完整谓词的表达式。
来源:http://blogs.msdn.com/b/alexj/archive/2009/03/26/tip-8-writing-where-in-style-queries-using-linq-to-entities.aspx

0
这不完全是IN运算符,但它可能会帮助你获得预期的结果,也许是一种更通用的方法(因为它允许比较两个集合):INTERSECT
这里有一个可行的例子。
var selected = 
  users.Where(u => 
    new[] { "Admin", "User", "Limited" }.Intersect(new[] {u.User_Rights}).Any()
  );
OR
var selected = 
  users.Where(u => 
    new[] {u.User_Rights}.Intersect(new[] { "Admin", "User", "Limited" }).Any()
  );

我想性能应该进行基准测试(与当前接受的答案相比)以完全验证这个解决方案...

编辑:

正如Gert Arnold所要求的示例(EF 6): 这段代码给我任何名字或姓氏匹配“John”或“Doe”的用户:

// GET: webUsers
public async Task<ActionResult> Index()
{
  var searchedNames = new[] { "John", "Doe" };

  return 
    View(
      await db
        .webUsers
        .Where(u => new[] { u.firstName, u.lastName }.Intersect(searchedNames).Any())
        .ToListAsync()
    );

  //return View(await db.webUsers.ToListAsync());
}

这个“更通用”在哪里?这是一个非常牵强的解决方案。绝对不比简单的“Contains”更好。 - Gert Arnold
代码现在可以扩展比较的两个方面(集合),而不是比较原子值以检查其是否包含在集合中,这样可以减少在需要扩展用例时的重构。我同意在 OP 的情况下这有点过度,但它确实有效。 - Axel Samyn
请通过发布可工作的代码来证明该声明。 - Gert Arnold
我认为我真正想表达的是集合运算符是更通用的方式来看待OP的问题。(在我看来,IN运算符感觉像是INTERSECT运算符的一个特定用例...) - Axel Samyn
这个链接可能会帮助你理解我的观点。 - Axel Samyn
显示剩余4条评论

0

真实例子:

var trackList = Model.TrackingHistory.GroupBy(x => x.ShipmentStatusId).Select(x => x.Last()).Reverse();
List<int> done_step1 = new List<int>() {2,3,4,5,6,7,8,9,10,11,14,18,21,22,23,24,25,26 };
bool isExists = trackList.Where(x => done_step1.Contains(x.ShipmentStatusId.Value)).FirstOrDefault() != null;

0

查询语法:

string[] month = { "jan", "feb", "mar" };

var qry = from c in populationdata
            where c.birthmonth in month
            select c;

将从“populationdata”中选择记录,其中月份为字符串数组“month”中的一个。

-15

真的吗?你们从未使用过

where (t.MyTableId == 1 || t.MyTableId == 2 || t.MyTableId == 3)

9
在一个有1000行以上的表格中使用20个或更多值尝试这种方法,你很快就会看到接受的解决方案的优势。此外,在where语句中添加任意数量的条件并不容易(例如,如果用户选择包括选项1和2,但不包括3)。 - Trisped
好的,我不需要任何疯狂科学家的东西,这个答案得到了我的投票,因为我需要一个AND和2个ORS var SamplePoints =(从c中选择_db.tblPWS_WSF_SPID_ISN_Lookup.OrderBy(x => x.WSFStateCode) where c.PWS == id && ((c.WSFStateCode.Substring(0, 2) == "SR") || (c.WSFStateCode.Substring(0, 2) == "CH")) select c).ToList(); - JustJohn
@Trisped - 行数(1000)不会改变任何东西 - 或者我有遗漏的吗? - tymtam
@Trisped 你是在说 where new[] { 1, 2, 3 }.Contains(x)where (x == 1 || x == 2 || x == 3) 进行更少的比较吗? - tymtam
我认为在Oracle中,使用||Contains的性能是相同的,无论使用多少个||,而在MS SQL中,性能差异非常大(慢30%?)。我有一种感觉,你关于1000甚至10万行的论点是不正确的。你能支持一下吗? - tymtam
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接