|| (или) Оператор в Linq с C #

Я использую linq для фильтрации выборки MessageItems. Написанный мной метод принимает множество параметров, которые могут быть нулевыми. Если они равны нулю, критерии для файла следует игнорировать. Если он не равен нулю, используйте его для фильтрации результатов.

Насколько я понимаю, при выполнении || операция - C #, если первое выражение истинно, второе выражение не должно оцениваться.

e.g.

if(ExpressionOne() || ExpressionTwo())
{
     // only ExpressionOne was evaluated because it was true
}

теперь, в linq, я пробую это:

var messages = (from msg in dc.MessageItems
where  String.IsNullOrEmpty(fromname) || (!String.IsNullOrEmpty(fromname) && msg.FromName.ToLower().Contains(fromname.ToLower()))
select msg);

Я бы подумал, что это будет правильно, потому что String.IsNullOrEmpty(fromname) будет равно true и второй части || не побежит.

Однако он запускается, и вторая часть

msg.FromName.ToLower().Contains(fromname.ToLower()))

выдает исключение с нулевой ссылкой (потому что fromname имеет значение NULL) !! - Я получаю классическое исключение «Ссылка на объект не установлена ​​на экземпляр объекта».

Любая помощь?


person Ev.    schedule 21.04.2009    source источник
comment
Важно знать, если это LINQ to Objects? LINQ to SQL? LINQ to Entities? и т.д ... Ваше предположение, что || will гарантируется только в LINQ to Objects, поскольку ваш код не транслируется (например, в T-SQL).   -  person Lucas    schedule 21.04.2009
comment
Круто. Хорошо, Лукас, я использую Linq to SQL. Я думаю, вы действительно знаете, о чем я говорю. Значит, вы говорите, что в Linq to SQL не ожидали, что это сработает? Как мне сделать это в Linq to SQL? Я просто хочу сказать: если переданный параметр равен нулю, тогда получить запись независимо от совпадения, если он не равен нулю, тогда убедитесь, что каждая возвращенная запись соответствует переданному параметру. Большое спасибо!   -  person Ev.    schedule 21.04.2009
comment
Ev, если это linq to sql, тогда оценка короткого замыкания просто не то, на что можно положиться полная остановка   -  person ShuggyCoUk    schedule 21.04.2009
comment
Хорошо, это круто @ShuggyCoUk. Я ценю вклад! Вы понимаете, что я здесь пытаюсь сделать? Похоже, мне что-то совершенно не хватает! Я знаю, что это будет работать в SQL, и я знаю, что это будет работать в C #, поэтому в Linq to SQL должен быть какой-то эквивалент. Я прав? Итак, все, что я хочу сказать, это в этом конкретном предложении where либо получить все строки, если параметр fromname имеет значение null. Если fromname НЕ ПУСТО, убедитесь, что мы соответствуем параметру. Поймай меня? Еще раз спасибо за вашу помощь, приятель   -  person Ev.    schedule 21.04.2009
comment
Вы не используете LINQ to SQL, потому что (1) метод Boolean IsNullOrEmpty (System.String) не поддерживает преобразование в SQL и (2) вы не получите исключение NullReferenceException (это произойдет только на стороне клиента, а не на БД).   -  person Lucas    schedule 23.04.2009


Ответы (6)


Прочтите эту документацию, в которой объясняется, как linq и C # могут испытывать Отключить.

Поскольку ожидается, что выражения Linq будут сокращены до чего-то другого, кроме простых методов, вы можете обнаружить, что этот код ломается, если позже он используется в каком-либо контексте, отличном от Linq to Objects.

Это сказал

String.IsNullOrEmpty(fromname) || 
(   !String.IsNullOrEmpty(fromname) && 
    msg.FromName.ToLower().Contains(fromname.ToLower())
)

Плохо сформирован, так как он действительно должен быть

String.IsNullOrEmpty(fromname) || 
msg.FromName.ToLower().Contains(fromname.ToLower())

что делает приятным и ясным, что вы полагаетесь на то, что msg и msg.FromName оба также не равны нулю.

Чтобы упростить себе жизнь в C #, вы можете добавить следующий метод расширения строки

public static class ExtensionMethods
{
    public static bool Contains(
        this string self, string value, StringComparison comparison)
    {
        return self.IndexOf(value, comparison) >= 0;
    }

    public static bool ContainsOrNull(
        this string self, string value, StringComparison comparison)
    {
        if (value == null)
            return false;
        return self.IndexOf(value, comparison) >= 0;
    }
}

Затем используйте:

var messages = (from msg in dc.MessageItems
where  msg.FromName.ContainsOrNull(
    fromname, StringComparison.InvariantCultureIgnoreCase)
select msg);

Однако проблема не в этом. Проблема в том, что аспекты системы Linq to SQL пытаются использовать значение fromname для построения запроса, который отправляется на сервер.

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

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

Возможно, лучше было бы:

IEnumerable<MessageItem> results;
if (string.IsNullOrEmpty(fromname))
{ 
    results = from msg in dc.MessageItems 
    select msg;    
}
else
{
    results = from msg in dc.MessageItems 
    where msg.FromName.ToLower().Contains(fromname) 
    select msg;    
}

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

person ShuggyCoUk    schedule 21.04.2009
comment
Ура, Шугги. Я сейчас пролистаю эти документы. Я согласен, это было немного отстойно - не нужен был! String.IsNullOrEmpty (fromname) && (я думаю, что это закралось, когда я тестировал), но это все еще решает мою проблему. Я обновил эту строку, указав String.IsNullOrEmpty (fromname) || msg.FromName.ToLower (). Contains (fromname.ToLower ()) Как вы предложили - теперь пользу. Я сейчас пролистаю документацию. Отправьте сообщение, если вы думаете о чем-то другом! - person Ev.; 21.04.2009
comment
foo.Bar shoudld fail - foo is null, но я спрашиваю, что, если я скажу if (foo == null || foo.Bar ()) Это не должно вызывать нулевую ссылку. должно это? Так почему он бросает это в Linq? У меня такое чувство, что мне здесь не хватает чего-то действительно важного !! Дай мне знать, если поймешь! Я здесь вообще не использую никаких расширений. :) - person Ev.; 21.04.2009
comment
@Ev Я предлагаю вам использовать метод расширения, а затем, заказывая не имеет значения, вы переводите проблему в удобочитаемый и функциональный метод расширения - person ShuggyCoUk; 21.04.2009
comment
@Ev ваше впечатление, что (a == null || use (a)) является законным, является неправильным в Linq, просто извините. Вы пробовали мой метод расширения с измененным предложением where? - person ShuggyCoUk; 21.04.2009
comment
@ShuggyCoUk: Думаю, теперь я тебя понял. Напишите расширение, которое сделает это за меня. Я собираюсь попробовать это прямо сейчас. Звучит разумно. У вас есть объяснение, почему это неправильно? Просто потому, что мне любопытно, потому что логика мне кажется здравой. Возможно, это по-другому оценивается. Я просмотрел те документы, которые вы упомянули, и если я делаю это, там не упоминается, но, возможно, это подразумевается другими правилами, которые, возможно, были выше моей головы. Я проверю еще раз. - person Ev.; 21.04.2009
comment
поскольку такие вещи, как sql-движки, не гарантируют выполнение какой-то оценки короткого замыкания (без использования таких дрянных вещей, как case), система Linq не дает никаких гарантий, что это произойдет (отсюда и документ об этом). Расширение писать не нужно - оно уже есть в ответе. - person ShuggyCoUk; 21.04.2009
comment
@Shuggy. Извините за поздний ответ - проект сняли (вы знаете, как это), но теперь я вернулся к нему. Я только что реализовал ваш метод расширения, который очень элегантен, но все еще не работает. Я получаю следующее исключение: Метод Boolean ContainsOrNull (System.String, System.String, System.StringComparison) не поддерживает перевод в SQL. social.msdn.microsoft. com / Forums / en-US / linqprojectgeneral / Который я сейчас ищу - person Ev.; 22.04.2009
comment
Так что я еще не растерялся! Я проверяю это, пока мы говорим. Я уверен, что нахожусь на правильном пути. Чувак, решение должно быть так близко! Еще раз благодарю за потраченное время / помощь! Спасибо! Я снова буду заниматься этим сегодня и сегодня днем, так что, надеюсь, я что-нибудь придумаю и опубликую. Любая дополнительная помощь была бы замечательной :) - person Ev.; 22.04.2009
comment
Ах хорошо. Я предположил, что вы извлекаете данные, а затем фильтруете их. Это не сработает. Я отредактирую соответственно - person ShuggyCoUk; 22.04.2009
comment
Каким бы ни было ваше решение, оно должно в основном предоставлять ненулевое значение для Contains или избегать всего этого вместе (как это делает мое), извините - person ShuggyCoUk; 22.04.2009
comment
Да, я думаю, ты совершенно прав. Мне это кажется странным, потому что это обычное дело. Ну что ж ... милая :) - person Ev.; 22.04.2009
comment
@Shuggy: я только что разместил это в другом сообщении и подумал, что добавлю сюда. Просто придумал способ прояснить, что мне нужно. Мне нужен эквивалент Linq to SQL: SELECT * FROM tblSomeTable WHERE @theValue IS NULL OR Lower (@theValue) = Lower (theValue) - person Ev.; 28.04.2009
comment
Проблема здесь в том, что на сервере sql Lower (@variable) имеет значение null, когда @variable имеет значение null. В Linq, поскольку переменная доступна в момент, когда она пытается вызвать ToLower (в смысле этого слова .Net) перед отправкой запроса, происходит сбой. Это поведение может измениться в будущем (хотя я думаю, что это маловероятно), но я не знаю, как заставить Linq to SQL откладывать оценку методов для переменных, о которых он знает (в отличие от идентификаторов столбцов). - person ShuggyCoUk; 28.04.2009

Хорошо. Я нашел решение.

Я изменил оскорбительную строку на:

where (String.IsNullOrEmpty(fromemail)  || (msg.FromEmail.ToLower().Contains((fromemail ?? String.Empty).ToLower())))

Работает, но похоже на взлом. Я уверен, что если первое выражение истинно, второе не должно оцениваться.

Было бы здорово, если бы кто-нибудь мог подтвердить или опровергнуть это для меня ...

Или, если у кого-то есть лучшее решение, дайте мне знать !!!

person Ev.    schedule 21.04.2009

Если вы используете LINQ to SQL, вы не можете ожидать такого же поведения короткого замыкания C # в SQL Server. См. этот вопрос о предложениях WHERE короткого замыкания (или их отсутствии) в SQL Server.

Кроме того, как я уже упоминал в комментарии, я не верю, что вы получаете это исключение в LINQ to SQL, потому что:

  1. Метод String.IsNullOrEmpty(String) не поддерживает преобразование в SQL, поэтому его нельзя использовать в LINQ to SQL.
  2. Вы бы не получили исключение NullReferenceException. Это управляемое исключение, оно может произойти только на стороне клиента, а не в SQL Server.

Вы уверены, что это где-то не проходит через LINQ to Objects? Вы вызываете ToList () или ToArray () в своем источнике или ссылаетесь на него как на IEnumerable ‹T› перед выполнением этого запроса?


Обновление: Прочитав ваши комментарии, я снова проверил это и кое-что понял. Я ошибался, говоря, что вы не используете LINQ to SQL. Вы не получали исключение "String.IsNullOrEmpty(String) has no supported translation to SQL", потому что IsNullOrEmpty() вызывается для локальной переменной, а не для столбца SQL, поэтому он работает на стороне клиента, даже если вы используете LINQ to SQL (не LINQ to Objects ). Поскольку он выполняется на стороне клиента, вы можете получить NullReferenceException при вызове этого метода, потому что он не транслируется в SQL, где вы не можете получить NullReferenceException.

Один из способов сделать ваше решение менее хакерским - разрешить fromname "нулевое значение" вне запроса:

string lowerfromname = String.IsNullOrEmpty(fromname) ? fromname : fromname.ToLower();

var messages = from msg in dc.MessageItems
               where String.IsNullOrEmpty(lowerfromname) || msg.Name.ToLower().Contains(lowerfromname)
               select msg.Name;

Обратите внимание, что это не всегда будет переведено на что-то вроде (используя ваши комментарии в качестве примера):

SELECT ... FROM ... WHERE @theValue IS NULL OR @theValue = theValue

Его перевод будет определен во время выполнения в зависимости от того, является ли fromname нулевым или нет. Если он равен нулю, он будет переведен без предложения WHERE. Если он не равен нулю, он будет транслироваться простым "WHERE @theValue = theValue" без проверки на нуль в T-SQL.

Итак, в конце концов, вопрос о том, будет ли это короткое замыкание в SQL или нет, в этом случае не имеет значения, потому что среда выполнения LINQ to SQL будет выдавать разные запросы T-SQL, если fromname имеет значение NULL или нет. В некотором смысле, перед запросом к базе данных происходит короткое замыкание на стороне клиента.

person Lucas    schedule 25.04.2009
comment
Я вызываю ToList (), когда запускается linq, но не раньше. Это нормально, если я не могу ожидать такого же поведения при коротком замыкании. Я доволен этим сейчас. Я нашел решение, но все еще не очень им доволен. Мое решение заключалось в изменении String.IsNullOrEmpty (fromname) || msg.FromName.ToLower (). Содержит (fromname.ToLower ()) to where ((fromname == null) || (msg.FromName.IndexOf (fromname) ›= 0)), поэтому я не предполагаю, что fromname не нулевой. Не думаю, что это лучший способ. Я хочу что-то, что превратилось бы в эквивалент - person Ev.; 28.04.2009
comment
ВЫБРАТЬ * ИЗ tblSomeTable, ГДЕ @TheValue IS NULL ИЛИ @theValue = theValue - person Ev.; 28.04.2009
comment
Так что, если у вас есть идеи, было бы здорово! - person Ev.; 28.04.2009
comment
Спасибо, ваши комментарии заставили меня понять, что я совершил ошибку. я обновил свой ответ. - person Lucas; 28.04.2009

Вы уверены, что это значение «fromname» равно нулю, а не «msg.FromName», которое имеет значение NULL?

person Brian    schedule 21.04.2009
comment
Привет - спасибо за быстрый ответ! Да, fromname имеет значение null. Я тестировал, удалив ToLower (), и я не проблема. - person Ev.; 21.04.2009
comment
Итак, чтобы уточнить - это fromname, которое имеет значение null, а не msg.FromName. Есть другие идеи? - person Ev.; 21.04.2009
comment
Что сказал @ShuggyCoUk. Что такое тип dc.MessageItems? Есть ли у него модный поставщик LINQ? - person Brian; 21.04.2009
comment
Привет, Брайан. Это мой собственный класс. Я использую Linq to SQL, поэтому это объект, который в основном представляет собой таблицу. Члены - это такие вещи, как msg.FromName, msg.Email, msg.Location, msg.message Text - но не сумасшедший провайдер. Итак, dc.MessageItems, по сути, представляет собой набор MessageItem, мне просто нужно знать, почему оценивается вторая часть or operatoin, даже если первое выражение истинно! :) Спасибо за помощь! - person Ev.; 21.04.2009

Как сказал Брайан, я бы посмотрел, имеет ли msg.FromName значение null, прежде чем выполнять ToLower (). Contains (fromname.ToLower ()))

person Nordes    schedule 21.04.2009
comment
Приветствую Nordes. Как я уже сказал, я уверен, что msg.FromName в этом случае не равно нулю. Что вы, ребята, думаете о второй части экзамена на получение оценки? Я прав, что этого не должно быть? - person Ev.; 21.04.2009

Вы правы, что второе условное выражение не должно оцениваться, поскольку вы используете сравнения короткого замыкания (см. Какова наилучшая практика в отношении оценки короткого замыкания C #?), однако я подозреваю, что Linq может попытаться оптимизировать ваш запрос перед его выполнением и при этом может изменить порядок выполнения.

Заключение всего этого в скобки, на мой взгляд, дает более ясное утверждение, поскольку все условие «где» содержится в скобках.

person Lazarus    schedule 21.04.2009
comment
@ Лазарь. Спасибо за комментарий. Кажется вполне возможным, что Linq оптимизирует эти средства сравнения ярлыков и теряет функциональность, на которую я полагаюсь. Есть идеи, как заставить его в Linq? Спасибо за комментарий! - person Ev.; 22.04.2009
comment
Надо было упомянуть, что я пробовал много разных вариантов скобок! - person Ev.; 22.04.2009