Làm cách nào để truy vấn giá trị null trong khung thực thể?


109

Tôi muốn thực hiện một truy vấn như thế này

   var result = from entry in table
                     where entry.something == null
                     select entry;

và nhận được một IS NULLtạo.

Đã chỉnh sửa: Sau hai câu trả lời đầu tiên, tôi cảm thấy cần phải làm rõ rằng tôi đang sử dụng Entity Framework chứ không phải Linq to SQL. Phương thức object.Equals () dường như không hoạt động trong EF.

Chỉnh sửa số 2: Truy vấn trên hoạt động như dự định. Nó tạo ra một cách chính xác IS NULL. Tuy nhiên, mã sản xuất của tôi là

value = null;
var result = from entry in table
                         where entry.something == value
                         select entry;

và SQL được tạo là something = @p; @p = NULL. Có vẻ như EF dịch đúng biểu thức hằng nhưng nếu một biến có liên quan, nó sẽ xử lý nó giống như một phép so sánh thông thường. Thực sự có ý nghĩa. Tôi sẽ đóng câu hỏi này


17
Tôi nghĩ nó không thực sự có ý nghĩa ... Trình kết nối phải thông minh một chút và không yêu cầu chúng tôi thực hiện công việc của nó: thực hiện một bản dịch chính xác trong SQL của truy vấn C # chính xác. Điều này tạo ra một hành vi không mong muốn.
Julien N

6
Tôi với Julien, đây là một thất bại về phía EF
Mr Bell

1
Đây là sự thất bại của các tiêu chuẩn và bây giờ nó chỉ trở nên tồi tệ hơn khi so sánh với null vĩnh viễn dẫn đến không xác định như SQL Server 2016 với ANSI NULL được đặt vĩnh viễn. Null có thể đại diện cho một giá trị không xác định, nhưng bản thân "null" không phải là một giá trị không xác định. So sánh giá trị null với giá trị null sẽ hoàn toàn mang lại kết quả đúng, nhưng không may là tiêu chuẩn khác với thông thường cũng như logic Boolean.
Triynko

Câu trả lời:


126

Giải pháp cho Linq-to-SQL:

var result = from entry in table
             where entry.something.Equals(value)
             select entry;

Giải pháp cho Linq-to-Entities (ouch!):

var result = from entry in table
             where (value == null ? entry.something == null : entry.something == value)
             select entry;

Đây là một con bọ khó chịu đã cắn tôi nhiều lần. Nếu lỗi này cũng ảnh hưởng đến bạn, vui lòng truy cập báo cáo lỗi trên UserVoice và cho Microsoft biết rằng lỗi này cũng ảnh hưởng đến bạn.


Chỉnh sửa: Lỗi này đang được sửa trong EF 4.5 ! Cảm ơn mọi người đã ủng hộ lỗi này!

Để có khả năng tương thích ngược, nó sẽ được chọn tham gia - bạn cần bật cài đặt theo cách thủ công để entry == valuehoạt động. Chưa có từ nào về cài đặt này là gì. Giữ nguyên!


Chỉnh sửa 2: Theo bài đăng này của nhóm EF, vấn đề này đã được khắc phục trong EF6! Tuyệt vời!

Chúng tôi đã thay đổi hành vi mặc định của EF6 để bù cho logic ba giá trị.

Điều này có nghĩa là mã hiện có dựa trên hành vi cũ ( null != nullnhưng chỉ khi so sánh với một biến) sẽ cần được thay đổi để không dựa vào hành vi đó hoặc đặt UseCSharpNullComparisonBehaviorthành false để sử dụng hành vi cũ bị hỏng.


6
Tôi đã bình chọn báo cáo lỗi. Hy vọng rằng họ sẽ sửa lỗi này. Tôi không thể nói rằng tôi thực sự nhớ lỗi này đã xuất hiện trong phiên bản beta năm 2010 ...
noobish

2
ồ vào microsoft ... thật không?!?!? Trong phiên bản 4.1?!?! +1
David

1
Cách giải quyết Linq-To-SQL đó dường như không hoạt động (thử với Hướng dẫn?). Sử dụng Entities-Workaround hoạt động trong L2S, nhưng tạo ra SQL khủng khiếp. Tôi phải thực hiện một câu lệnh if trong mã(var result = from ...; if(value.HasValue) result = result.Where(e => e.something == value) else result = result.Where(e => e.something == null);
Michael Stum

5
Object.Equals thực sự hoạt động(where Object.Equals(entry.something,value))
Michael Stum

5
@ leen3o (hoặc bất kỳ ai khác) - Có ai chưa tìm thấy bản sửa lỗi bị cáo buộc này nằm ở đâu trong EF 4.5 / 5.0? Tôi đang sử dụng 5.0 và nó vẫn hoạt động sai.
Shaul Behr

17

Vì Entity Framework 5.0, bạn có thể sử dụng mã sau để giải quyết vấn đề của mình:

public abstract class YourContext : DbContext
{
  public YourContext()
  {
    (this as IObjectContextAdapter).ObjectContext.ContextOptions.UseCSharpNullComparisonBehavior = true;
  }
}

Điều này sẽ giải quyết vấn đề của bạn vì Entity Framerwork sẽ sử dụng so sánh rỗng 'C # like'.


16

Có một cách giải quyết đơn giản hơn một chút hoạt động với LINQ cho các thực thể:

var result = from entry in table
         where entry.something == value || (value == null && entry.something == null)
         select entry;

Điều này có hiệu quả bởi vì AZ đã nhận thấy, LINQ đối với các đối tượng là các trường hợp đặc biệt x == null (tức là so sánh bình đẳng với hằng số null) và dịch nó thành x LÀ NULL.

Chúng tôi hiện đang xem xét việc thay đổi hành vi này để tự động đưa ra các phép so sánh bù trừ nếu cả hai bên của bình đẳng đều có giá trị vô hiệu. Tuy nhiên, có một số thách thức:

  1. Điều này có thể phá vỡ mã đã phụ thuộc vào hành vi hiện có.
  2. Bản dịch mới có thể ảnh hưởng đến hiệu suất của các truy vấn hiện có ngay cả khi tham số null hiếm khi được sử dụng.

Trong mọi trường hợp, việc chúng ta có bắt tay vào việc này hay không sẽ phụ thuộc rất nhiều vào mức độ ưu tiên tương đối mà khách hàng của chúng ta chỉ định cho nó. Nếu bạn quan tâm đến vấn đề này, tôi khuyến khích bạn bỏ phiếu cho vấn đề đó trên trang web Đề xuất tính năng mới của chúng tôi: https://data.uservoice.com .


9

Nếu đó là kiểu nullable, có thể thử sử dụng thuộc tính HasValue?

var result = from entry in table
                 where !entry.something.HasValue
                 select entry;

Không có bất kỳ EF nào để kiểm tra ở đây mặc dù ... chỉ là một gợi ý =)


1
Chà ... điều này chỉ hoạt động nếu bạn chỉ đang tìm kiếm null, nhưng sau đó sử dụng == nullkhông bị lỗi. Vấn đề là lọc theo giá trị của một biến, giá trị của nó có thể là null, và giá trị null sẽ tìm thấy các bản ghi null.
Dave Cousineau

1
Câu trả lời của bạn đã cứu tôi. Tôi đã quên sử dụng kiểu nullable trên lớp mô hình thực thể của mình và không thể (x => x.Column == null)hoạt động. :)
Reuel Ribeiro

Điều này cho thấy System.NullReferenceException , vì đối tượng allready là null!
TiyebM


5

để đối phó với việc sử dụng Null Comparisons Object.Equals()thay vì==

kiểm tra tài liệu tham khảo này


Điều này hoạt động hoàn hảo trong Linq-To-Sql và cũng tạo ra SQL thích hợp (một số câu trả lời khác ở đây tạo ra SQL khủng khiếp hoặc kết quả sai).
Michael Stum

Giả sử tôi muốn so sánh với null, Object.Equals(null)nếu Objectchính nó là null thì sao?
TiyebM

4

Chỉ ra rằng tất cả các đề xuất của Entity Framework <6.0 đều tạo ra một số SQL khó xử. Xem ví dụ thứ hai để biết cách sửa lỗi "sạch".

Cách giải quyết nực cười

// comparing against this...
Foo item = ...

return DataModel.Foos.FirstOrDefault(o =>
    o.ProductID == item.ProductID
    // ridiculous < EF 4.5 nullable comparison workaround http://stackoverflow.com/a/2541042/1037948
    && item.ProductStyleID.HasValue ? o.ProductStyleID == item.ProductStyleID : o.ProductStyleID == null
    && item.MountingID.HasValue ? o.MountingID == item.MountingID : o.MountingID == null
    && item.FrameID.HasValue ? o.FrameID == item.FrameID : o.FrameID == null
    && o.Width == w
    && o.Height == h
    );

kết quả trong SQL như:

SELECT TOP (1) [Extent1].[ID]                 AS [ID],
       [Extent1].[Name]               AS [Name],
       [Extent1].[DisplayName]        AS [DisplayName],
       [Extent1].[ProductID]          AS [ProductID],
       [Extent1].[ProductStyleID]     AS [ProductStyleID],
       [Extent1].[MountingID]         AS [MountingID],
       [Extent1].[Width]              AS [Width],
       [Extent1].[Height]             AS [Height],
       [Extent1].[FrameID]            AS [FrameID],
FROM   [dbo].[Foos] AS [Extent1]
WHERE  (CASE
  WHEN (([Extent1].[ProductID] = 1 /* @p__linq__0 */)
        AND (NULL /* @p__linq__1 */ IS NOT NULL)) THEN
    CASE
      WHEN ([Extent1].[ProductStyleID] = NULL /* @p__linq__2 */) THEN cast(1 as bit)
      WHEN ([Extent1].[ProductStyleID] <> NULL /* @p__linq__2 */) THEN cast(0 as bit)
    END
  WHEN (([Extent1].[ProductStyleID] IS NULL)
        AND (2 /* @p__linq__3 */ IS NOT NULL)) THEN
    CASE
      WHEN ([Extent1].[MountingID] = 2 /* @p__linq__4 */) THEN cast(1 as bit)
      WHEN ([Extent1].[MountingID] <> 2 /* @p__linq__4 */) THEN cast(0 as bit)
    END
  WHEN (([Extent1].[MountingID] IS NULL)
        AND (NULL /* @p__linq__5 */ IS NOT NULL)) THEN
    CASE
      WHEN ([Extent1].[FrameID] = NULL /* @p__linq__6 */) THEN cast(1 as bit)
      WHEN ([Extent1].[FrameID] <> NULL /* @p__linq__6 */) THEN cast(0 as bit)
    END
  WHEN (([Extent1].[FrameID] IS NULL)
        AND ([Extent1].[Width] = 20 /* @p__linq__7 */)
        AND ([Extent1].[Height] = 16 /* @p__linq__8 */)) THEN cast(1 as bit)
  WHEN (NOT (([Extent1].[FrameID] IS NULL)
             AND ([Extent1].[Width] = 20 /* @p__linq__7 */)
             AND ([Extent1].[Height] = 16 /* @p__linq__8 */))) THEN cast(0 as bit)
END) = 1

Giải pháp khắc phục

Nếu bạn muốn tạo SQL sạch hơn, một cái gì đó như:

// outrageous < EF 4.5 nullable comparison workaround http://stackoverflow.com/a/2541042/1037948
Expression<Func<Foo, bool>> filterProductStyle, filterMounting, filterFrame;
if(item.ProductStyleID.HasValue) filterProductStyle = o => o.ProductStyleID == item.ProductStyleID;
else filterProductStyle = o => o.ProductStyleID == null;

if (item.MountingID.HasValue) filterMounting = o => o.MountingID == item.MountingID;
else filterMounting = o => o.MountingID == null;

if (item.FrameID.HasValue) filterFrame = o => o.FrameID == item.FrameID;
else filterFrame = o => o.FrameID == null;

return DataModel.Foos.Where(o =>
    o.ProductID == item.ProductID
    && o.Width == w
    && o.Height == h
    )
    // continue the outrageous workaround for proper sql
    .Where(filterProductStyle)
    .Where(filterMounting)
    .Where(filterFrame)
    .FirstOrDefault()
    ;

dẫn đến những gì bạn muốn ngay từ đầu:

SELECT TOP (1) [Extent1].[ID]                 AS [ID],
           [Extent1].[Name]               AS [Name],
           [Extent1].[DisplayName]        AS [DisplayName],
           [Extent1].[ProductID]          AS [ProductID],
           [Extent1].[ProductStyleID]     AS [ProductStyleID],
           [Extent1].[MountingID]         AS [MountingID],
           [Extent1].[Width]              AS [Width],
           [Extent1].[Height]             AS [Height],
           [Extent1].[FrameID]            AS [FrameID],
FROM   [dbo].[Foos] AS [Extent1]
WHERE  ([Extent1].[ProductID] = 1 /* @p__linq__0 */)
   AND ([Extent1].[Width] = 16 /* @p__linq__1 */)
   AND ([Extent1].[Height] = 20 /* @p__linq__2 */)
   AND ([Extent1].[ProductStyleID] IS NULL)
   AND ([Extent1].[MountingID] = 2 /* @p__linq__3 */)
   AND ([Extent1].[FrameID] IS NULL)

Mã chạy trên SQL sẽ sạch hơn và nhanh hơn nhưng EF sẽ tạo & lưu trữ kế hoạch truy vấn mới cho mọi tổ hợp trước khi gửi đến máy chủ sql, điều này làm cho nó chậm hơn so với các cách giải quyết khác.
Burak Tamtürk

2
var result = from entry in table
                     where entry.something == null
                     select entry;

Truy vấn trên hoạt động như dự định. Nó chính xác tạo ra IS NULL. Tuy nhiên, mã sản xuất của tôi là

var value = null;
var result = from entry in table
                         where entry.something == value
                         select entry;

và SQL được tạo là một cái gì đó = @p; @p = NULL. Có vẻ như EF dịch đúng biểu thức hằng nhưng nếu một biến có liên quan, nó sẽ xử lý nó giống như một phép so sánh thông thường. Thực sự có ý nghĩa.


1

Có vẻ như Linq2Sql cũng có "vấn đề" này. Có vẻ như có một lý do hợp lệ cho hành vi này là do ANSI NULL được BẬT hay TẮT nhưng nó làm rối trí tại sao một "== null" thẳng trên thực tế sẽ hoạt động như bạn mong đợi.


1

Về mặt cá nhân, tôi thích:

var result = from entry in table    
             where (entry.something??0)==(value??0)                    
              select entry;

kết thúc

var result = from entry in table
             where (value == null ? entry.something == null : entry.something == value)
             select entry;

bởi vì nó ngăn chặn sự lặp lại - mặc dù điều đó không chính xác về mặt toán học, nhưng nó phù hợp với hầu hết các trường hợp.


0

Tôi không thể nhận xét bài đăng của jumpga, nhưng trong số các giải pháp khác nhau được trình bày ở đây, giải pháp của jumpga tạo ra SQL tốt nhất. Cả hai hiệu suất khôn ngoan và chiều dài khôn ngoan. Tôi vừa kiểm tra với SQL Server Profiler và bằng cách xem kế hoạch thực thi (với "SET STATISTICS PROFILE ON").


0

Thật không may trong Entity Framework 5 DbContext, vấn đề vẫn chưa được khắc phục.

Tôi đã sử dụng cách giải quyết này (hoạt động với MSSQL 2012 nhưng cài đặt ANSI NULLS có thể không được chấp nhận trong bất kỳ phiên bản MSSQL nào trong tương lai).

public class Context : DbContext
{

    public Context()
        : base("name=Context")
    {
        this.Database.Connection.StateChange += Connection_StateChange;
    }

    void Connection_StateChange(object sender, System.Data.StateChangeEventArgs e)
    {
        // Set ANSI_NULLS OFF when any connection is opened. This is needed because of a bug in Entity Framework
        // that is not fixed in EF 5 when using DbContext.
        if (e.CurrentState == System.Data.ConnectionState.Open)
        {
            var connection = (System.Data.Common.DbConnection)sender;
            using (var cmd = connection.CreateCommand())
            {
                cmd.CommandText = "SET ANSI_NULLS OFF";
                cmd.ExecuteNonQuery();
            }
        }
    }
}

Cần lưu ý rằng đó là một cách giải quyết sai lầm nhưng nó là một cách giải quyết có thể được thực hiện rất nhanh chóng và hoạt động cho tất cả các truy vấn.


Điều này sẽ ngay lập tức ngừng hoạt động khi ANSI NULLS được đặt vĩnh viễn thành BẬT trong phiên bản SQL Server trong tương lai, trong trường hợp cảnh báo không rõ ràng.
Triynko

0

Nếu bạn thích sử dụng cú pháp method (lambda) như tôi, bạn có thể làm điều tương tự như sau:

var result = new TableName();

using(var db = new EFObjectContext)
{
    var query = db.TableName;

    query = value1 == null 
        ? query.Where(tbl => tbl.entry1 == null) 
        : query.Where(tbl => tbl.entry1 == value1);

    query = value2 == null 
        ? query.Where(tbl => tbl.entry2 == null) 
        : query.Where(tbl => tbl.entry2 == value2);

    result = query
        .Select(tbl => tbl)
        .FirstOrDefault();

   // Inspect the value of the trace variable below to see the sql generated by EF
   var trace = ((ObjectQuery<REF_EQUIPMENT>) query).ToTraceString();

}

return result;

-1
var result = from entry in table    
             where entry.something == value||entry.something == null                   
              select entry;

sử dụng nó


5
Điều đó RẤT sai vì nó sẽ chọn tất cả các mục nhập có giá trị khớp VÀ tất cả các mục nhập có giá trị rỗng, ngay cả khi bạn yêu cầu một giá trị.
Michael Stum
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.