【问题标题】:Linq to Entities - SQL "IN" clauseLinq to Entities - SQL \"IN\" 子句
【发布时间】:2023-01-13 12:51:40
【问题描述】:

在 T-SQL 中你可以有这样的查询:

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

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

【问题讨论】:

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


    【解决方案1】:

    你需要根据你对它的思考方式来扭转它。您不是通过“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;
    }
    

    【讨论】:

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

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

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

    【讨论】:

      【解决方案3】:

      在这种情况下,我将选择 Inner Join。如果我使用包含,它会迭代 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,它将搜索列表 2 中的每个列表 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 本身之前总是有单词“GUID”。如果你不添加这个,ObjectQuery&lt;T&gt;.Where 会抛出以下异常:

        参数类型 'Edm.Guid' 和 'Edm.String' 与此不兼容 操作., near equals expression, 第 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)为所有值构建一个谓词表达式,并将这些谓词组合在一起以创建一个完整的表达式谓词

          来源: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 要求举个例子(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好。
              • 代码现在可以扩展比较的两侧(集合),而不是比较原子值来检查它是否包含在集合中,这样在您需要扩展用例时允许更少的重构。我同意这在 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 = (from c in _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")) 选择 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)做的比较少吗?
              猜你喜欢
              • 2010-10-25
              • 2010-09-30
              • 2018-04-22
              • 2011-06-16
              • 2011-10-18
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多