考虑 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()
时,您遵循或建议哪些约定?
如果您的结果集返回 0 条记录:
SingleOrDefault 返回类型的默认值(例如 int 的默认值为 0)
FirstOrDefault 返回类型的默认值
如果结果集返回 1 条记录:
SingleOrDefault 返回该记录
FirstOrDefault 返回该记录
如果您的结果集返回许多记录:
SingleOrDefault 抛出异常
FirstOrDefault 返回第一条记录
结论:
如果您希望在结果集包含许多记录时引发异常,请使用 SingleOrDefault
。
如果无论结果集包含什么,您总是想要 1 条记录,请使用 FirstOrDefault
每当您使用 SingleOrDefault
时,您都清楚地表明该查询最多应产生一个单个结果。另一方面,当使用 FirstOrDefault
时,查询可以返回任意数量的结果,但您声明您只想要第一个结果。
我个人发现语义非常不同,根据预期结果使用适当的语义可以提高可读性。
有
语义差异
性能差异
两者之间。
语义差异:
FirstOrDefault 返回可能多个的第一项(如果不存在,则返回默认值)。
SingleOrDefault 假定存在单个项目并返回它(如果不存在,则返回默认值)。多个项目违反合同,抛出异常。
性能差异
FirstOrDefault 通常更快,它会迭代直到找到元素,并且只有在找不到元素时才需要迭代整个可枚举。在许多情况下,找到一个项目的概率很高。
SingleOrDefault 需要检查是否只有一个元素,因此总是迭代整个可枚举。准确地说,它会迭代直到找到第二个元素并引发异常。但在大多数情况下,没有第二个元素。
结论
如果您不在乎有多少项目或当您无法检查唯一性时(例如在非常大的集合中),请使用 FirstOrDefault。当您在将项目添加到集合时检查唯一性时,在搜索这些项目时再次检查可能太昂贵了。
如果您不必过多关心性能并希望确保单个项目的假设对读者来说是清楚的并在运行时检查,请使用 SingleOrDefault。
实际上,即使在假设单个项目的情况下,您也经常使用 First
/ FirstOrDefault
来提高性能。您仍然应该记住 Single
/ SingleOrDefault
可以提高可读性(因为它说明了单个项目的假设)和稳定性(因为它检查它)并适当地使用它。
enumerator.MoveNext()
的两次调用
没有人提到过 SQL 翻译的 FirstOrDefault 做 TOP 1 记录,SingleOrDefault 做 TOP 2,因为它需要知道是否有超过 1 条记录。
FirstOrDefault()
添加了 LIMIT 0,1
,而 SingleOrDefault()
没有添加任何内容。
对于 LINQ -> SQL:
单一或默认
将生成类似“select * from users where userid = 1”的查询
选择匹配记录,如果找到多个记录,则抛出异常
如果您基于主/唯一键列获取数据,请使用
第一或默认
将生成类似“select top 1 * from users where userid = 1”的查询
选择第一个匹配的行
如果您基于非主键/唯一键列获取数据,请使用
在我的逻辑要求结果为零或一的情况下,我使用 SingleOrDefault
。如果还有更多,这是一个错误情况,这很有帮助。
在您的情况下,我将使用以下内容:
按 ID==5 选择:在这里使用 SingleOrDefault 是可以的,因为您希望有一个 [或无] 实体,如果您有多个 ID 为 5 的实体,则存在错误并且绝对值得例外。
当搜索名字等于“Bobby”的人时,可能会有多个(我认为很可能),所以你不应该使用 Single 或 First,只需使用 Where 操作选择(如果“Bobby”返回太多实体,用户必须细化他的搜索或选择返回的结果之一)
创建日期的顺序也应该使用 Where 操作执行(不太可能只有一个实体,排序不会有太大用处;)但这意味着您希望对所有实体进行排序 - 如果您只想要一个,请使用 FirstOrDefault,如果你有多个实体,Single 每次都会抛出。
SingleOrDefault:您是说“最多”有一项与查询或默认匹配 FirstOrDefault:您是说“至少”一项与查询或默认匹配
下次你需要选择时大声说出来,你可能会明智地选择。 :)
FirstOrDefault. More correctly:
FirstOrDefault` = 任意数量的结果,但我只关心第一个,也可能没有结果。 SingleOrDefault
= 有 1 个或 0 个结果,如果有更多则意味着某处有错误。 First
= 至少有一个结果,我想要它。 Single
= 正好有 1 个结果,不多也不少,我想要那个。
两者都是元素运算符,它们用于从序列中选择单个元素。但它们之间有细微的差别。如果满足多个元素的条件,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();
在你的最后一个例子中:
var latestCust = db.Customers
.OrderByDescending(x=> x.CreatedOn)
.FirstOrDefault();//Single or First, or doesn't matter?
是的,它确实。如果您尝试使用 SingleOrDefault()
并且查询结果超过记录,您将获得异常。您可以安全使用 SingleOrDefault()
的唯一时间是当您期望只有 1 且只有 1 个结果时......
因此,据我所知,如果您要查询保证唯一的数据(即由主键等数据库约束强制执行),SingleOrDefault
会很好。
或者有没有更好的查询主键的方法。
假设我的 TableAcc 有
AccountNumber - Primary Key, integer
AccountName
AccountOpenedDate
AccountIsActive
etc.
我想查询 AccountNumber 987654
,我使用
var data = datacontext.TableAcc.FirstOrDefault(obj => obj.AccountNumber == 987654);
在我看来,FirstOrDefault
被过度使用了。在大多数情况下,当您过滤数据时,您要么期望返回与逻辑条件匹配的元素集合,要么通过其唯一标识符获取单个唯一元素——例如用户、书籍、帖子等......为什么我们甚至可以说 FirstOrDefault()
是一种代码味道,不是因为它有问题,而是因为它被使用得太频繁了。 This blog post 详细探讨了该主题。在大多数情况下,IMO SingleOrDefault()
是一个更好的选择,因此请注意这个错误,并确保您使用最合适的方法,清楚地代表您的合同和期望。
回复中遗漏的一件事....
如果有多个结果,没有 order by 的 FirstOrDefault 可以根据服务器碰巧使用的索引策略带回不同的结果。
就我个人而言,我无法忍受在代码中看到 FirstOrDefault,因为对我来说它表示开发人员并不关心结果。使用命令虽然它可以用作执行最新/最早的一种方式。我不得不纠正许多由粗心的开发人员使用 FirstOrDefault 引起的问题。
从本质上讲,这为您提供了某种验证来清理您的数据,如果您选择其中一个,它将同时提供数据,但 SingleOrDefault 会让您意识到,当您期望的数据应该只有 1 个结果并吐出时更多 1 那么你需要看看为什么你的存储过程或查询会导致这样的重复项在查询中永远不会好。
我向 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 |
我不明白您为什么要使用 FirstOrDefault(x=> x.ID == key)
,如果您使用 Find(key)
,它可以更快地检索结果。如果您使用表的主键进行查询,经验法则是始终使用 Find(key)
。 FirstOrDefault
应该用于 (x=> x.Username == username)
等谓词。
这不值得被否决,因为问题的标题并非特定于 DB 上的 linq 或 Linq to List/IEnumerable 等。
Find()
在哪个命名空间中?
FirstOrDefault
返回第一条记录意味着新记录(最后)/旧记录(第一条)?你能澄清一下吗?