ฉันจะสอบถามค่าว่างในกรอบงานเอนทิตีได้อย่างไร


109

ฉันต้องการดำเนินการค้นหาเช่นนี้

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

และIS NULLสร้าง

แก้ไข: หลังจากสองคำตอบแรกฉันรู้สึกว่าจำเป็นต้องชี้แจงว่าฉันใช้ Entity Framework ไม่ใช่ Linq กับ SQL เมธอด object.Equals () ดูเหมือนจะไม่ทำงานใน EF

แก้ไขหมายเลข 2: ข้อความค้นหาข้างต้นทำงานได้ตามที่ตั้งใจไว้ IS NULLได้อย่างถูกต้องสร้าง อย่างไรก็ตามรหัสการผลิตของฉันคือ

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

และ SQL something = @p; @p = NULLสร้างเป็น ดูเหมือนว่า EF จะแปลนิพจน์ค่าคงที่ได้อย่างถูกต้อง แต่หากมีตัวแปรเข้ามาเกี่ยวข้องจะถือว่ามันเหมือนกับการเปรียบเทียบปกติ ทำให้รู้สึกจริง ฉันจะปิดคำถามนี้


17
ฉันคิดว่ามันไม่สมเหตุสมผลจริงๆ ... ตัวเชื่อมต่อควรฉลาดเล็กน้อยและไม่ขอให้เราทำงาน: ทำการแปลที่ถูกต้องใน SQL ของแบบสอบถาม C # ที่ถูกต้อง สิ่งนี้ก่อให้เกิดพฤติกรรมที่ไม่คาดคิด
Julien N

6
ฉันอยู่กับ Julien นี่เป็นความล้มเหลวในส่วนของ EF
Mr Bell

1
นี่เป็นความล้มเหลวของมาตรฐานและมี แต่จะแย่ลงเรื่อย ๆ ในตอนนี้การเปรียบเทียบกับค่าว่างจะส่งผลให้ไม่ได้กำหนดไว้อย่างถาวรเมื่อ SQL Server 2016 โดยมี ANSI NULLs ตั้งค่าเป็นเปิดอย่างถาวร Null อาจแสดงถึงค่าที่ไม่รู้จัก แต่ตัว "null" ไม่ใช่ค่าที่ไม่รู้จัก การเปรียบเทียบค่า null กับค่า null ควรให้ผลลัพธ์เป็นจริงอย่างแน่นอน แต่น่าเสียดายที่มาตรฐานแยกออกจากสามัญสำนึกและตรรกะบูลีน
Triynko

คำตอบ:


126

วิธีแก้ปัญหาสำหรับ Linq-to-SQL:

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

วิธีแก้ปัญหาสำหรับ Linq-to-Entities (อุ๊ย!):

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

นี่เป็นแมลงที่น่ารังเกียจซึ่งกัดฉันหลายครั้ง หากข้อบกพร่องนี้ส่งผลกระทบต่อคุณเช่นกันโปรดไปที่รายงานข้อบกพร่องใน UserVoiceและแจ้งให้ Microsoft ทราบว่าข้อบกพร่องนี้ส่งผลกระทบต่อคุณเช่นกัน


แก้ไข: ข้อบกพร่องนี้ได้รับการแก้ไขใน EF 4.5 ! ขอบคุณทุกคนสำหรับการโหวตข้อบกพร่องนี้!

สำหรับความเข้ากันได้แบบย้อนหลังระบบจะเลือกใช้ - คุณต้องเปิดใช้งานการตั้งค่าด้วยตนเองเพื่อสร้าง entry == valueทำงานได้ ยังไม่มีคำว่าการตั้งค่านี้คืออะไร คอยติดตาม!


แก้ไข 2: ตามกระทู้นี้โดยทีม EF ปัญหานี้ได้รับการแก้ไขแล้วใน EF6! วู้ฮู้!

เราเปลี่ยนพฤติกรรมเริ่มต้นของ EF6 เพื่อชดเชยตรรกะสามค่า

ซึ่งหมายความว่าโค้ดที่มีอยู่ซึ่งอาศัยพฤติกรรมเก่า( null != nullแต่เมื่อเปรียบเทียบกับตัวแปรเท่านั้น)จะต้องมีการเปลี่ยนแปลงเพื่อไม่ขึ้นอยู่กับพฤติกรรมนั้นหรือตั้งค่าUseCSharpNullComparisonBehaviorเป็นเท็จเพื่อใช้พฤติกรรมเก่าที่เสียไป


6
ฉันโหวตรายงานข้อบกพร่องแล้ว หวังว่าพวกเขาจะแก้ไขปัญหานี้ ฉันไม่สามารถพูดได้ว่าฉันจำข้อผิดพลาดนี้ใน vs2010 beta ได้จริงๆ ...
noobish

2
โอ้มาที่ microsoft ... จริงเหรอ!?!? ในเวอร์ชั่น 4.1?!?! +1
David

1
วิธีแก้ปัญหา Linq-To-SQL นั้นดูเหมือนจะไม่ได้ผล (ลองใช้ Guid?) การใช้เอนทิตี - วิธีแก้ปัญหาทำงานใน L2S แต่สร้าง SQL ที่น่ากลัว ฉันต้องทำ if-statement ในรหัส(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 ใช้งานได้จริง(where Object.Equals(entry.something,value))
Michael Stum

5
@ leen3o (หรือใครก็ได้) - ยังมีใครพบว่าการแก้ไขที่ถูกกล่าวหานี้อยู่ใน EF 4.5 / 5.0 หรือไม่? ฉันใช้ 5.0 และยังทำงานผิดปกติ
Shaul Behr

17

เนื่องจาก Entity Framework 5.0 คุณสามารถใช้รหัสต่อไปนี้เพื่อแก้ปัญหาของคุณ:

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

สิ่งนี้จะช่วยแก้ปัญหาของคุณได้เนื่องจาก Entity Framerwork จะใช้การเปรียบเทียบค่าว่าง 'C # like'


16

มีวิธีแก้ปัญหาที่ง่ายกว่าเล็กน้อยที่ใช้ได้กับ LINQ ถึงเอนทิตี:

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

สิ่งนี้ใช้ได้ผลตามที่ AZ สังเกต LINQ ถึงเอนทิตีกรณีพิเศษ x == null (เช่นการเปรียบเทียบความเท่าเทียมกับค่าคงที่ว่าง) และแปลเป็น x IS NULL

ขณะนี้เรากำลังพิจารณาเปลี่ยนแปลงพฤติกรรมนี้เพื่อแนะนำการเปรียบเทียบการชดเชยโดยอัตโนมัติหากทั้งสองด้านของความเท่าเทียมกันเป็นโมฆะ มีความท้าทายสองสามประการ:

  1. ซึ่งอาจทำให้โค้ดแตกซึ่งขึ้นอยู่กับพฤติกรรมที่มีอยู่แล้ว
  2. การแปลใหม่อาจส่งผลต่อประสิทธิภาพของการสืบค้นที่มีอยู่แม้ว่าจะแทบไม่ได้ใช้พารามิเตอร์ null ก็ตาม

ไม่ว่าในกรณีใดการที่เราจะทำงานนี้จะขึ้นอยู่กับลำดับความสำคัญสัมพัทธ์ที่ลูกค้าของเรากำหนดให้ หากคุณสนใจเกี่ยวกับปัญหาที่ผมแนะนำให้คุณโหวตให้ไว้ในเว็บไซต์ของข้อเสนอแนะคุณสมบัติใหม่ของเรา: https://data.uservoice.com


9

หากเป็นประเภทที่ว่างเปล่าอาจลองใช้คุณสมบัติ HasValue?

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

ไม่มี EF ให้ทดสอบที่นี่เลย ... แค่ข้อเสนอแนะ =)


1
ใช้งานได้ก็ต่อเมื่อคุณแค่มองหาค่าว่าง แต่การใช้== nullก็จะไม่โดนบั๊กอยู่ดี ประเด็นคือการกรองตามค่าของตัวแปรซึ่งค่าอาจเป็นค่าว่างและให้ค่า null ค้นหาระเบียน null
Dave Cousineau

1
คำตอบของคุณช่วยฉันไว้ ฉันลืมใช้ประเภทที่ว่างเปล่าในคลาสโมเดลเอนทิตีของฉันและไม่สามารถใช้(x => x.Column == null)งานได้ :)
Reuel Ribeiro

สิ่งนี้ทำให้System.NullReferenceException เนื่องจากวัตถุ allready เป็นโมฆะ!
TiyebM


5

เพื่อจัดการกับ Null Comparisons ใช้ Object.Equals()แทน==

ตรวจสอบข้อมูลอ้างอิงนี้


สิ่งนี้ทำงานได้อย่างสมบูรณ์ใน Linq-To-Sql และยังสร้าง SQL ที่เหมาะสม (คำตอบอื่น ๆ ที่นี่สร้าง SQL ที่น่ากลัวหรือผลลัพธ์ที่ไม่ถูกต้อง)
Michael Stum

สมมติว่าผมต้องการที่จะ compaire มีnull, Object.Equals(null)สิ่งที่ถ้าObjectตัวเองเป็นโมฆะ?
TiyebM

4

ชี้ให้เห็นว่าคำแนะนำ Entity Framework <6.0 ทั้งหมดสร้าง SQL ที่น่าอึดอัดใจ ดูตัวอย่างที่สองสำหรับการแก้ไข "สะอาด"

วิธีแก้ปัญหาที่ไร้สาระ

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

ผลลัพธ์ใน SQL เช่น:

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

วิธีแก้ปัญหาอุกอาจ

หากคุณต้องการสร้าง SQL ที่สะอาดขึ้นสิ่งที่ต้องการ:

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

ผลลัพธ์ในสิ่งที่คุณต้องการตั้งแต่แรก:

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)

โค้ดที่ทำงานบน SQL จะสะอาดและเร็วขึ้น แต่ EF จะสร้างและแคชแผนการสืบค้นใหม่สำหรับทุกชุดค่าผสมก่อนที่จะส่งไปยังเซิร์ฟเวอร์ sql ซึ่งทำให้ช้ากว่าวิธีแก้ปัญหาอื่น ๆ
Burak Tamtürk

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

ข้อความค้นหาข้างต้นทำงานได้ตามที่ตั้งใจไว้ มันสร้างเป็นโมฆะอย่างถูกต้อง อย่างไรก็ตามรหัสการผลิตของฉันคือ

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

และ SQL ที่สร้างขึ้นคืออะไร = @p; @p = NULL ดูเหมือนว่า EF จะแปลนิพจน์ค่าคงที่ได้อย่างถูกต้อง แต่หากมีตัวแปรเข้ามาเกี่ยวข้องจะถือว่ามันเหมือนกับการเปรียบเทียบปกติ ทำให้รู้สึกจริง


1

ปรากฏว่า Linq2Sql มี "ปัญหา" นี้เช่นกัน ดูเหมือนว่ามีเหตุผลที่ถูกต้องสำหรับพฤติกรรมนี้เนื่องจากว่า ANSI NULL เปิดหรือปิดอยู่ แต่มันทำให้ใจคิดว่าทำไม "== null" แบบตรงจึงทำงานได้ตามที่คุณคาดหวัง


1

โดยส่วนตัวแล้วฉันชอบ:

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

เกิน

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

เพราะป้องกันการทำซ้ำแม้ว่าจะไม่ตรงตามหลักคณิตศาสตร์ แต่ก็เข้ากันได้ดีกับกรณีส่วนใหญ่


0

ฉันไม่สามารถแสดงความคิดเห็นในโพสต์ของ divega ได้ แต่ในบรรดาโซลูชันต่างๆที่นำเสนอที่นี่โซลูชันของ Divega สร้าง SQL ที่ดีที่สุด ทั้งประสิทธิภาพที่ชาญฉลาดและความยาวที่ชาญฉลาด ฉันเพิ่งตรวจสอบด้วย SQL Server Profiler และดูแผนการดำเนินการ (โดยมี "SET STATISTICS PROFILE ON")


0

น่าเสียดายที่ใน Entity Framework 5 DbContext ปัญหายังไม่ได้รับการแก้ไข

ฉันใช้วิธีแก้ปัญหานี้ (ใช้ได้กับ MSSQL 2012 แต่การตั้งค่า ANSI NULLS อาจเลิกใช้งานในเวอร์ชัน MSSQL ในอนาคต)

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

ควรสังเกตว่าเป็นวิธีแก้ปัญหาที่สกปรก แต่เป็นวิธีที่สามารถนำไปใช้งานได้อย่างรวดเร็วและใช้ได้กับทุกข้อความค้นหา


สิ่งนี้จะหยุดทำงานทันทีเมื่อ ANSI NULLS ถูกตั้งค่าเป็นเปิดอย่างถาวรใน SQL Server เวอร์ชันอนาคตในกรณีที่คำเตือนไม่ชัดเจน
Triynko

0

หากคุณต้องการใช้ไวยากรณ์ method (lambda) เหมือนที่ฉันทำคุณสามารถทำสิ่งเดียวกันนี้ได้:

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;

ใช้สิ่งนั้น


5
นั่นผิดมากเพราะจะเลือกรายการทั้งหมดที่ค่าตรงกันและรายการทั้งหมดที่บางอย่างเป็นโมฆะแม้ว่าคุณจะขอค่าก็ตาม
Michael Stum
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.