LINQ查询:基于键确定一个列表中的对象是否存在于另一个列表中

7

我要做的基本上是根据多个条件对对象列表进行过滤,其中一个条件是该键不存在于另一个列表中。以下是一个示例:
我的两个类类似于这样:

public class Test  
{  
  public string name;  
  public string instructor_name;  
  public string course;  
}  

public class Appointment  
{  
  public string site;
  public DateTime forWhen;
  public string testName;
}

我想通过查看课程并确保测试不存在于List<Appointment>中来整理List<Test>。在SQL中,我会像这样做:

SELECT new Group<Test>(c.Key, c)
FROM tests in testList
WHERE tests.Course != "Science"
AND tests.name NOT IN (SELECT testName FROM appotList)

然而,我无法想出如何在LINQ中实现这一点。有什么想法吗?

你可能需要先看一下那条 SQL 的第一行……它不是 SQL :) - Adam Robinson
我意识到这一点,但我并不想用SQL来实现它。那只是我如果要在SQL中实现它所采用的格式。我以这种方式编写它,以便人们对我在LINQ中尝试做什么有更好的理解。 - legacybass
2个回答

12

如果您想进行客户端过滤,使用LINQ to Objects非常容易。可以像这样实现:

List<Test> tests = ...;
List<Appointment> appts = ...;

var query = tests.Except(
            tests.Join(appts, t => t.name, a => a.testName, (t, a) => t));

以下内容稍微简单易懂一些:

下面是更易读的版本:

var query = tests.Where(t => !appts.Any(a => a.testName == t.name));

但是第一个版本会更快,因为Join函数会计算匹配项的哈希表,而不是在appts列表中为每个tests元素进行线性搜索。


第二个回答对我来说更有意义,所以我决定使用它,但我对你的帖子很好奇。在Where方法中可以制定多个选择条件吗?我想能够比较测试日期,以查看是否可以进行测试,并检查是否已经有预约。当涉及到使用=>运算符时,我仍然是一个新手,所以我不确定如何列出多个条件。 - legacybass
@legacybass:是的,在“Where”部分中可以包含任意多个条件。我发布了这个版本,因为如果您要针对内存中的对象列表而不是创建数据库查询,则更简单(更快)。 - Adam Robinson
那些是用逗号还是分号分隔的?在Join方法中如何使用多个条件?这是我最感兴趣学习如何使用的。 - legacybass
@legacybass:Join函数不像where那样健壮。它的工作原理是创建一个简单的哈希表,通过一个单一的值将两行链接起来。您可以在多个条件上进行连接,但只能在列之间进行简单的等式比较。如果您想要一个具体的例子,最好是提出另一个关于如何在连接子句中执行多个条件的问题,我很乐意回答:) - Adam Robinson
注意大小写:var query = tests.Where(t => !appts.Any(a => a.testName.ToLower() == t.name.ToLower())); - Jon

5

http://introducinglinq.com/blogs/marcorusso/archive/2008/01/14/the-not-in-clause-in-linq-to-sql.aspx

考虑以下代码,它返回在订单表中没有订单的所有客户。这是一个 SQL 查询,可以返回该值。
SELECT *
FROM [dbo].[Customers] AS [t0]
WHERE [t0].[CustomerID] NOT IN (
    SELECT [t1].[CustomerID]
    FROM [dbo].[Orders] AS [t1]
)

这不是获得所需结果的最快方式(使用NOT EXISTS 是更受欢迎的方式——稍后会详细介绍)。LINQ 提供了一个 Contains 扩展方法,允许编写以下代码。
NorthwindDataContext dc = new NorthwindDataContext();
dc.Log = Console.Out;
var query =
    from c in dc.Customers
    where !(from o in dc.Orders
            select o.CustomerID)
           .Contains(c.CustomerID)
    select c;
foreach (var c in query) Console.WriteLine( c );

在LINQ to SQL中,查询会被转化为以下SQL代码:
SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName],
       [t0].[ContactTitle], [t0].[Address], [t0].[City],
       [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [dbo].[Customers] AS [t0]
WHERE NOT (EXISTS(
    SELECT NULL AS [EMPTY]
    FROM [dbo].[Orders] AS [t1]
    WHERE [t1].[CustomerID] = [t0].[CustomerID]
    ))

这种方法不仅在语义上等同,而且执行速度更快。以下是打开SET STATISTICS IO后的结果。第一个结果是使用NOT IN子句的手写查询。第二个结果是由LINQ to SQL生成的查询。


除非我误读了问题,看起来他是在问如何进行客户端过滤(即对象LINQ),而不是如何在SQL中进行过滤。 - Adam Robinson
@Adam - LINQ查询仍然应该起作用,不是吗?他可以使用自己的对象集合,而不是dc.Customers和dc.Orders。 - Jakub Konecki
这实际上是我正在寻找的信息。我必须搜索了三十多个教程和文档网站,但从未找到像这样相关的内容。正是我所需要的。谢谢! - legacybass

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