ChatGPT解决这个技术问题 Extra ChatGPT

LINQ:何时使用 SingleOrDefault 与 FirstOrDefault() 与过滤条件

考虑 IEnumerable 扩展方法 SingleOrDefault()FirstOrDefault()

MSDN documents that SingleOrDefault

返回序列的唯一元素,如果序列为空,则返回默认值;如果序列中有多个元素,此方法将引发异常。

FirstOrDefault from MSDN(大概在使用 OrderBy()OrderByDescending() 或根本不使用时),

返回序列的第一个元素

考虑一些示例查询,何时使用这两种方法并不总是很清楚:

var someCust = db.Customers
.SingleOrDefault(c=>c.ID == 5); //unlikely(?) to be more than one, but technically COULD BE

var bobbyCust = db.Customers
.FirstOrDefault(c=>c.FirstName == "Bobby"); //clearly could be one or many, so use First?

var latestCust = db.Customers
.OrderByDescending(x=> x.CreatedOn)
.FirstOrDefault();//Single or First, or does it matter?

问题

在决定在 LINQ 查询中使用 SingleOrDefault()FirstOrDefault() 时,您遵循或建议哪些约定


s
shashwat

如果您的结果集返回 0 条记录:

SingleOrDefault 返回类型的默认值(例如 int 的默认值为 0)

FirstOrDefault 返回类型的默认值

如果结果集返回 1 条记录:

SingleOrDefault 返回该记录

FirstOrDefault 返回该记录

如果您的结果集返回许多记录:

SingleOrDefault 抛出异常

FirstOrDefault 返回第一条记录

结论:

如果您希望在结果集包含许多记录时引发异常,请使用 SingleOrDefault

如果无论结果集包含什么,您总是想要 1 条记录,请使用 FirstOrDefault


我建议实际上很少需要异常,因此大多数情况下 FirstOrDefault 将是首选。我知道海事组织不会经常存在案例。
FirstOrDefault 返回第一条记录意味着新记录(最后)/旧记录(第一条)?你能澄清一下吗?
@Duk,这取决于您如何对记录进行排序。您可以在调用 FirstOrDefault 之前使用 OrderBy() 或 OrderByDescending() 等。请参阅 OP 的代码示例。
我也喜欢这个答案。特别是考虑到在某些情况下您实际上希望抛出异常,因为您打算在其他地方正确处理这种罕见的情况,而不是假装它没有发生。当您想要异常时,您要清楚地说明这一点,并且还迫使其他人处理只是封装,使整个系统更加健壮。
写的很清楚,让人很容易理解。
W
Wai Ha Lee

每当您使用 SingleOrDefault 时,您都清楚地表明该查询最多应产生一个单个结果。另一方面,当使用 FirstOrDefault 时,查询可以返回任意数量的结果,但您声明您只想要第一个结果。

我个人发现语义非常不同,根据预期结果使用适当的语义可以提高可读性。


一个非常重要的区别是,如果在具有多个元素的序列上使用 SingleOrDefault,它会引发异常。
@kami 如果它没有抛出异常,它将与 FirstOrDefault 完全相同。例外是使它成为 SingleOrDefault 的原因。提出来的好点,并在分歧的棺材上钉上了钉子。
我必须说,从性能方面来看, FirstOrDefault 的工作速度比 SingleOrDefault 快大约 10 倍,它使用包含 9,000,000 个元素的 List ,类包含 2 个整数,而 Func 包含对这两个整数的搜索。在 var v = list.SingleOrDefault(x => x.Id1 == i && x.Id2 == i); 上循环搜索 200 次需要 22 秒和 var v = list.FirstOrDefault(x => x.Id1 == i && x.Id2 == i);约 3 秒
@BitsandBytesHandyman 如果当序列包含多个项目时 SignleOrDefault 没有引发异常,它的行为将与 FirstOrDefault 完全不同。 FirstOrDefault 返回第一项,如果序列为空,则返回 null。 SingleOrDefault 应该返回唯一的项目,如果序列为空或者如果它包含多个项目,则返回 null,而根本不会引发异常。
@RSW 是的,我知道这一点。仔细阅读我的评论,我说的是 SingleOrDefault 应该做什么,而不是它做什么。但当然,它应该做什么,是非常主观的。对我来说,“SomethingOrDefault”模式意味着:获取“Something”的值。如果“某事”未能返回值,则返回默认值。这意味着即使在“Something”会引发异常的情况下也应该返回默认值。所以,在我看来,Single 会抛出异常,SingleOrDefault 应该返回默认值。
S
Stefan Steinegger

语义差异

性能差异

两者之间。

语义差异:

FirstOrDefault 返回可能多个的第一项(如果不存在,则返回默认值)。

SingleOrDefault 假定存在单个项目并返回它(如果不存在,则返回默认值)。多个项目违反合同,抛出异常。

性能差异

FirstOrDefault 通常更快,它会迭代直到找到元素,并且只有在找不到元素时才需要迭代整个可枚举。在许多情况下,找到一个项目的概率很高。

SingleOrDefault 需要检查是否只有一个元素,因此总是迭代整个可枚举。准确地说,它会迭代直到找到第二个元素并引发异常。但在大多数情况下,没有第二个元素。

结论

如果您不在乎有多少项目或当您无法检查唯一性时(例如在非常大的集合中),请使用 FirstOrDefault。当您在将项目添加到集合时检查唯一性时,在搜索这些项目时再次检查可能太昂贵了。

如果您不必过多关心性能并希望确保单个项目的假设对读者来说是清楚的并在运行时检查,请使用 SingleOrDefault。

实际上,即使在假设单个项目的情况下,您也经常使用 First / FirstOrDefault 来提高性能。您仍然应该记住 Single / SingleOrDefault 可以提高可读性(因为它说明了单个项目的假设)和稳定性(因为它检查它)并适当地使用它。


+1“或者当你负担不起检查唯一性时(例如在一个非常大的集合中)。”。我在找这个。我还会在插入时添加强制唯一性,或/和通过设计而不是在进行查询时!
@memetolsen 考虑使用 LINQ to SQL 为这两者吐出的代码 - FirstOrDefault 使用 Top 1。 SingleOrDefault 使用 Top 2。
@memetolsen 就原始答案而言是正确的,您的评论指的是数据库,所以我提供了提供商所发生的事情。虽然 .Net 代码仅迭代 2 个值,但数据库会访问尽可能多的记录,直到找到满足条件的第二个记录。
关于 SingleOrDefault 的性能注意事项:它不必遍历整个集合。它只是查看返回的结果是否包含多个元素。所以,我认为处理时间是卑鄙的。
“SingleOrDefault 需要检查是否只有一个元素,因此总是迭代整个可枚举”这是不正确的;它只需要知道可枚举是否包含“多个”元素,因此只需检索前两个元素就足够了。您可以在 github.com/microsoft/referencesource/blob/master/System.Core/… 的源代码中看到没有循环,只有对 enumerator.MoveNext() 的两次调用
s
shalke

没有人提到过 SQL 翻译的 FirstOrDefault 做 TOP 1 记录,SingleOrDefault 做 TOP 2,因为它需要知道是否有超过 1 条记录。


当我通过 LinqPad 和 VS 运行 SingleOrDefault 时,我从来没有得到 SELECT TOP 2,使用 FirstOrDefault 我能够得到 SELECT TOP 1,但据我所知,你没有得到 SELECT TOP 2。
嘿,我也在 linqpad 中尝试过,sql 查询让我害怕,因为它完全获取所有行。我不确定这怎么会发生?
这完全取决于使用的 LINQ 提供程序。例如,LINQ to SQL 和 LINQ to Entities 可以以不同的方式转换为 SQL。我刚刚使用 IQ MySql 提供程序尝试了 LINQPad,FirstOrDefault() 添加了 LIMIT 0,1,而 SingleOrDefault() 没有添加任何内容。
EF Core 2.1 将 FirstOrDefault 转换为 SELECT TOP(1),将 SingleOrDefault 转换为 SELECT TOP(2)
如果您指定 SingleOrDefault,数据库会执行 SELECT TOP(2),以便 .NET 可以在有多个结果时出现异常。
P
Prakash

对于 LINQ -> SQL:

单一或默认

将生成类似“select * from users where userid = 1”的查询

选择匹配记录,如果找到多个记录,则抛出异常

如果您基于主/唯一键列获取数据,请使用

第一或默认

将生成类似“select top 1 * from users where userid = 1”的查询

选择第一个匹配的行

如果您基于非主键/唯一键列获取数据,请使用


我认为您应该从 SingleOrDefault 中删除“选择所有匹配的行”
s
spender

在我的逻辑要求结果为零或一的情况下,我使用 SingleOrDefault。如果还有更多,这是一个错误情况,这很有帮助。


我经常发现 SingleOrDefault() 会突出显示我没有对结果集应用正确过滤的情况,或者基础数据中存在重复问题的情况。我发现自己经常使用 Single() 和 SingleOrDefault() 而不是 First() 方法。
如果您有大量可枚举但在与数据库(例如 SQL Server)交谈时,它将执行前 2 次调用,并且如果您的索引设置正确,则 LINQ to Objects 上的 Single() 和 SingleOrDefault() 会产生性能影响调用不应该很昂贵,我宁愿快速失败并找到数据问题,而不是在调用 First() 或 FirstOrDefault() 时通过获取错误的副本可能引入其他数据问题。
O
Olli

在您的情况下,我将使用以下内容:

按 ID==5 选择:在这里使用 SingleOrDefault 是可以的,因为您希望有一个 [或无] 实体,如果您有多个 ID 为 5 的实体,则存在错误并且绝对值得例外。

当搜索名字等于“Bobby”的人时,可能会有多个(我认为很可能),所以你不应该使用 Single 或 First,只需使用 Where 操作选择(如果“Bobby”返回太多实体,用户必须细化他的搜索或选择返回的结果之一)

创建日期的顺序也应该使用 Where 操作执行(不太可能只有一个实体,排序不会有太大用处;)但这意味着您希望对所有实体进行排序 - 如果您只想要一个,请使用 FirstOrDefault,如果你有多个实体,Single 每次都会抛出。


我不同意。如果您的数据库 ID 是主键,那么数据库已经强制执行唯一性。浪费 CPU 周期来检查数据库是否在每个查询上都完成了它的工作是愚蠢的。
S
Steven

SingleOrDefault:您是说“最多”有一项与查询或默认匹配 FirstOrDefault:您是说“至少”一项与查询或默认匹配

下次你需要选择时大声说出来,你可能会明智地选择。 :)


实际上没有结果是完全可以接受的使用 FirstOrDefault. More correctly: FirstOrDefault` = 任意数量的结果,但我只关心第一个,也可能没有结果。 SingleOrDefault = 有 1 个或 0 个结果,如果有更多则意味着某处有错误。 First = 至少有一个结果,我想要它。 Single = 正好有 1 个结果,不多也不少,我想要那个。
k
karlingen

两者都是元素运算符,它们用于从序列中选择单个元素。但它们之间有细微的差别。如果满足多个元素的条件,SingleOrDefault() 运算符将抛出异常,而 FirstOrDefault() 不会为相同的元素抛出任何异常。这是示例。

List<int> items = new List<int>() {9,10,9};
//Returns the first element of a sequence after satisfied the condition more than one elements
int result1 = items.Where(item => item == 9).FirstOrDefault();
//Throw the exception after satisfied the condition more than one elements
int result3 = items.Where(item => item == 9).SingleOrDefault();

“它们之间存在细微差别” - 这是主要的!
b
bytebender

在你的最后一个例子中:

var latestCust = db.Customers
.OrderByDescending(x=> x.CreatedOn)
.FirstOrDefault();//Single or First, or doesn't matter?

是的,它确实。如果您尝试使用 SingleOrDefault() 并且查询结果超过记录,您将获得异常。您可以安全使用 SingleOrDefault() 的唯一时间是当您期望只有 1 且只有 1 个结果时......


这是正确的。如果你得到 0 结果,你也会得到一个异常。
N
Nikolay Kostov

因此,据我所知,如果您要查询保证唯一的数据(即由主键等数据库约束强制执行),SingleOrDefault 会很好。

或者有没有更好的查询主键的方法。

假设我的 TableAcc 有

AccountNumber - Primary Key, integer
AccountName
AccountOpenedDate
AccountIsActive
etc.

我想查询 AccountNumber 987654,我使用

var data = datacontext.TableAcc.FirstOrDefault(obj => obj.AccountNumber == 987654);

V
Vasil Kosturski

在我看来,FirstOrDefault 被过度使用了。在大多数情况下,当您过滤数据时,您要么期望返回与逻辑条件匹配的元素集合,要么通过其唯一标识符获取单个唯一元素——例如用户、书籍、帖子等......为什么我们甚至可以说 FirstOrDefault() 是一种代码味道,不是因为它有问题,而是因为它被使用得太频繁了。 This blog post 详细探讨了该主题。在大多数情况下,IMO SingleOrDefault() 是一个更好的选择,因此请注意这个错误,并确保您使用最合适的方法,清楚地代表您的合同和期望。


E
El Dude

回复中遗漏的一件事....

如果有多个结果,没有 order by 的 FirstOrDefault 可以根据服务器碰巧使用的索引策略带回不同的结果。

就我个人而言,我无法忍受在代码中看到 FirstOrDefault,因为对我来说它表示开发人员并不关心结果。使用命令虽然它可以用作执行最新/最早的一种方式。我不得不纠正许多由粗心的开发人员使用 FirstOrDefault 引起的问题。


P
PinoyDev

从本质上讲,这为您提供了某种验证来清理您的数据,如果您选择其中一个,它将同时提供数据,但 SingleOrDefault 会让您意识到,当您期望的数据应该只有 1 个结果并吐出时更多 1 那么你需要看看为什么你的存储过程或查询会导致这样的重复项在查询中永远不会好。


F
Fred

我向 Google 查询了 GitHub 上不同方法的使用情况。这是通过为每种方法运行 Google 搜索查询并使用查询“site:github.com file:cs ...”将查询限制为 github.com 域和 .cs 文件扩展名来完成的。

似乎 First* 方法比 Single* 方法更常用。

| Method               | Results |
|----------------------|---------|
| FirstAsync           |     315 |
| SingleAsync          |     166 |
| FirstOrDefaultAsync  |     357 |
| SingleOrDefaultAsync |     237 |
| FirstOrDefault       |   17400 |
| SingleOrDefault      |    2950 |

有趣的答案,但希望这并不意味着选择受欢迎程度
T
Theron Govender

我不明白您为什么要使用 FirstOrDefault(x=> x.ID == key),如果您使用 Find(key),它可以更快地检索结果。如果您使用表的主键进行查询,经验法则是始终使用 Find(key)FirstOrDefault 应该用于 (x=> x.Username == username) 等谓词。

这不值得被否决,因为问题的标题并非特定于 DB 上的 linq 或 Linq to List/IEnumerable 等。


Find() 在哪个命名空间中?
你能告诉我们吗?仍在等待答案。
“IEnumerable”一词位于问题正文的第一行。如果您只阅读标题而不是实际问题,并因此发布了不正确的答案,那么这就是您的错误,也是对 IMO 投反对票的完全正当理由。