Поделиться через


Сравнения значений NULL

Значение null в источнике данных указывает, что значение неизвестно. В запросах LINQ to Entities можно проверить значения null, чтобы определенные вычисления или сравнения выполнялись только в строках с корректными или ненулевыми данными. Однако семантика CLR null может отличаться от семантики NULL источника данных. Большинство баз данных используют версию трехзначной логики для обработки сравнения значений NULL. То есть сравнение со значением NULL не оценивается как значение true или false, оно оценивается как значение unknown. Часто это реализация ANSI NULL, но это не всегда так.

По умолчанию в SQL Server сравнение null-equals-NULL возвращает значение NULL. В следующем примере строки, в которых ShipDate имеет значение NULL, исключаются из результирующий набор, а инструкция Transact-SQL возвращает 0 строк.

-- Find order details and orders with no ship date.  
SELECT h.SalesOrderID  
FROM Sales.SalesOrderHeader h  
JOIN Sales.SalesOrderDetail o ON o.SalesOrderID = h.SalesOrderID  
WHERE h.ShipDate IS Null  

Это очень отличается от семантики CLR NULL, где сравнение NULL равно null возвращает значение true.

Следующий запрос LINQ выражается в среде CLR, но он выполняется в источнике данных. Поскольку в источнике данных нет гарантии соблюдения семантики CLR, ожидаемое поведение невозможно определить.

using (AdventureWorksEntities context = new AdventureWorksEntities())
{
    ObjectSet<SalesOrderHeader> orders = context.SalesOrderHeaders;
    ObjectSet<SalesOrderDetail> details = context.SalesOrderDetails;

    var query =
        from order in orders
        join detail in details
        on order.SalesOrderID
        equals detail.SalesOrderID
        where order.ShipDate == null
        select order.SalesOrderID;

    foreach (var OrderID in query)
    {
        Console.WriteLine($"OrderID : {OrderID}");
    }
}
Using context As New AdventureWorksEntities()

    Dim orders As ObjectSet(Of SalesOrderHeader) = context.SalesOrderHeaders
    Dim details As ObjectSet(Of SalesOrderDetail) = context.SalesOrderDetails

    Dim query = _
        From order In orders _
        Join detail In details _
        On order.SalesOrderID _
        Equals detail.SalesOrderID _
        Where order.ShipDate = Nothing
        Select order.SalesOrderID


    For Each orderID In query
        Console.WriteLine("OrderID: {0} ", orderID)
    Next
End Using

Селекторы ключей

Выборка ключа — это функция, используемая в стандартных операторах запросов для извлечения ключа из элемента. В функции селектора ключей выражение можно сравнить с константой. Семантика CLR null отображается, если выражение сравнивается с константой NULL или если сравниваются две константы NULL. Если в источнике данных сравниваются два столбца с null-значениями, проявляется семантика null. Селекторы ключей находятся во многих стандартных операторах запросов группирования и упорядочивания, таких как GroupBy, и используются для выбора ключей, по которым можно упорядочить или сгруппировать результаты запроса.

Свойство NULL на объекте NULL

В Entity Framework свойства объекта NULL имеют значение NULL. При попытке ссылаться на свойство null-объекта в среде CLR вы получите NullReferenceException. Если запрос LINQ включает свойство пустого объекта, это может привести к несогласованности поведения.

Например, в следующем запросе приведение к NewProduct выполняется на уровне дерева команд, что может привести к значению null свойства Introduced. Если база данных определила сравнения NULL таким образом, что сравнение DateTime оценивается как истинное, строка будет включена.

using (AdventureWorksEntities context = new AdventureWorksEntities())
{

    DateTime dt = new DateTime();
    var query = context.Products
        .Where(p => (p as NewProduct).Introduced > dt)
        .Select(x => x);
}
Using context As New AdventureWorksEntities()
    Dim dt As DateTime = New DateTime()
    Dim query = context.Products _
        .Where(Function(p) _
            ((DirectCast(p, NewProduct)).Introduced > dt)) _
        .Select(Function(x) x)
End Using

Передача пустых коллекций в агрегатные функции

При передаче коллекции, поддерживающей IQueryable в агрегатную функцию, агрегатные операции выполняются в базе данных в LINQ to Entities. В результатах запроса, выполняемого в памяти, и запроса, выполненного в базе данных, могут быть различия. При запросе в памяти, если совпадений нет, запрос возвращает ноль. В базе данных тот же запрос возвращает null. Если значение null передано в агрегатную функцию LINQ, будет выброшено исключение. Чтобы принять возможные значения null, необходимо привести типы и свойства типов, которые получают результаты запросов, к допускающим NULL типам значений.

См. также