LINQ Đơn vs Đầu tiên


214

LINQ:

Có hiệu quả hơn khi sử dụng Single()toán tử First()khi tôi biết chắc chắn rằng truy vấn sẽ trả về một bản ghi ?

Có sự khác biệt?

Câu trả lời:


311

Nếu bạn đang mong đợi một bản ghi duy nhất, thì luôn rõ ràng trong mã của bạn.

Tôi biết những người khác đã viết lý do tại sao bạn sử dụng cái này hay cái kia, nhưng tôi nghĩ tôi sẽ minh họa tại sao bạn KHÔNG nên sử dụng cái này, khi bạn muốn nói cái kia.

Lưu ý: Trong mã của tôi, tôi thường sẽ sử dụng FirstOrDefault()SingleOrDefault()đó là một câu hỏi khác.

Ví dụ: lấy một bảng lưu trữ Customersbằng các ngôn ngữ khác nhau bằng Khóa tổng hợp ( ID, Lang):

DBContext db = new DBContext();
Customer customer = db.Customers.Where( c=> c.ID == 5 ).First();

Mã này ở trên giới thiệu một lỗi logic có thể (khó theo dõi). Nó sẽ trả về nhiều hơn một bản ghi (giả sử bạn có bản ghi khách hàng bằng nhiều ngôn ngữ) nhưng nó sẽ luôn chỉ trả lại bản đầu tiên ... đôi khi có thể hoạt động ... nhưng không phải là bản ghi khác. Thật không thể đoán trước.

Vì mục đích của bạn là trả lại một lần Customersử dụng Single();

Sau đây sẽ đưa ra một ngoại lệ (đó là những gì bạn muốn trong trường hợp này):

DBContext db = new DBContext();
Customer customer = db.Customers.Where( c=> c.ID == 5 ).Single();

Sau đó, bạn chỉ cần đánh vào trán mình và nói với chính mình ... OOPS! Tôi quên lĩnh vực ngôn ngữ! Sau đây là phiên bản chính xác:

DBContext db = new DBContext();
Customer customer = db.Customers.Where( c=> c.ID == 5 && c.Lang == "en" ).Single();

First() là hữu ích trong kịch bản sau đây:

DBContext db = new DBContext();
NewsItem newsitem = db.NewsItems.OrderByDescending( n => n.AddedDate ).First();

Nó sẽ trả về MỘT đối tượng và vì bạn đang sử dụng sắp xếp, nó sẽ là bản ghi gần đây nhất được trả về.

Sử dụng Single()khi bạn cảm thấy nên luôn luôn trả về 1 bản ghi sẽ giúp bạn tránh các lỗi logic.


76
Cả hai phương thức Đơn và Đầu tiên đều có thể lấy tham số biểu thức để lọc, do đó, hàm Where không cần thiết. Ví dụ: Khách hàng của khách hàng = db.Customers.Single (c => c.ID == 5);
Josh Noe

6
@JoshNoe - Tôi tò mò, có sự khác biệt customers.Where(predicate).Single() customers.Single(predicate)nào không?
drzaus

9
@drzaus - Về mặt logic, không, cả hai đều lọc các giá trị được trả về dựa trên vị từ. Tuy nhiên, tôi đã kiểm tra việc tháo gỡ và Trường hợp (vị ngữ) .Single () có ba hướng dẫn bổ sung trong trường hợp đơn giản mà tôi đã thực hiện. Vì vậy, trong khi tôi không phải là chuyên gia về IL, nhưng có vẻ như khách hàng.Single (vị ngữ) sẽ hiệu quả hơn.
Josh Noe

5
@JoshNoe nó hoàn toàn ngược lại khi nó bật ra.
Đặc vụ


72

Độc thân sẽ đưa ra một ngoại lệ nếu nó tìm thấy nhiều hơn một bản ghi phù hợp với tiêu chí. Đầu tiên sẽ luôn luôn chọn bản ghi đầu tiên từ danh sách. Nếu truy vấn trả về chỉ 1 bản ghi, bạn có thể đi với First().

Cả hai sẽ ném một InvalidOperationExceptionngoại lệ nếu bộ sưu tập trống. Hoặc bạn có thể sử dụng SingleOrDefault(). Điều này sẽ không ném ngoại lệ nếu danh sách trống


29

Độc thân()

Trả về một yếu tố cụ thể duy nhất của một truy vấn

Khi sử dụng : Nếu chính xác 1 yếu tố được mong đợi; không 0 hoặc nhiều hơn 1. Nếu danh sách trống hoặc có nhiều hơn một phần tử, nó sẽ đưa ra một Ngoại lệ "Chuỗi chứa nhiều hơn một phần tử"

SingleOrDefault ()

Trả về một yếu tố cụ thể duy nhất của truy vấn hoặc giá trị mặc định nếu không tìm thấy kết quả

Khi sử dụng : Khi 0 hoặc 1 phần tử được mong đợi. Nó sẽ ném một ngoại lệ nếu danh sách có 2 mục trở lên.

Đầu tiên()

Trả về phần tử đầu tiên của truy vấn có nhiều kết quả.

Khi sử dụng : Khi 1 hoặc nhiều yếu tố được mong đợi và bạn chỉ muốn đầu tiên. Nó sẽ ném một ngoại lệ nếu danh sách không chứa phần tử.

FirstOrDefault ()

Trả về phần tử đầu tiên của danh sách với bất kỳ số lượng phần tử hoặc giá trị mặc định nếu danh sách trống.

Khi sử dụng : Khi nhiều yếu tố được mong đợi và bạn chỉ muốn đầu tiên. Hoặc danh sách trống và bạn muốn có một giá trị mặc định cho loại được chỉ định, giống như default(MyObjectType). Ví dụ: nếu loại danh sách là list<int>nó sẽ trả về số đầu tiên từ danh sách hoặc 0 nếu danh sách trống. Nếu có list<string>, nó sẽ trả về chuỗi đầu tiên từ danh sách hoặc null nếu danh sách trống.


1
Giải thích tốt đẹp. Tôi chỉ thay đổi rằng bạn có thể sử dụng Firstkhi có 1 hoặc nhiều yếu tố được mong đợi , không chỉ "nhiều hơn 1" và FirstOrDefaultvới bất kỳ số lượng yếu tố nào.
Andrew

18

Có một sự khác biệt tinh tế, ngữ nghĩa giữa hai phương pháp này.

Sử dụng Singleđể truy xuất phần tử đầu tiên (và duy nhất) từ một chuỗi nên chứa một phần tử và không còn nữa. Nếu chuỗi có nhiều hơn phần tử, việc gọi của Singlebạn sẽ khiến ngoại lệ bị ném do bạn chỉ ra rằng chỉ nên có một phần tử.

Sử dụng Firstđể truy xuất phần tử đầu tiên từ một chuỗi có thể chứa bất kỳ số phần tử nào. Nếu chuỗi có nhiều hơn phần tử, việc gọi của Firstbạn sẽ không gây ra ngoại lệ do bạn chỉ ra rằng bạn chỉ cần phần tử đầu tiên trong chuỗi và không quan tâm nếu có nhiều hơn.

Nếu chuỗi không chứa phần tử, cả hai lệnh gọi phương thức sẽ khiến ngoại lệ bị ném vì cả hai phương thức đều mong muốn có ít nhất một phần tử.


17

Nếu bạn không đặc biệt muốn có một ngoại lệ được ném trong trường hợp có nhiều hơn một mục, hãy sử dụngFirst() .

Cả hai đều hiệu quả, lấy mục đầu tiên. First()hiệu quả hơn một chút vì không cần kiểm tra xem có mục thứ hai không.

Sự khác biệt duy nhất là Single()hy vọng sẽ chỉ có một mục trong bảng liệt kê và sẽ đưa ra một ngoại lệ nếu có nhiều hơn một mục. Bạn sử dụng .Single() nếu bạn đặc biệt muốn một ngoại lệ được ném trong trường hợp này.


14

Nếu tôi nhớ lại, Single () kiểm tra xem có phần tử nào khác sau phần tử đầu tiên không (và đưa ra một ngoại lệ nếu đó là trường hợp), trong khi First () dừng lại sau khi nhận được phần tử đó. Cả hai ném một ngoại lệ nếu chuỗi trống.

Cá nhân, tôi luôn sử dụng First ().


2
Trong SQL, họ tạo First () làm TOP 1 và Single () làm TOP 2 nếu tôi không nhầm.
Matthijs Wessels

10

Về tính chính xác: Một đồng nghiệp và tôi đã thảo luận về hiệu suất của Single vs First (hoặc SingleOrDefault vs FirstOrDefault), và tôi đã tranh luận về việc First (hoặc FirstOrDefault) sẽ nhanh hơn và cải thiện hiệu suất (Tôi hoàn toàn làm về ứng dụng của chúng tôi chạy nhanh hơn).

Tôi đã đọc một số bài đăng trên Stack Overflow tranh luận về điều này. Một số người nói rằng có hiệu suất tăng nhỏ khi sử dụng First thay vì Single. Điều này là do First sẽ đơn giản trả về mục đầu tiên trong khi Single phải quét tất cả các kết quả để đảm bảo không có trùng lặp (nghĩa là: nếu nó tìm thấy mục ở hàng đầu tiên của bảng, nó vẫn sẽ quét mọi hàng khác để đảm bảo không có giá trị thứ hai phù hợp với điều kiện sẽ gây ra lỗi). Tôi cảm thấy như mình đang ở trên một nền tảng vững chắc với việc First First, nhanh hơn so với Single Single, vì vậy tôi bắt đầu chứng minh điều đó và đưa cuộc tranh luận vào phần còn lại.

Tôi thiết lập một bài kiểm tra trong cơ sở dữ liệu của mình và thêm 1.000.000 hàng ID UniqueIdentifier Foreign UniqueIdentifier Info nvarchar (50) (chứa đầy các chuỗi số 1 0 0 đến Điên 999,9999

Tôi đã tải dữ liệu và đặt ID làm trường khóa chính.

Sử dụng LinqPad, mục tiêu của tôi là chỉ ra rằng nếu bạn tìm kiếm một giá trị trên 'Nước ngoài' hoặc 'Thông tin' khi sử dụng Đơn, thì điều đó sẽ tồi tệ hơn nhiều so với sử dụng Đầu tiên.

Tôi không thể giải thích kết quả tôi nhận được. Trong hầu hết mọi trường hợp, sử dụng Single hoặc SingleOrDefault nhanh hơn một chút. Điều này không có ý nghĩa logic nào với tôi, nhưng tôi muốn chia sẻ điều đó.

Ví dụ: Tôi đã sử dụng các truy vấn sau:

var q = TestTables.First(x=>x.Info == "314638") ;
//Vs.
Var q = TestTables.Single(x=>x.Info =="314638") ; //(this was slightly faster to my surprise)

Tôi đã thử các truy vấn tương tự trên trường khóa 'Nước ngoài' không được lập chỉ mục với suy nghĩ sẽ chứng minh Đầu tiên nhanh hơn, nhưng Đơn luôn luôn nhanh hơn một chút trong các thử nghiệm của tôi.


2
Nếu nó nằm trên một trường được lập chỉ mục hơn cơ sở dữ liệu thì không cần phải quét để đảm bảo nó là duy nhất. Nó đã biết rằng nó là. Vì vậy, không có chi phí nào ở đó và chi phí duy nhất ở phía máy chủ đảm bảo chỉ có một bản ghi trở lại. Tự mình thực hiện kiểm tra hiệu suất không phải là kết luận theo cách này hay cách khác
mirhagk

1
Tôi không nghĩ những kết quả này sẽ giống nhau trên một đối tượng phức tạp nếu bạn đang tìm kiếm trên một lĩnh vực không được IComparer sử dụng.
Anthony Nichols

Đó nên là một câu hỏi khác. Hãy chắc chắn bao gồm nguồn của bài kiểm tra của bạn .
Sinatr

5

Họ khác nhau. Cả hai đều khẳng định rằng tập kết quả không trống, nhưng đơn cũng khẳng định rằng không có nhiều hơn 1 kết quả. Cá nhân tôi sử dụng Độc thân trong trường hợp tôi chỉ mong có 1 kết quả chỉ vì nhận lại hơn 1 kết quả là một lỗi và có lẽ nên được xử lý như vậy.


5

Bạn có thể thử ví dụ đơn giản để có được sự khác biệt. Ngoại lệ sẽ được ném trên dòng 3;

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

3

Rất nhiều người tôi biết sử dụng FirstOrDefault (), nhưng tôi có xu hướng sử dụng SingleOrDefault () nhiều hơn vì thường đó sẽ là một số loại không nhất quán dữ liệu nếu có nhiều hơn một. Mặc dù vậy, đây là giao dịch với LINQ-to-Object.


-1

Các hồ sơ trong thực thể nhân viên:

Employeeid = 1: Chỉ có một nhân viên có ID này

Firstname = Robert: Nhiều hơn một nhân viên có tên này

Employeeid = 10: Không có nhân viên có ID này

Bây giờ cần phải hiểu những gì Single()First()có nghĩa là chi tiết.

Độc thân()

Single () được sử dụng để trả về một bản ghi duy nhất tồn tại trong một bảng, do đó, truy vấn bên dưới sẽ trả về Nhân viên employeed =1vì chúng ta chỉ có một Nhân viên có Employeed1. Nếu chúng ta có hai bản ghi EmployeeId = 1thì nó sẽ báo lỗi (xem phần lỗi dưới đây trong truy vấn thứ hai nơi chúng tôi đang sử dụng một ví dụ cho Firstname.

Employee.Single(e => e.Employeeid == 1)

Ở trên sẽ trả về một bản ghi duy nhất, trong đó có 1 employeeId

Employee.Single(e => e.Firstname == "Robert")

Ở trên sẽ đưa ra một ngoại lệ vì các bản ghi nhiều mức trong bảng cho FirstName='Robert'. Ngoại lệ sẽ là

UnlimitedOperationException: Trình tự chứa nhiều hơn một phần tử

Employee.Single(e => e.Employeeid == 10)

Điều này sẽ, một lần nữa, đưa ra một ngoại lệ vì không có bản ghi nào tồn tại cho id = 10. Ngoại lệ sẽ là

UnlimitedOperationException: Trình tự không chứa phần tử.

Đối với EmployeeId = 10nó sẽ trả về null, nhưng vì chúng tôi đang sử dụng Single()nên nó sẽ báo lỗi. Để xử lý lỗi null chúng ta nên sử dụng SingleOrDefault().

Đầu tiên()

Đầu tiên () trả về từ nhiều bản ghi, các bản ghi tương ứng được sắp xếp theo thứ tự tăng dần theo để birthdatenó sẽ trả về 'Robert' người già nhất.

Employee.OrderBy(e => e. Birthdate)
.First(e => e.Firstname == "Robert")

Ở trên nên trả lại cái cũ nhất, Robert theo DOB.

Employee.OrderBy(e => e. Birthdate)
.First(e => e.Employeeid == 10)

Ở trên sẽ đưa ra một ngoại lệ vì không có bản ghi nào cho id = 10 tồn tại. Để tránh một ngoại lệ null chúng ta nên sử dụng FirstOrDefault()chứ không phải First().

Lưu ý: Chúng tôi chỉ có thể sử dụng First()/ Single()khi chúng tôi hoàn toàn chắc chắn rằng nó không thể trả về giá trị null.

Trong cả hai hàm, sử dụng SingleOrDefault () HOẶC FirstOrDefault () sẽ xử lý một ngoại lệ null, trong trường hợp không tìm thấy bản ghi nào, nó sẽ trả về null.


Hãy giải thích câu trả lời của bạn.
MrMaavin

1
@MrMaavin Tôi đã cập nhật, vui lòng cho tôi biết bây giờ có dễ hiểu cho bạn không?
Cảnh sát trưởng
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.