当我确定查询只会返回一个记录时,使用 Single() 操作符比 First() 操作符更有效率吗?
这两个操作符有什么区别吗?
我知道其他人已经写过为什么会使用其中之一,但我认为我可以说明一下当你想要使用其中一个时为什么不应该使用另一个。
注意:在我的代码中,我通常会使用FirstOrDefault()
和SingleOrDefault()
,但那是另一个问题。
例如,考虑一个使用复合键(ID
,Lang
)存储不同语言客户的表:
DBContext db = new DBContext();
Customer customer = db.Customers.Where( c=> c.ID == 5 ).First();
以上代码可能存在一个潜在的逻辑错误(难以追踪)。如果您有多种语言的客户记录,它将返回多个记录,但始终仅返回第一个记录... 这可能有时有效... 但有时无效。它是不可预测的。
由于您的意图是返回单个 Customer
,请使用 Single()
;
以下代码将抛出异常(在此情况下这正是您想要的):
DBContext db = new DBContext();
Customer customer = db.Customers.Where( c=> c.ID == 5 ).Single();
然后,你只需要轻轻地拍打自己的额头并对自己说... 呜呼!我忘记了语言字段!以下是正确的版本:
DBContext db = new DBContext();
Customer customer = db.Customers.Where( c=> c.ID == 5 && c.Lang == "en" ).Single();
First()
在以下情况下非常有用:
DBContext db = new DBContext();
NewsItem newsitem = db.NewsItems.OrderByDescending( n => n.AddedDate ).First();
它会返回一个对象,由于您使用了排序,因此将返回最近的记录。
当您需要显式地始终返回1条记录时,使用Single()
可以帮助避免逻辑错误。
如果Single方法找到多于一个匹配标准的记录,它将抛出异常。
First方法总是从列表中选择第一条记录。如果查询返回的只有一条记录,您可以使用 First()
。
如果集合为空,Both方法将抛出InvalidOperationException
异常。
或者,您可以使用SingleOrDefault()
。如果列表为空,它不会抛出异常。
Single()
返回查询结果中的单个元素。
使用场景: 当期望只有一个元素时,而不是0个或多个元素。如果列表为空或包含多个元素,则会抛出异常“序列包含多个元素”。
SingleOrDefault()
返回查询结果中的单个元素,如果没有结果则返回默认值。
使用场景: 当期望0个或1个元素时。如果列表包含2个或更多项,则会抛出异常。
First()
返回查询结果中的第一个元素。
使用场景: 当期望有1个或多个元素时,但只需要第一个元素时。如果列表不包含元素,则会抛出异常。
FirstOrDefault()
返回列表中的第一个元素,如果列表为空则返回默认值。
使用场景: 当期望有多个元素时,但只需要第一个元素时。或者当列表为空时,需要指定类型的默认值,与
default(MyObjectType)
相同。例如:如果列表类型是list<int>
,则返回列表中的第一个数字,如果列表为空,则返回0。如果是list<string>
,则返回列表中的第一个字符串,如果列表为空,则返回null。
First
,而不仅仅是“多于1个”,而FirstOrDefault
则适用于任意数量的元素。 - Andrew若不需要在有多个项的情况下抛出异常,请使用 First()
。
二者都是高效的,都取第一项。因为 First()
不会检查是否有第二项,所以它略微更加高效。
唯一的区别是,Single()
假设枚举中只有一个项,并在存在多个项时抛出异常。在这种情况下,您可以使用.Single()
指定抛出异常。
这两种方法之间有微妙的语义差别。
使用 Single
从只包含一个元素的序列中检索第一个(且唯一)元素。如果序列有多个元素,则会抛出异常,因为你指定了应该只有一个元素。
使用 First
从可以包含任意数量元素的序列中检索第一个元素。如果序列有多个元素,则不会抛出异常,因为你指定了只需要序列中的第一个元素,而不关心是否存在更多元素。
如果序列不包含任何元素,则这两种方法调用都会引发异常,因为两种方法都期望至少有一个元素存在。
如果我没记错的话,Single()会检查第一个元素后是否还有其他元素(如果是,则抛出异常),而First()则在获取第一个元素后停止。如果序列为空,两者都会抛出异常。
个人而言,我总是使用First()。
关于性能:我和一位同事正在讨论 Single vs First(或者SingleOrDefault vs FirstOrDefault)的性能, 我辩称使用 First (或者FirstOrDefault)可以更快、提高性能 (因为我非常注重让我们的应用程序运行更快)。
我在 Stack Overflow 上读了几篇有争议的帖子。有些人说使用 First 代替 Single 可以获得轻微的性能提升。这是因为 First 只会返回第一个项目,而 Single 必须扫描所有结果以确保没有重复项(例如:如果它在表的第一行找到该项,则仍将扫描每一行以确保没有第二个满足条件的值,否则将引发错误)。我认为使用 "First" 比 "Single" 更快,所以我开始尝试证明它,并结束这场辩论。
我在数据库中设置了一个测试,并添加了 1,000,000 行数据
ID UniqueIdentifier Foreign UniqueIdentifier Info nvarchar(50)(填充数字“0”到“999,9999”的字符串)我加载了数据,并将 ID 设置为主键字段。
通过 LinqPad,我的目标是展示,如果你使用 Single 在 'Foreign' 或 'Info'上搜索值,那么它比使用 First 要慢得多。
我无法解释我得到的结果。几乎在所有情况下,使用 Single 或者SingleOrDefault 都比使用 First 稍微快一点。这对我来说毫无逻辑意义,但是我想分享一下。
例如:我使用了以下查询:
var q = TestTables.First(x=>x.Info == "314638") ;
//Vs.
Var q = TestTables.Single(x=>x.Info =="314638") ; //(this was slightly faster to my surprise)
我尝试对未被索引的“Foreign”键字段进行类似的查询,认为这样可以证明First更快,但在我的测试中Single始终略微更快。
First
,然后再测试Single
,那么一切可能已经在缓存中,因此计算所需的时间更少。对于转换为SQL的查询,请注意First
将转换为SELECT TOP 1
,而Single
将转换为SELECT TOP 2
,以验证是否只找到一个。这可能会对查询规划器决定查询计划以及复杂查询产生微妙的影响。我曾经看到过这种情况,其中一个查询需要几秒钟,而另一个查询则需要很长时间。 - Dave Van den Eynde List<int> records = new List<int>{1,1,3,4,5,6};
var record = records.First(x => x == 1);
record = records.Single(x => x == 1);
它们是不同的。两者都断言结果集不为空,但Single还断言结果不超过1个。在我看来,当我只期望有一个结果时,我会使用Single,因为返回多个结果是错误的,可能应该被视为这样。
我认识很多人都使用FirstOrDefault(),但我更倾向于使用SingleOrDefault(),因为如果存在多个元素,则通常会出现某种数据不一致性。这是处理LINQ-to-Objects的情况。