Bagaimana saya bisa meminta nilai null dalam kerangka entitas?

109

Saya ingin menjalankan kueri seperti ini

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

dan dapatkan IS NULL.

Diedit: Setelah dua jawaban pertama saya merasa perlu mengklarifikasi bahwa saya menggunakan Entity Framework dan bukan Linq ke SQL. Metode object.Equals () tampaknya tidak berfungsi di EF.

Edit no.2: Kueri di atas berfungsi sebagaimana mestinya. Ini menghasilkan dengan benar IS NULL. Namun kode produksi saya adalah

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

dan SQL yang dihasilkan adalah something = @p; @p = NULL. Tampaknya EF menerjemahkan ekspresi konstan dengan benar tetapi jika sebuah variabel terlibat, itu memperlakukannya seperti perbandingan normal. Sebenarnya masuk akal. Saya akan menutup pertanyaan ini

Adrian Zanescu
sumber
17
Saya pikir itu tidak masuk akal ... Konektor harus sedikit pintar dan tidak meminta kita untuk melakukan tugasnya: melakukan terjemahan yang benar dalam SQL dari kueri C # yang benar. Ini menghasilkan perilaku yang tidak terduga.
Julien N
6
Saya bersama Julien, ini adalah kegagalan dari pihak EF
Mr Bell
1
Ini adalah kegagalan standar, dan itu hanya menjadi lebih buruk sekarang karena perbandingan terhadap nol secara permanen mengakibatkan tidak terdefinisi pada SQL Server 2016 dengan ANSI NULLs secara permanen diaktifkan. Null mungkin mewakili nilai yang tidak diketahui, tetapi "null" itu sendiri bukanlah nilai yang tidak diketahui. Perbandingan nilai nol dengan nilai nol harus benar-benar menghasilkan true, tetapi sayangnya standar tersebut berangkat dari akal sehat serta logika Boolean.
Triynko

Jawaban:

126

Solusi untuk Linq-to-SQL:

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

Solusi untuk Linq-ke-Entitas (aduh!):

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

Ini adalah serangga jahat yang telah menggigit saya beberapa kali. Jika bug ini juga memengaruhi Anda, kunjungi laporan bug di UserVoice dan beri tahu Microsoft bahwa bug ini juga memengaruhi Anda.


Sunting: Bug ini sedang diperbaiki di EF 4.5 ! Terima kasih semuanya telah meningkatkan bug ini!

Untuk kompatibilitas mundur, itu akan diikutsertakan - Anda perlu mengaktifkan pengaturan secara manual agar entry == valueberfungsi. Belum ada kabar tentang apa pengaturan ini. Tetap disini!


Sunting 2: Menurut posting ini oleh tim EF, masalah ini telah diperbaiki di EF6! Woo hoo!

Kami mengubah perilaku default EF6 untuk mengkompensasi logika tiga nilai.

Ini berarti bahwa kode yang ada yang bergantung pada perilaku lama ( null != null, tetapi hanya ketika membandingkan dengan variabel) perlu diubah untuk tidak bergantung pada perilaku itu, atau disetel UseCSharpNullComparisonBehaviorke false untuk menggunakan perilaku lama yang rusak.

BlueRaja - Danny Pflughoeft
sumber
6
Saya telah memilih laporan bug. Semoga mereka memperbaiki ini. Saya tidak dapat mengatakan bahwa saya benar-benar ingat bug ini hadir dalam versi beta
vs2010
2
oh ayolah microsoft ... benarkah?!?!? Dalam versi 4.1?!?! +1
David
1
Solusi Linq-To-SQL itu tampaknya tidak berhasil (mencoba dengan Panduan?). Menggunakan Entities-Workaround berfungsi di L2S, tetapi menghasilkan SQL yang menghebohkan. Saya harus melakukan pernyataan-jika dalam kode(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 sebenarnya berfungsi(where Object.Equals(entry.something,value))
Michael Stum
5
@ leen3o (atau orang lain) - Apakah ada orang yang menemukan di mana dugaan perbaikan ini di EF 4.5 / 5.0? Saya menggunakan 5.0 dan masih bermasalah.
Shaul Behr
17

Sejak Entity Framework 5.0 Anda dapat menggunakan kode berikut untuk menyelesaikan masalah Anda:

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

Ini seharusnya menyelesaikan masalah Anda karena Entity Framerwork akan menggunakan perbandingan nol 'C # like'.

ITmeze
sumber
16

Ada solusi yang sedikit lebih sederhana yang berfungsi dengan LINQ ke Entitas:

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

Ini berfungsi karena, seperti yang diperhatikan AZ, LINQ ke kasus khusus Entitas x == null (yaitu perbandingan kesetaraan terhadap konstanta null) dan menerjemahkannya ke x IS NULL.

Saat ini kami sedang mempertimbangkan untuk mengubah perilaku ini untuk memperkenalkan perbandingan kompensasi secara otomatis jika kedua sisi persamaan dapat dinihilkan. Namun ada beberapa tantangan:

  1. Ini berpotensi merusak kode yang sudah bergantung pada perilaku yang ada.
  2. Terjemahan baru dapat memengaruhi kinerja kueri yang ada bahkan ketika parameter null jarang digunakan.

Bagaimanapun, apakah kita dapat mengerjakan ini akan sangat bergantung pada prioritas relatif yang diberikan pelanggan kita padanya. Jika Anda peduli dengan masalah ini, saya mendorong Anda untuk memilihnya di situs Saran Fitur baru kami: https://data.uservoice.com .

divega
sumber
9

Jika itu adalah tipe nullable, mungkin coba gunakan properti HasValue?

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

Tidak ada EF untuk diuji di sini ... hanya saran =)

Svish
sumber
1
Nah ... ini hanya berfungsi jika Anda hanya mencari null, tetapi kemudian menggunakannya == nulltidak terkena bug. Intinya adalah memfilter berdasarkan nilai variabel, yang nilainya mungkin nol, dan memiliki nilai nol menemukan catatan nol.
Dave Cousineau
1
Jawaban Anda menyelamatkan saya. Saya lupa menggunakan tipe nullable pada kelas model entitas saya dan tidak (x => x.Column == null)dapat berfungsi. :)
Reuel Ribeiro
Ini memberi System.NullReferenceException , karena objek allready adalah null!
TiyebM
5

untuk menangani penggunaan Null Comparisons, Object.Equals()bukan==

periksa referensi ini

Oscar Cabrero
sumber
Ini bekerja dengan sempurna di Linq-To-Sql dan juga menghasilkan SQL yang tepat (beberapa jawaban lain di sini menghasilkan SQL yang menghebohkan atau hasil yang salah).
Michael Stum
Misalkan saya ingin compaire dengan null, Object.Equals(null), bagaimana jika Objectitu sendiri adalah null?
TiyebM
4

Menunjukkan bahwa semua Entity Framework <6.0 saran menghasilkan beberapa SQL yang canggung. Lihat contoh kedua untuk perbaikan "bersih".

Solusi Konyol

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

menghasilkan SQL seperti:

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

Solusi yang Memalukan

Jika Anda ingin menghasilkan SQL yang lebih bersih, sesuatu seperti:

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

menghasilkan apa yang Anda inginkan di tempat pertama:

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)
drzaus
sumber
Kode yang berjalan pada SQL akan lebih bersih dan lebih cepat tetapi EF akan membuat & menyimpan rencana kueri baru untuk setiap kombinasi sebelum mengirimkannya ke server sql, yang membuatnya lebih lambat daripada solusi lainnya.
Burak Tamtürk
2
var result = from entry in table
                     where entry.something == null
                     select entry;

Kueri di atas berfungsi sebagaimana mestinya. Ini dengan benar menghasilkan IS NULL. Namun kode produksi saya adalah

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

dan SQL yang dihasilkan adalah sesuatu = @p; @p = NULL. Tampaknya EF menerjemahkan ekspresi konstan dengan benar tetapi jika sebuah variabel terlibat, itu memperlakukannya seperti perbandingan normal. Sebenarnya masuk akal.

Adrian Zanescu
sumber
1

Tampaknya Linq2Sql juga memiliki "masalah" ini. Tampaknya ada alasan yang sah untuk perilaku ini karena apakah ANSI NULL AKTIF atau NONAKTIF tetapi membingungkan pikiran mengapa "== null" yang lurus sebenarnya akan berfungsi seperti yang Anda harapkan.

JasonCoder
sumber
1

Secara pribadi, saya lebih suka:

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

lebih

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

karena mencegah pengulangan - meskipun secara matematis tidak tepat, tetapi cocok untuk kebanyakan kasus.

Vincent Courcelle
sumber
0

Saya tidak dapat mengomentari posting divega, tetapi di antara berbagai solusi yang disajikan di sini, solusi divega menghasilkan SQL terbaik. Baik kinerja bijaksana maupun panjang. Saya baru saja memeriksa dengan SQL Server Profiler dan dengan melihat rencana eksekusi (dengan "SET STATISTICS PROFILE ON").

Buginator
sumber
0

Sayangnya di Entity Framework 5 DbContext masalah ini masih belum diperbaiki.

Saya menggunakan solusi ini (bekerja dengan MSSQL 2012 tetapi pengaturan ANSI NULLS mungkin tidak berlaku lagi di versi MSSQL di masa mendatang).

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

Perlu dicatat bahwa ini adalah solusi yang kotor tetapi dapat diterapkan dengan sangat cepat dan berfungsi untuk semua kueri.

Knaģis
sumber
Ini akan segera berhenti berfungsi setelah ANSI NULLS secara permanen disetel ke ON di versi SQL Server yang akan datang, jika peringatan tidak jelas.
Triynko
0

Jika Anda lebih suka menggunakan sintaks metode (lambda) seperti yang saya lakukan, Anda dapat melakukan hal yang sama seperti ini:

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;
John Meyer
sumber
-1
var result = from entry in table    
             where entry.something == value||entry.something == null                   
              select entry;

gunakan itu

Andrew
sumber
5
Itu SANGAT salah karena itu akan memilih semua entri yang nilainya cocok DAN semua entri di mana ada yang nol, bahkan jika Anda meminta sebuah nilai.
Michael Stum