2011-11-22 85 views
5

Tôi có LINQ này để phản đối truy vấn:LINQ to đối tượng khi đối tượng là null VS LINQ to SQL

var result = Users.Where(u => u.Address.Country.Code == 12) 

tôi nhận được một ngoại lệ nếu địa chỉ hoặc Country là null.
Tại sao truy vấn này không kiểm tra xem địa chỉ có rỗng hay không và sau khi được mua sắm? Bằng cách này tôi sẽ không cần phải viết truy vấn khủng khiếp này:

var result = Users.Where(u => u.Address != null && 
           u.Address.Country != null && 
           u.Address.Country.Code == 12) 

Trong LINQ to SQL truy vấn đầu tiên wiil thực hiện công việc (từ lý do khác tất nhiên).

Có phải cách để tránh "kiểm tra rỗng" trong LINQ đối tượng không?

Trả lời

6

Thật không may, "null" được coi là không nhất quán trong C# (và bằng nhiều ngôn ngữ lập trình khác). Số học không thể gửi được là được nâng lên. Đó là, nếu bạn làm số học trên các số nguyên nullable, và không có toán hạng nào là null, thì bạn sẽ nhận được câu trả lời bình thường, nhưng nếu bất kỳ số nào trong số đó là null, bạn sẽ nhận được null. Nhưng nhà điều hành "quyền truy cập thành viên" là không phải là đã được dỡ bỏ; nếu bạn cung cấp một toán hạng null cho truy cập thành viên "." nhà điều hành, nó ném một ngoại lệ hơn là trả về một giá trị null.

đã được chúng tôi thiết kế một hệ thống kiểu từ đầu, chúng ta có thể nói rằng tất cả loại là nullable, và rằng bất kỳ biểu hiện có chứa một toán hạng rỗng tạo ra một kết quả vô không phụ thuộc vào loại của toán hạng. Vì vậy, gọi một phương thức với một bộ nhận null hoặc một đối số null sẽ tạo ra một kết quả rỗng. Hệ thống đó tạo ra rất nhiều ý nghĩa, nhưng rõ ràng là đã quá muộn để chúng tôi thực hiện điều đó ngay bây giờ; hàng triệu hàng triệu dòng mã đã được viết mà hy vọng hành vi hiện tại.

Chúng tôi đã cân nhắc thêm một toán tử truy cập thành viên "đã được dỡ bỏ", có thể được chú ý .?. Vì vậy, sau đó bạn có thể nói where user.?Address.?Country.?Code == 12 và điều đó sẽ tạo ra một int vô giá mà sau đó có thể được so sánh với 12 như ints vô giá trị bình thường. Tuy nhiên, điều này chưa bao giờ vượt qua giai đoạn "vâng, đó có thể là tốt đẹp trong một phiên bản tương lai" của quá trình thiết kế vì vậy tôi sẽ không mong đợi nó bất kỳ lúc nào.


CẬP NHẬT: Toán tử "Elvis" được đề cập ở trên đã được triển khai trong C# 6.0.

+0

Và bây giờ nó được thực hiện trong ngôn ngữ (nếu cách khác xung quanh cú pháp) nhưng không được thực hiện trong bản dịch LINQ SQL như là một no-op nên không thể dễ dàng được sử dụng. – NetMage

5

Không, nó là ngoại lệ tham chiếu null giống như truy cập var x = u.Address.Country.Code; sẽ là NullReferenceException.

Bạn phải luôn đảm bảo rằng bạn đang tham khảo không phải là null trong LINQ đối với các đối tượng, như bạn làm với bất kỳ câu lệnh mã nào khác.

Bạn có thể làm điều này bằng cách sử dụng && logic bạn có, hoặc bạn có thể chuỗi Where khoản được tốt (mặc dù điều này sẽ chứa nhiều vòng lặp và có lẽ thực hiện chậm hơn):

var result = Users.Where(u => u.Address != null) 
        .Where(u.Address.Country != null) 
        .Where(u.Address.Country.Code == 12); 

LƯU Ý: Các Maybe() phương pháp dưới đây chỉ được cung cấp như là một phương pháp "bạn cũng có thể", tôi không nói rằng nó tốt hay xấu, chỉ hiển thị những gì một số người làm. Vui lòng không bỏ phiếu nếu bạn không thích số Maybe() Tôi chỉ lặp lại các giải pháp khác nhau mà tôi đã xem ...

Tôi đã xem một số phương pháp mở rộng Maybe() cho phép bạn thực hiện bạn muốn. Một số người làm/không thích những điều này bởi vì họ là những phương pháp mở rộng hoạt động trên một tài liệu tham khảo null. Tôi không nói điều đó tốt hay xấu, chỉ là một số người cảm thấy vi phạm hành vi giống OO tốt.

Ví dụ, bạn có thể tạo ra một phương pháp khuyến nông như:

public static class ObjectExtensions 
{ 
    // returns default if LHS is null 
    public static TResult Maybe<TInput, TResult>(this TInput value, Func<TInput, TResult> evaluator) 
     where TInput : class 
    { 
     return (value != null) ? evaluator(value) : default(TResult); 
    } 

    // returns specified value if LHS is null 
    public static TResult Maybe<TInput, TResult>(this TInput value, Func<TInput, TResult> evaluator, TResult failureValue) 
     where TInput : class 
    { 
     return (value != null) ? evaluator(value) : failureValue; 
    } 
} 

Và sau đó làm:

var result = Users.Where(u => u.Maybe(x => x.Address) 
        .Maybe(x => x.Country) 
        .Maybe(x => x.Code) == 12); 

Về cơ bản, điều này chỉ thác các null xuống chuỗi (hoặc giá trị mặc định trong trường hợp của loại không tham chiếu).

CẬP NHẬT:

Nếu bạn muốn cung cấp một giá trị thất bại không phải mặc định (nói Mã là -1 nếu bất kỳ phần nào là null), bạn sẽ chỉ cần vượt qua giá trị thất bại mới vào Có lẽ ():

// if you wanted to compare to zero, for example, but didn't want null 
// to translate to zero, change the default in the final maybe to -1 
var result = Users.Where(u => u.Maybe(x => x.Address) 
        .Maybe(x => x.Country) 
        .Maybe(x => x.Code, -1) == 0); 

Như tôi đã nói, đây chỉ là một trong nhiều giải pháp. Một số người không thích có thể gọi các phương thức mở rộng từ các loại tham chiếu null, nhưng đây là một tùy chọn mà một số người có xu hướng sử dụng để tránh các vấn đề xếp tầng null này.

Hiện tại, không có toán tử khử tham chiếu không an toàn được tích hợp vào C#, do đó bạn sống với các kiểm tra rỗng có điều kiện như bạn đã có trước đó, hãy đính kèm các câu hỏi Where() để chúng lọc ra null, hoặc xây dựng một cái gì đó để cho phép bạn ghép các số null như các phương pháp Maybe() ở trên.

+0

Lưu ý rằng nếu so sánh 'Mã' với' 0', nó sẽ trả về 'true' ngay cả khi bất kỳ thuộc tính gốc nào là' null' (vì '0' là mặc định cho kiểu). –

+0

@GeorgeDuckett: Đúng, đó là sự thật. Đây chỉ là một quá tải của nhiều, tôi thực sự có một số quá tải trong thư viện mà tôi sử dụng để cung cấp các giá trị mặc định khác. Ví dụ .... (xem mã cập nhật). –

+0

Tôi thực sự không thích những thứ đó, khó hiểu hơn khi bạn đọc mã, gần như nhiều mã. – Magnus