Як я можу запитати нульові значення в рамках сутності?


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 #. Це породжує несподівану поведінку.
Жульєн N

6
Я з Жюльєном, це невдача з боку EF
містер Белл

1
Це невдача стандартів, і лише погіршується тепер, коли порівняння з null назавжди призводить до невизначеності, як у SQL Server 2016, з ANSI NULLs постійно встановлено. Null може представляти невідоме значення, але сам "null" не є невідомим значенням. Порівняння нульового значення з нульовим значенням повинно бути абсолютно істинним, але, на жаль, стандарт відходить від здорового глузду, а також булевої логіки.
Трайнко

Відповіді:


126

Вирішення питання для Linq-SQL:

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

Обхід для Linq-до-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! Woohoo!

Ми змінили поведінку за замовчуванням EF6, щоб компенсувати тризначну логіку.

Це означає, що існуючий код, який спирається на стару поведінку ( null != nullале лише при порівнянні зі змінною) , або повинен бути змінений, щоб не покладатися на цю поведінку, або встановлений UseCSharpNullComparisonBehaviorна хибний, щоб використовувати стару порушену поведінку.


6
Я проголосував за повідомлення про помилку. Сподіваємось, вони це виправлять. Я не можу сказати, що я дійсно пам'ятаю, що ця помилка присутня у бета-версії vs2010 ...
noobish

2
ой на мікрософт ... справді?!?!? У версії 4.1?!?! +1
Девід

1
Схоже, що вирішення Linq-To-SQL не працює (намагається використовувати Guid?). Використання Entities-Workkaround працює в L2S, але генерує жахливий SQL. Мені довелося зробити if-заяву в коді(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, і він все ще не поводиться.
Шауль Бехр

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 для Entities:

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

Це працює, оскільки AZ зауважив, що LINQ для Entities особливих випадків x == null (тобто порівняння рівності проти нульової постійної) і переводить його на x IS NULL.

Зараз ми розглядаємо можливість зміни такої поведінки для автоматичного введення компенсуючих порівнянь, якщо обидві сторони рівності є нульовими. Однак є кілька проблем:

  1. Це потенційно може порушити код, який вже залежить від існуючої поведінки.
  2. Новий переклад може вплинути на ефективність існуючих запитів, навіть коли нульовий параметр рідко використовується.

У будь-якому випадку, чи будемо ми працювати над цим, буде дуже залежати від відносного пріоритету, який наші клієнти присвоюють йому. Якщо ви піклуєтесь про проблему, я рекомендую проголосувати за це на нашому новому веб-сайті із пропозиціями щодо функцій: https://data.uservoice.com .


9

Якщо це зведений тип, можливо, спробуйте скористатися властивістю HasValue?

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

Не майте жодної EF для тестування тут, хоча ... просто пропозиція =)


1
Ну ... це працює лише в тому випадку, якщо ви просто шукаєте нулі, але потім ви == nullне користуєтеся помилкою. Суть полягає у фільтруванні за значенням змінної, значення якої може бути нульовим, а значення null знайти null записи.
Дьюз Кузен

1
Ваша відповідь врятувала мене. Я забув використати нульовий тип на моєму модельному класі моделей і не зміг змусити (x => x.Column == null)працювати. :)
Реуель Рібейро

Це дає System.NullReferenceException , оскільки об'єкт уже нульовий!
TiyebM


5

мати справу з використанням Null Comparons Object.Equals() замість==

перевірити це посилання


Це чудово працює в Linq-To-Sql, а також генерує належний SQL (деякі інші відповіді тут генерують жахливі SQL або неправильні результати).
Майкл Стум

Припустимо , я хочу 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, що робить його повільніше, ніж інші обхідні шляхи.
Бурак Тамтурк

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 NULLs увімкнено чи вимкнено, але він марить розумом, чому прямий "== 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 і переглянув план виконання (з "ВКЛЮЧИТИ ПРОФІЛЬ СТАТИСТИКИ").


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, якщо попередження не було зрозумілим.
Трайко

0

Якщо ви віддаєте перевагу використанню синтаксису методу (лямбда), як і я, ви можете зробити те саме, що і це:

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
Це ДУЖЕ неправильно, оскільки він вибере всі записи, де значення відповідає І всі записи, де щось недійсне, навіть якщо ви попросите значення.
Майкл Стум
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.