【问题标题】:LINQ: When to use SingleOrDefault vs. FirstOrDefault() with filtering criteriaLINQ:何时使用 SingleOrDefault 与 FirstOrDefault() 与过滤条件
【发布时间】:2010-12-17 06:30:32
【问题描述】:

考虑 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() 时,您遵循或建议哪些约定

【问题讨论】:

    标签: .net linq linq-to-sql


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        【解决方案3】:

        我向 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 |
        

        【讨论】:

        • 有趣的答案,但希望它并不意味着选择受欢迎程度
        【解决方案4】:

        每当您使用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 应该返回默认值。
        【解决方案5】:

        对于 LINQ -> SQL:

        SingleOrDefault

        • 将生成类似“select * from users where userid = 1”的查询
        • 选择匹配的记录,如果找到多个记录则抛出异常
        • 如果您基于主键/唯一键列获取数据,请使用

        FirstOrDefault

        • 将生成类似“select top 1 * from users where userid = 1”的查询
        • 选择第一个匹配的行
        • 如果您基于非主键/唯一键列获取数据,请使用

        【讨论】:

        • 我认为你应该从 SingleOrDefault 中删除“选择所有匹配的行”
        【解决方案6】:

        两者都是元素运算符,它们用于从序列中选择单个元素。但它们之间存在细微差别。如果满足多个元素的条件,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();
        

        【讨论】:

        • “它们之间有细微的差别” - 这是主要的!
        【解决方案7】:

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

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

        【讨论】:

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

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

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

        假设我的 TableAcc 有

        AccountNumber - Primary Key, integer
        AccountName
        AccountOpenedDate
        AccountIsActive
        etc.
        

        我想查询AccountNumber 987654,我使用

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

        【讨论】:

          【解决方案9】:

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

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

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

          【讨论】:

            【解决方案10】:

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

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

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

            • SingleOrDefault 返回该记录
            • FirstOrDefault 返回该记录

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

            • SingleOrDefault 抛出异常
            • FirstOrDefault 返回第一条记录

            结论:

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

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

            【讨论】:

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

            • 语义差异
            • 性能差异

            两者之间。

            语义差异:

            • 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()
            【解决方案12】:

            没有人提到过 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,1SingleOrDefault() 什么也没添加。
            • EF Core 2.1 将 FirstOrDefault 转换为 SELECT TOP(1),SingleOrDefault 转换为 SELECT TOP(2)
            • 如果您指定 SingleOrDefault,数据库会执行 SELECT TOP(2),以便 .NET 可以在有多个结果时出现异常。
            【解决方案13】:

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

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

            【讨论】:

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

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

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

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

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

            【讨论】:

            • 我不同意。如果您的数据库 ID 是主键,那么数据库已经强制执行唯一性。浪费 CPU 周期来检查数据库是否在每个查询中都完成了它的工作是愚蠢的。
            【解决方案15】:

            在你的最后一个例子中:

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

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

            【讨论】:

            • 没错。如果你得到 0 结果,你也会得到一个异常。
            【解决方案16】:

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

            【讨论】:

            • 我经常发现 SingleOrDefault() 会突出显示我没有对结果集应用正确过滤的情况,或者基础数据中存在重复问题的情况。我发现自己经常使用 Single() 和 SingleOrDefault() 而不是 First() 方法。
            • 如果您有大量可枚举但在与数据库(例如 SQL Server)对话时,它将执行前 2 个调用,并且如果你的索引设置正确,调用不应该很昂贵,我宁愿快速失败并找到数据问题,而不是在调用 First() 或 FirstOrDefault() 时通过获取错误的副本来可能引入其他数据问题。跨度>
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-01-10
            • 2011-12-01
            • 1970-01-01
            • 2010-11-04
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多