Как я могу запросить нулевые значения в структуре сущностей?

109

Я хочу выполнить такой запрос

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

и получите IS NULLсгенерированный.

Отредактировано: после первых двух ответов я чувствую необходимость уточнить, что я использую Entity Framework, а не Linq to 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
Я с Жюльеном, это провал со стороны EF
Мистер Белл
1
Это нарушение стандартов, и сейчас ситуация только ухудшается, когда сравнение с null постоянно приводит к undefined с SQL Server 2016 с постоянно включенными ANSI NULL. Null может представлять неизвестное значение, но сам «null» не является неизвестным значением. Сравнение нулевого значения с нулевым значением должно абсолютно дать истину, но, к сожалению, стандарт отклоняется от здравого смысла, а также от логической логики.
Трийнко 05

Ответы:

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 ! Спасибо всем за поддержку этой ошибки!

Для обратной совместимости это будет опция opt-in - вам нужно вручную включить настройку, чтобы она entry == valueработала. Пока нет информации о том, что это за настройка. Быть в курсе!


Изменить 2: Согласно этому сообщению команды EF, эта проблема была исправлена ​​в EF6! Woohoo!

Мы изменили поведение EF6 по умолчанию, чтобы компенсировать трехзначную логику.

Это означает, что существующий код, который полагается на старое поведение ( null != nullно только при сравнении с переменной) , нужно будет либо изменить, чтобы не полагаться на это поведение, либо установить UseCSharpNullComparisonBehaviorзначение false, чтобы использовать старое нарушенное поведение.

BlueRaja - Дэнни Пфлугхофт
источник
6
Я проголосовал за отчет об ошибке. Надеюсь, они это исправят. Я не могу сказать, что действительно помню, как эта ошибка присутствовала в бета-версии vs2010 ...
noobish
2
ну да ладно майкрософт ... правда?!?!? В версии 4.1?!?! +1
Дэвид
1
Этот обходной путь Linq-To-SQL, похоже, не работает (пытается с Guid?). Использование Entities-Workaround работает в L2S, но генерирует ужасающий SQL. Я должен был сделать оператор if в коде(var result = from ...; if(value.HasValue) result = result.Where(e => e.something == value) else result = result.Where(e => e.something == null);
Майкл Штум
5
Object.Equals действительно работает(where Object.Equals(entry.something,value))
Майкл Штум
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 будет использовать сравнение NULL типа C #.

ITmeze
источник
16

Существует несколько более простой способ обхода, который работает с LINQ to Entities:

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

Это работает, потому что, как заметил AZ, LINQ to Entities особые случаи x == null (то есть сравнение равенства с нулевой константой) и преобразует его в x IS NULL.

В настоящее время мы рассматриваем возможность изменения этого поведения, чтобы автоматически вводить компенсирующие сравнения, если обе стороны равенства допускают значение NULL. Однако есть несколько проблем:

  1. Это может потенциально нарушить код, который уже зависит от существующего поведения.
  2. Новый перевод может повлиять на производительность существующих запросов, даже если нулевой параметр используется редко.

В любом случае, займемся ли мы этим, будет во многом зависеть от относительного приоритета, который придают ему наши клиенты. Если вас волнует эта проблема, я призываю вас проголосовать за нее на нашем новом сайте с предложениями функций: https://data.uservoice.com .

дайвега
источник
9

Если это тип, допускающий значение NULL, может быть, попробовать использовать свойство HasValue?

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

Но здесь нет EF для тестирования ... просто предложение =)

Свиш
источник
1
Что ж ... это работает только в том случае, если вы просто ищете нули, но тогда при использовании == nullошибка все равно не попадает. Дело в том, чтобы выполнить фильтрацию по значению переменной, значение которой может быть нулевым, и чтобы нулевое значение находило нулевые записи.
Дэйв Кузино,
1
Ваш ответ спас меня. Я забыл использовать тип, допускающий значение NULL, в моем классе модели сущностей и не смог заставить (x => x.Column == null)работать. :)
Руэл Рибейро
Это дает System.NullReferenceException , поскольку объект уже нулевой!
TiyebM
5

чтобы иметь дело с нулевым сравнением, используйте 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)
Drzaus
источник
Код, работающий на SQL, будет чище и быстрее, но EF будет генерировать и кэшировать новый план запроса для каждой комбинации перед отправкой на сервер sql, что делает его медленнее, чем другие обходные пути.
Burak Tamtürk
2
var result = from entry in table
                     where entry.something == null
                     select entry;

Вышеупомянутый запрос работает как задумано. Он правильно генерирует IS NULL. Однако мой производственный код был

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

и сгенерированный SQL был something = @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 и посмотрел на план выполнения (с «УСТАНОВИТЬ ПРОФИЛЬ СТАТИСТИКИ ВКЛ»).

Buginator
источник
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();
            }
        }
    }
}

Следует отметить, что это грязный обходной путь, но он может быть реализован очень быстро и работает для всех запросов.

Knais
источник
Это немедленно перестанет работать, как только ANSI NULLS будет постоянно установлен на ON в будущей версии SQL Server, если предупреждение не было ясным.
Трийнко 05
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
Это ОЧЕНЬ неправильно, потому что он выберет все записи, значение которых совпадает, И все записи, где что-то равно нулю, даже если вы запросите значение.
Майкл Штум