【问题标题】:Linq to Entities - SQL "IN" clauseLinq to Entities - SQL“IN”子句
【发布时间】:2010-10-25 20:54:30
【问题描述】:

在 T-SQL 中,您可以进行如下查询:

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

您将如何在 LINQ to Entities 查询中复制它?有没有可能?

【问题讨论】:

    标签: .net linq linq-to-entities in-clause


    【解决方案1】:

    你需要从你思考它的方式来改变它。不是在预定义的一组适用用户权限中查找当前项目的用户权限,而是询问一组预定义的用户权限是否包含当前项目的适用值。这与您在 .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;
    }
    

    【讨论】:

    • 也许我太快标记为答案,但我没有得到一个 .Contains 在 { "Admin", "User", "Limited" } VS2008 不喜欢那个代码之一位。
    • 对我的名字 "FailBoy" 真实我想通了:P 我把它放入一个字符串 [] 然后使用它并且它起作用了。谢谢!
    • 对不起,我忘了新建匿名数组;)我修复了我的代码示例。不过很高兴你自己想出来了。
    • 如果问题是关于 Linq-to-SQL 或一般的 Linq,这个答案是正确的。但是,由于它专门说“Linq-to-Entities”,所以这个答案是不正确的。 array.Contains 不受 Linq-to-Entities 支持。
    • @KristoferA -- 这对于早期版本的 EF 来说可能是正确的,但对我来说使用 EF4 似乎很好。
    【解决方案2】:

    这应该足以满足您的目的。它比较两个集合并检查一个集合的值是否与另一个集合中的值匹配

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

    【讨论】:

      【解决方案3】:

      在这种情况下,我会选择 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(); 
      

      包含的缺点

      假设我有两个列表对象。

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

      使用 Contains,它将搜索 List 2 中的每个 List 1 项目,这意味着迭代将发生 49 次!!!

      【讨论】:

      • 这完全忽略了语句被翻译成SQL的事实。见here
      【解决方案4】:

      这可能是您可以直接使用 LINQ 扩展方法检查 in 子句的可能方式

      var result = _db.Companies.Where(c => _db.CurrentSessionVariableDetails.Select(s => s.CompanyId).Contains(c.Id)).ToList();
      

      【讨论】:

        【解决方案5】:

        我还尝试使用类似 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 ifself 之前总是有单词“GUID”。如果不添加,ObjectQuery&lt;T&gt;.Where 会抛出以下异常:

        参数类型“Edm.Guid”和 'Edm.String' 与此不兼容 操作。,接近等于表达式, 第 6 行,第 14 列。

        在 MSDN 论坛中找到此内容,可能对您有所帮助。

        马蒂亚斯

        ...期待 .NET 和 Entity Framework 的下一个版本,届时一切都会变得更好。 :)

        【讨论】:

          【解决方案6】:

          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

          【讨论】:

            【解决方案7】:

            实例:

            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;
            

            【讨论】:

              【解决方案8】:

              这不完全是 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 举了一个例子: 这段代码给了我任何名字和/或姓氏与“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 好。
              • 现在代码可以扩展比较(集合)的两侧,而不是比较原子值以检查它是否包含在集合中,这样在您需要扩展用例时可以减少重构。我同意这在 OP 的情况下有点矫枉过正,但它确实有效。
              • 请通过发布工作代码来证明该声明。
              • 我认为我真正的意思是set operators 是查看 OP 问题的更通用的方式。 (IN 运算符感觉像是 INTERSECT 运算符 IMO 的一个特殊用例......)
              • 上面的代码工作正常。如果你愿意,我可以把我的工作代码发给你。
              【解决方案9】:

              真的吗?你们没用过

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

              【讨论】:

              • -1 在超过 1000 行的表中尝试使用 20 个或更多值,您将很快看到公认解决方案的优势。此外,在 where 语句中添加任意数量的条件并不容易(例如,如果用户选择包含选项 1 和 2,但不包含 3)。
              • 好吧,我不需要任何疯狂科学家的东西,这个答案是我的投票,因为我需要一个 AND 和 2 ORS var SamplePoints =(来自 _db.tblPWS_WSF_SPID_ISN_Lookup.OrderBy(x = > x.WSFStateCode) 其中 c.PWS == id && ((c.WSFStateCode.Substring(0, 2) == "SR") || (c.WSFStateCode.Substring(0, 2) == "CH") ) 选择 c).ToList();
              • @Trisped - 行数 (1000) 没有任何改变 - 还是我遗漏了什么?
              • @Tymski 是的,行数很重要。行数越多,计算越多。与可能值的数量相同:Checks = NumValues * NumRows。因为这是一个 M * N 类型的计算,如果其中一个很小,那么执行每个所需检查的时间也会很短。我添加了约束,以便 cjm30305 知道如何设置一个测试环境,以显示他的解决方案为何不佳。
              • @Trisped 你是说where new[] { 1, 2, 3 }.Contains(x) 的比较比where (x == 1 || x == 2 || x == 3) 少吗?
              猜你喜欢
              • 2023-01-13
              • 2010-09-30
              • 2018-04-22
              • 2011-06-16
              • 2011-10-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多