Предложение LINQ where с лямбда-выражением, содержащим предложения OR и нулевые значения, возвращающие неполные результаты - PullRequest
12 голосов
/ 15 марта 2011

вкратце проблема

у нас есть лямбда-выражение, используемое в предложении Where, которое не возвращает «ожидаемый» результат.

quicksummary

в объекте analysisObjectRepository есть определенные объекты, которые также содержат родительские отношения в свойстве с именем Parent.мы запрашиваем у данного analysisObjectRepository возвращение некоторых объектов.

detail

, что должен делать код ниже, возвращая корень, первых потомков (непосредственных потомков) ивнуки определенного объекта, содержащего значение идентификатора.

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

List<AnalysisObject> analysisObjects = 
    analysisObjectRepository
        .FindAll()
        .Where(x => x.ID               == packageId ||
                    x.Parent.ID        == packageId || 
                    x.Parent.Parent.ID == packageId)
        .ToList();

, но приведенный выше код возвращает только дочерние элементы и внуки, не возвращая корневые объекты (с нулевым родительским значением), которые делают условие

x.ID == packageId

истинным.

возвращаются только объекты, которые составляют второе

x.Parent.ID == packageId

и третье

x.Parent.Parent.ID == packageId

.

Если мы только напишем код для возвратаКорневой объект с приведенным ниже кодом, он возвращается, поэтому мы полностью уверены, что analysisObjectRepository содержит все объекты

List<AnalysisObject> analysisObjects = 
    analysisObjectRepository
        .FindAll()
        .Where(x => x.ID == packageId )
        .ToList();

Однако, когда мы переписываем его как делегат, мы получаем ожидаемый результат, возвращая всеожидаемые объекты.

List<AnalysisObject> analysisObjects = 
    analysisObjectRepository
        .FindAll()
        .Where(delegate(AnalysisObject x) 
        { 
            return 
              (x.ID == packageId) || 
              (x.Parent != null && x.Parent.ID == packageId) || 
                  (x.Parent != null && 
                   x.Parent.Parent != null && 
                   x.Parent.Parent.ID == packageId); })
        .ToList();

вопрос

Мы что-то упускаем в лямбда-выражении?это действительно простое условие из трех частей ИЛИ, и мы думаем, что любой объект, который выполняет любое из трех условий, должен быть возвращен.мы подозревали, что корневой объект, имеющий нулевое значение Parent, может вызвать проблему, но не можем точно это выяснить.

любая помощь будет полезна.

Ответы [ 3 ]

12 голосов
/ 15 марта 2011

Ваш второй делегат не переписывает первый в формате анонимного делегата (а не лямбда).Посмотрите на ваши условия.

Первый:

x.ID == packageId || x.Parent.ID == packageId || x.Parent.Parent.ID == packageId

Второй:

(x.ID == packageId) || (x.Parent != null && x.Parent.ID == packageId) || 
(x.Parent != null && x.Parent.Parent != null && x.Parent.Parent.ID == packageId)

При вызове лямбды будет выдано исключение для любого x, где идентификаторне совпадает, и родительский элемент является нулевым или не совпадает, а дедушка является нулевым.Скопируйте нулевые проверки в лямбду, и она должна работать правильно.

Редактировать после комментария к вопросу

Если ваш исходный объект не является List<T>, то у нас нет возможности узнать, чтотип возвращаемого значения FindAll() есть, и реализует ли это интерфейс IQueryable.Если это так, то это, вероятно, объясняет расхождение.Поскольку лямбды могут быть преобразованы во время компиляции в Expression<Func<T>> , но анонимные делегаты не могут , тогда вы можете использовать реализацию IQueryable при использовании лямбда-версии, но LINQ-to-Objects при использовании анонимноговерсия делегата.

Это также объясняет, почему ваша лямбда не вызывает NullReferenceException.Если бы вы передали это лямбда-выражение чему-то, что реализует IEnumerable<T>, но , а не IQueryable<T>, оценка лямбды во время выполнения (которая ничем не отличается от других методов, анонимных или нет) выдаст NullReferenceException в первый раз он столкнулся с объектом, где ID не был равен цели, а родитель или дедушка были нулевыми.

Добавлено 16.03.2011, 8:29 EDT

Рассмотримследующий простой пример:

IQueryable<MyObject> source = ...; // some object that implements IQueryable<MyObject>

var anonymousMethod =  source.Where(delegate(MyObject o) { return o.Name == "Adam"; });    
var expressionLambda = source.Where(o => o.Name == "Adam");

Эти два метода дают совершенно разные результаты.

Первый запрос - это простая версия.Результатом анонимного метода является делегат, который затем передается методу расширения IEnumerable<MyObject>.Where, где все содержимое source будет проверено (вручную в памяти с использованием обычного скомпилированного кода) для вашего делегата.Другими словами, если вы знакомы с блоками итераторов в C #, это примерно так:

public IEnumerable<MyObject> MyWhere(IEnumerable<MyObject> dataSource, Func<MyObject, bool> predicate)
{
    foreach(MyObject item in dataSource)
    {
        if(predicate(item)) yield return item;
    }
}

Существенным моментом здесь является то, что вы фактически выполняете фильтрацию в памяти на стороне клиента.Например, если бы вашим источником был какой-то SQL ORM, в запросе не было бы предложения WHERE;весь набор результатов будет возвращен клиенту и отфильтрован там .

Второй запрос, который использует лямбда-выражение, преобразуется в Expression<Func<MyObject, bool>> и использует IQueryable<MyObject>.Where()метод расширения.Это приводит к объекту, который также напечатан как IQueryable<MyObject>.Все это работает, передав выражение базовому поставщику. Вот почему вы не получаете NullReferenceException.Поставщик запросов полностью должен преобразовать выражение (которое, вместо того, чтобы быть фактически скомпилированной функцией, которую она может просто вызвать, является представлением логики выражения, использующего объекты), во что-то, что оно можетиспользовать.

Простой способ увидеть различие (или, по крайней мере, то, что есть ) различие, было бы сделать вызов AsEnumerable() перед вашим вызовом Where в лямбда-версии.Это заставит ваш код использовать LINQ-to-Objects (это означает, что он работает с IEnumerable<T>, как версия с анонимным делегатом, а не с IQueryable<T>, как в настоящее время с лямбда-версией), и вы получите ожидаемые исключения.

TL; DR версия

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

6 голосов
/ 15 марта 2011

Попробуйте написать лямбду с теми же условиями, что и у делегата. как это:

  List<AnalysisObject> analysisObjects = 
    analysisObjectRepository.FindAll().Where(
    (x => 
       (x.ID == packageId)
    || (x.Parent != null && x.Parent.ID == packageId)
    || (x.Parent != null && x.Parent.Parent != null && x.Parent.Parent.ID == packageId)
    ).ToList();
2 голосов
/ 15 марта 2011

Вы проверяете Parent свойства для нулевого в вашем делегате.То же самое должно работать и с лямбда-выражениями.

List<AnalysisObject> analysisObjects = analysisObjectRepository
        .FindAll()
        .Where(x => 
            (x.ID == packageId) || 
            (x.Parent != null &&
                (x.Parent.ID == packageId || 
                (x.Parent.Parent != null && x.Parent.Parent.ID == packageId)))
        .ToList();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...