Linq to Entities - SQL“IN”子句

Posted

技术标签:

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

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

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

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

【问题讨论】:

【参考方案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) 少吗?

以上是关于Linq to Entities - SQL“IN”子句的主要内容,如果未能解决你的问题,请参考以下文章

LINQ to Entities 中的“NOT IN”子句

Linq to Entities基础之需要熟知14个linq关键字(from,where,select,group,let,on,by...)

如何在 LINQ to Entities 中执行 SQL“在哪里存在”?

LINQ to Entities区分大小写比较

如何避免从 LINQ to Entities 中的多个线程从 SQL 数据库中读取相同的记录

如果存在-UPDATE-else-INSERT 与 Linq-to-Entities?