LINQ: Khi nào nên sử dụng SingleOrDefault so với FirstOrDefault () với tiêu chí lọc


506

Hãy xem xét các phương thức mở rộng IEnumerable SingleOrDefault()FirstOrDefault()

Tài liệu MSDNSingleOrDefault :

Trả về phần tử duy nhất của chuỗi hoặc giá trị mặc định nếu chuỗi trống; phương thức này đưa ra một ngoại lệ nếu có nhiều hơn một phần tử trong chuỗi.

trong khi FirstOrDefaulttừ MSDN (có lẽ khi sử dụng một OrderBy()hoặc OrderByDescending()hoặc không có gì cả),

Trả về phần tử đầu tiên của chuỗi

Hãy xem xét một số ít các truy vấn mẫu, không phải lúc nào cũng rõ ràng khi sử dụng hai phương thức này:

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?

Câu hỏi

Những quy ước nào bạn tuân theo hoặc đề xuất khi quyết định sử dụng SingleOrDefault()FirstOrDefault()trong các truy vấn LINQ của bạn?

Câu trả lời:


466

Bất cứ khi nào bạn sử dụng SingleOrDefault, bạn nêu rõ rằng các truy vấn nên kết quả trong ít nhất một đơn kết quả. Mặt khác, khi FirstOrDefaultđược sử dụng, truy vấn có thể trả về bất kỳ số lượng kết quả nào nhưng bạn nói rằng bạn chỉ muốn kết quả đầu tiên.

Cá nhân tôi thấy ngữ nghĩa rất khác nhau và sử dụng một ngữ nghĩa phù hợp, tùy thuộc vào kết quả mong đợi, cải thiện khả năng đọc.


164
Một sự khác biệt rất quan trọng là nếu bạn sử dụng SingleOrDefault trên một chuỗi có nhiều hơn một phần tử, nó sẽ ném ra một ngoại lệ.
Kamran Bigdely

17
@kami nếu nó không ném ngoại lệ, nó sẽ giống hệt như FirstOrDefault. Ngoại lệ là những gì làm cho nó SingleOrDefault. Điểm tốt đưa nó lên, và đặt một cái đinh trên quan tài của sự khác biệt.
Fabio S.

17
Tôi phải nói rằng từ hiệu năng khôn ngoan, FirstOrDefault đang hoạt động nhanh hơn khoảng 10 lần so với SingleOrDefault, sử dụng Danh sách <MyClass> gồm 9.000.000 phần tử, lớp chứa 2 số nguyên và Func chứa tìm kiếm hai số nguyên này. Tìm kiếm 200 lần trong một vòng lặp mất 22 giây trên var v = list.SingleOrDefault (x => x.Id1 == i && x.Id2 == i); và var v = list.FirstOrDefault (x => x.Id1 == i && x.Id2 == i); khoảng 3 giây
Chen

6
@BitsandBytesHandyman Nếu SignleOrDefault không đưa ra một ngoại lệ khi chuỗi chứa nhiều hơn một mục thì nó sẽ không hoạt động chính xác như FirstOrDefault. FirstOrDefault trả về mục đầu tiên hoặc null nếu chuỗi trống. SingleOrDefault sẽ trả về mục duy nhất hoặc null nếu chuỗi trống HOẶC nếu nó chứa nhiều hơn một mục, mà không ném ngoại lệ nào cả.
Thanocation Ioannidis

2
@RSW Vâng, tôi biết điều đó. Đọc kỹ nhận xét của tôi, tôi đã nói những gì SingleOrDefault nên làm, không phải những gì nó làm. Nhưng tất nhiên, những gì nó nên làm, là rất chủ quan. Đối với tôi, mẫu "SomethingOrDefault" có nghĩa là: Nhận giá trị của "Cái gì đó". Nếu "Cái gì đó" không trả về giá trị, hãy trả về giá trị mặc định. Điều đó có nghĩa là giá trị mặc định sẽ được trả về ngay cả trong trường hợp "Cái gì đó" sẽ ném ngoại lệ. Vì vậy, trong trường hợp Single sẽ đưa ra một ngoại lệ, SingleOrDefault sẽ trả về giá trị mặc định, theo quan điểm của tôi.
Thanocation Ioannidis

585

Nếu tập kết quả của bạn trả về 0 bản ghi:

  • SingleOrDefault trả về giá trị mặc định cho loại (ví dụ: mặc định cho int là 0)
  • FirstOrDefault trả về giá trị mặc định cho loại

Nếu bạn đặt kết quả trả về 1 bản ghi:

  • SingleOrDefault trả lại hồ sơ đó
  • FirstOrDefault trả lại hồ sơ đó

Nếu tập kết quả của bạn trả về nhiều bản ghi:

  • SingleOrDefault ném một ngoại lệ
  • FirstOrDefault trả về bản ghi đầu tiên

Phần kết luận:

Nếu bạn muốn ném ngoại lệ nếu tập kết quả chứa nhiều bản ghi, hãy sử dụng SingleOrDefault.

Nếu bạn luôn muốn 1 bản ghi bất kể tập kết quả chứa gì, hãy sử dụng FirstOrDefault


6
Tôi sẽ đề nghị rằng hiếm khi thực sự muốn có ngoại lệ, vì vậy hầu hết thời gian FirstOrDefault sẽ được ưu tiên. Tôi biết trường hợp sẽ tồn tại không thường xuyên mà imo.
MikeKulls

FirstOrDefaultđược trả lại bản ghi đầu tiên có nghĩa là bản ghi mới (bản cuối) / bản ghi cũ (bản đầu tiên)? Bạn có thể làm rõ tôi không?
Duk

@Duk, nó phụ thuộc vào cách bạn sắp xếp các hồ sơ. Bạn có thể sử dụng OrderBy () hoặc OrderByDesceinating (), v.v. trước khi gọi FirstOrDefault. Xem ví dụ mã của OP.
Gan

5
Tôi cũng thích câu trả lời này. Đặc biệt là có một số trường hợp bạn thực sự muốn ném ngoại lệ vì bạn có ý định xử lý trường hợp hiếm gặp đó ở nơi khác chứ không phải giả vờ như nó không xảy ra. Khi bạn muốn có ngoại lệ, bạn đang nói điều này một cách rõ ràng và cũng buộc người khác phải xử lý chỉ cần làm cho hệ thống tổng thể mạnh mẽ hơn.
Francis Rodgers

Nó được nêu rất rõ ràng để người ta có thể dễ dàng hiểu được.
Nirav Vasoya

244

  • một sự khác biệt về ngữ nghĩa
  • một sự khác biệt hiệu suất

giữa hai người.

Sự khác biệt về ngữ nghĩa:

  • FirstOrDefault trả về một mục đầu tiên có khả năng nhiều (hoặc mặc định nếu không tồn tại).
  • SingleOrDefaultgiả định rằng có một mục duy nhất và trả về nó (hoặc mặc định nếu không tồn tại). Nhiều mặt hàng là vi phạm hợp đồng, một ngoại lệ được ném ra.

Hiệu suất khác biệt

  • FirstOrDefaultthường nhanh hơn, nó lặp đi lặp lại cho đến khi tìm thấy phần tử và chỉ phải lặp lại toàn bộ vô số khi nó không tìm thấy nó. Trong nhiều trường hợp, có xác suất cao để tìm một mục.

  • SingleOrDefaultcần kiểm tra nếu chỉ có một phần tử và do đó luôn lặp lại toàn bộ vô số. Nói chính xác, nó lặp đi lặp lại cho đến khi tìm thấy phần tử thứ hai và ném ngoại lệ. Nhưng trong hầu hết các trường hợp, không có yếu tố thứ hai.

Phần kết luận

  • Sử dụng FirstOrDefaultnếu bạn không quan tâm có bao nhiêu mặt hàng hoặc khi bạn không đủ khả năng kiểm tra tính duy nhất (ví dụ: trong một bộ sưu tập rất lớn). Khi bạn kiểm tra tính duy nhất khi thêm các mục vào bộ sưu tập, có thể quá tốn kém để kiểm tra lại khi tìm kiếm các mục đó.

  • Sử dụng SingleOrDefaultnếu bạn không phải quan tâm đến hiệu suất quá nhiều và muốn đảm bảo rằng giả định của một mục duy nhất rõ ràng với người đọc và được kiểm tra khi chạy.

Trong thực tế, bạn sử dụng First/ FirstOrDefaultthường xuyên ngay cả trong trường hợp khi bạn giả sử một mục duy nhất, để cải thiện hiệu suất. Bạn vẫn nên nhớ rằng Single/ SingleOrDefaultcó thể cải thiện khả năng đọc (vì nó nêu giả định của một mục duy nhất) và tính ổn định (vì nó kiểm tra nó) và sử dụng nó một cách thích hợp.


16
+1 "hoặc khi bạn không đủ khả năng kiểm tra tính duy nhất (ví dụ: trong một bộ sưu tập rất lớn)." . Tôi vẫn đang tìm kiếm điều này. Tôi cũng sẽ thêm tính duy nhất bắt buộc khi chèn, hoặc / và theo thiết kế thay vì tại thời điểm thực hiện truy vấn!
Nawaz

Tôi có thể tưởng tượng việc SingleOrDefaultlặp lại qua rất nhiều đối tượng khi sử dụng Linq cho các đối tượng, nhưng không phải SingleOrDefaultlặp lại nhiều nhất là 2 mục nếu Linq đang nói chuyện với cơ sở dữ liệu chẳng hạn? Chỉ cần tự hỏi ..
Memet Olsen

3
@memetolsen Hãy xem xét việc nhổ mã cho hai người với LINQ to SQL - FirstOrDefault sử dụng Top 1. SingleOrDefault sử dụng Top 2.
Jim Wooley

@JimWooley Tôi đoán là tôi đã hiểu nhầm từ 'vô số'. Tôi nghĩ Stefan có nghĩa là C # Enumerable.
Memet Olsen

1
@memetolsen đúng về câu trả lời ban đầu, bình luận của bạn đã đề cập đến cơ sở dữ liệu, vì vậy tôi đã cung cấp những gì xảy ra từ nhà cung cấp. Mặc dù mã .Net chỉ lặp lại trên 2 giá trị, cơ sở dữ liệu truy cập nhiều bản ghi cần thiết cho đến khi nó đạt đến giá trị thứ hai đáp ứng tiêu chí.
Jim Wooley

76

Không ai đã đề cập rằng FirstOrDefault được dịch trong SQL thực hiện bản ghi TOP 1 và SingleOrDefault thực hiện TOP 2, bởi vì nó cần phải biết có nhiều hơn 1 bản ghi.


3
Khi tôi chạy SingleOrDefault thông qua LinqPad và VS, tôi chưa bao giờ CHỌN TOP 2, với FirstOrDefault tôi đã có thể chọn CHỌN TOP 1, nhưng theo như tôi có thể nói với bạn là không được CHỌN TOP 2.
Jamie R Ryussywski

Này, tôi cũng đã được thử trong linqpad và truy vấn sql làm tôi sợ vì nó hoàn toàn tìm nạp tất cả các hàng. Tôi không chắc làm thế nào điều này có thể xảy ra?
AnyOne

1
Điều này phụ thuộc hoàn toàn vào nhà cung cấp LINQ được sử dụng. Ví dụ: LINQ to SQL và LINQ to Entities có thể dịch sang SQL theo nhiều cách khác nhau. Tôi vừa thử LINQPad với nhà cung cấp IQ MySql, và FirstOrDefault()thêm vào LIMIT 0,1trong khi SingleOrDefault()không thêm gì.
Lucas

1
EF Core 2.1 dịch FirstOrDefault thành CHỌN TOP (1), SingleOrDefault sang CHỌN TOP (2)
camainc

19

Đối với LINQ -> SQL:

SingleOrDefault

  • sẽ tạo truy vấn như "select * từ người dùng trong đó userid = 1"
  • Chọn bản ghi khớp, ngoại lệ Ném nếu tìm thấy nhiều hơn một bản ghi
  • Sử dụng nếu bạn đang tìm nạp dữ liệu dựa trên cột khóa chính / duy nhất

Đầu tiên

  • sẽ tạo truy vấn như "chọn top 1 * từ người dùng trong đó userid = 1"
  • Chọn các hàng khớp đầu tiên
  • Sử dụng nếu bạn đang tìm nạp dữ liệu dựa trên cột khóa chính / duy nhất

tôi nghĩ bạn nên xóa "Chọn tất cả các hàng khớp" khỏi SingleOrDefault
Saim Abdullah

10

Tôi sử dụng SingleOrDefaulttrong các tình huống mà logic của tôi chỉ ra rằng sẽ là 0 hoặc một kết quả. Nếu có nhiều hơn, đó là một tình huống lỗi, rất hữu ích.


3
Thông thường tôi thấy rằng SingleOrDefault () nêu bật các trường hợp tôi chưa áp dụng bộ lọc chính xác trên tập kết quả hoặc khi có sự cố trùng lặp trong dữ liệu cơ bản. Thường xuyên hơn không, tôi thấy mình sử dụng các phương thức Single () và SingleOrDefault () so với các phương thức First ().
TimS

Có các hàm ý hiệu năng cho Single () và SingleOrDefault () trên LINQ to Object nếu bạn có số lượng lớn nhưng khi nói chuyện với cơ sở dữ liệu (ví dụ: SQL Server), nó sẽ thực hiện cuộc gọi 2 đầu và nếu bạn lập chỉ mục được thiết lập chính xác cuộc gọi không nên tốn kém và tôi thà thất bại nhanh chóng và tìm ra vấn đề dữ liệu thay vì có thể đưa ra các vấn đề dữ liệu khác bằng cách lấy sai bản sao khi gọi First () hoặc FirstOrDefault ().
heartlandcoder

5

SingleOrDefault: Bạn đang nói rằng "Nhiều nhất" có một mục khớp với truy vấn hoặc mặc định FirstOrDefault: Bạn đang nói rằng có "Ít nhất" một mục khớp với truy vấn hoặc mặc định

Nói to lên lần sau bạn cần chọn và bạn có thể sẽ chọn một cách khôn ngoan. :)


5
Trên thực tế không có kết quả nào là hoàn toàn chấp nhận được khi sử dụng FirstOrDefault. More correctly: FirstOrDefault` = Bất kỳ số lượng kết quả nào nhưng tôi chỉ quan tâm đến kết quả đầu tiên, cũng có thể không có kết quả. SingleOrDefault= Có 1 hoặc 0 kết quả, nếu có nhiều hơn có nghĩa là có lỗi ở đâu đó. First= Có ít nhất một kết quả, và tôi muốn nó. Single= Có chính xác 1 kết quả, không hơn, không kém, và tôi muốn kết quả đó.
Davy8

4

Trong trường hợp của bạn, tôi sẽ sử dụng như sau:

chọn theo ID == 5: bạn có thể sử dụng SingleOrDefault ở đây, vì bạn mong đợi một thực thể [hoặc không], nếu bạn có nhiều hơn một thực thể có ID 5, có gì đó sai và chắc chắn có ngoại lệ.

khi tìm kiếm những người có tên đầu tiên bằng "Bobby", có thể có nhiều hơn một (hoàn toàn có thể tôi nghĩ), vì vậy bạn không nên sử dụng Độc thân hay Đầu tiên, chỉ cần chọn với Thao tác (nếu "Bobby" trả về quá nhiều các thực thể, người dùng phải tinh chỉnh tìm kiếm của mình hoặc chọn một trong các kết quả được trả về)

thứ tự theo ngày tạo cũng phải được thực hiện với Thao tác ở đâu (không có duy nhất một thực thể, sắp xếp sẽ không được sử dụng nhiều;) tuy nhiên điều này ngụ ý bạn muốn TẤT CẢ các thực thể được sắp xếp - nếu bạn chỉ muốn MỘT, hãy sử dụng FirstOrDefault, Độc thân sẽ ném mỗi lần nếu bạn có nhiều hơn một thực thể.


3
Tôi không đồng ý. Nếu ID cơ sở dữ liệu của bạn là khóa chính, thì cơ sở dữ liệu đã thực thi tính duy nhất. Lãng phí chu kỳ CPU để kiểm tra xem cơ sở dữ liệu có thực hiện công việc của mình trên mọi truy vấn không chỉ là ngớ ngẩn.
John Henckel

4

Cả hai đều là toán tử phần tử và chúng được sử dụng để chọn một phần tử từ một chuỗi. Nhưng có một sự khác biệt nhỏ giữa chúng. Toán tử SingleOrDefault () sẽ đưa ra một ngoại lệ nếu có nhiều hơn một phần tử được thỏa mãn điều kiện trong đó như FirstOrDefault () sẽ không ném bất kỳ ngoại lệ nào cho cùng. Dưới đây là ví dụ.

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();

2
"Có một sự khác biệt nhỏ giữa chúng" - Đó là chính!
Nikhil Vartak

3

Trong ví dụ cuối cùng của bạn:

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

Có nó làm. Nếu bạn cố gắng sử dụng SingleOrDefault()và kết quả truy vấn nhiều hơn bản ghi bạn sẽ nhận được và ngoại lệ. Lần duy nhất bạn có thể sử dụng một cách an toàn SingleOrDefault()là khi bạn chỉ mong đợi 1 và chỉ 1 kết quả ...


Đúng rồi. Nếu bạn nhận được 0 kết quả, bạn cũng nhận được một ngoại lệ.
Dennis Rongo

1

Vì vậy, như tôi hiểu bây giờ, SingleOrDefault sẽ tốt nếu bạn đang truy vấn dữ liệu được đảm bảo là duy nhất tức là được thi hành bởi các ràng buộc DB như khóa chính.

Hoặc có một cách tốt hơn để truy vấn cho khóa chính.

Giả sử TableAcc của tôi có

AccountNumber - Primary Key, integer
AccountName
AccountOpenedDate
AccountIsActive
etc.

và tôi muốn truy vấn AccountNumber 987654, tôi sử dụng

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

1

Theo tôi FirstOrDefaultđang bị lạm dụng rất nhiều. Trong phần lớn các trường hợp khi bạn lọc dữ liệu, bạn sẽ mong nhận lại được một tập hợp các phần tử khớp với điều kiện logic hoặc một phần tử duy nhất bằng mã định danh duy nhất của nó - chẳng hạn như người dùng, sách, bài đăng v.v ... Đó là tại sao chúng ta thậm chí có thể nói xa hơn rằng đó FirstOrDefault()là mùi mã không phải vì có gì đó không đúng với nó mà vì nó được sử dụng quá thường xuyên. Bài đăng blog này khám phá các chủ đề chi tiết. IMO hầu hết thời gian SingleOrDefault()là một sự thay thế tốt hơn nhiều vì vậy hãy coi chừng sai lầm này và đảm bảo bạn sử dụng phương pháp phù hợp nhất thể hiện rõ ràng hợp đồng và kỳ vọng của bạn.


-1

Một điều bị bỏ lỡ trong các phản hồi ....

Nếu có nhiều kết quả, FirstOrDefault mà không có đơn đặt hàng bằng cách có thể mang lại các kết quả khác nhau dựa trên chiến lược chỉ mục nào đã được sử dụng bởi máy chủ.

Cá nhân tôi không thể đứng nhìn thấy FirstOrDefault trong mã bởi vì với tôi nó nói rằng nhà phát triển không quan tâm đến kết quả. Với một đơn đặt hàng mặc dù nó có thể hữu ích như một cách thực thi mới nhất / sớm nhất. Tôi đã phải sửa rất nhiều vấn đề gây ra bởi các nhà phát triển bất cẩn khi sử dụng FirstOrDefault.


-2

Tôi đã yêu cầu Google sử dụng các phương pháp khác nhau trên GitHub. Điều này được thực hiện bằng cách chạy truy vấn tìm kiếm của Google cho từng phương thức và giới hạn truy vấn đối với miền github.com và phần mở rộng tệp .cs bằng cách sử dụng truy vấn "site: github.com tệp: cs ..."

Có vẻ như các phương thức First * được sử dụng phổ biến hơn các phương thức Single *.

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

-8

Tôi không hiểu tại sao bạn sử dụng FirstOrDefault(x=> x.ID == key)khi điều này có thể lấy kết quả nhanh hơn nhiều nếu bạn sử dụng Find(key). Nếu bạn đang truy vấn bằng khóa Chính của bảng, quy tắc ngón tay cái là luôn luôn sử dụng Find(key). FirstOrDefaultnên được sử dụng cho các công cụ vị ngữ như(x=> x.Username == username) vv

điều này không xứng đáng với một downvote vì tiêu đề của câu hỏi không dành riêng cho linq trên DB hoặc Linq cho List / IEnumerable, v.v.


1
Không gian tên nào Find()trong?
p.campbell

Bạn có thể vui lòng cho chúng tôi biết? Vẫn đang chờ câu trả lời.
Denny

Có thể là thế này: stackoverflow.com/questions/14032709/
Kẻ

Từ "IEnumerable" nằm trong dòng đầu tiên của nội dung câu hỏi. Nếu bạn chỉ đọc tiêu đề chứ không phải câu hỏi thực tế và đã đăng một câu trả lời không chính xác, đó là sai lầm của bạn một lý do hoàn toàn chính đáng để đánh giá thấp IMO.
F1Krazy
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.