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

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

12 голосов
/ 21 апреля 2009

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

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

, например

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 равно нулю) !! - Я получаю классическое исключение «Ссылка на объект не установлена ​​для экземпляра объекта».

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

Ответы [ 6 ]

14 голосов
/ 21 апреля 2009

Прочитайте этой документации , которая объясняет, как 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, даже если оно равно null, что вызывает исключение).

в этом случае вы можете сделать то, что вы уже обнаружили: оставить запрос как есть, но убедитесь, что вы всегда можете создать ненулевое значение 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;    
}

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

5 голосов
/ 21 апреля 2009

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

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

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

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

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

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

4 голосов
/ 25 апреля 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 перед выполнением этого запроса?


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

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

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 нулевым или нет. Если оно равно null, оно будет переведено без предложения WHERE. Если оно не равно нулю, оно будет переводиться с простым «WHERE @theValue = theValue», без проверки нуля в T-SQL.

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

3 голосов
/ 21 апреля 2009

Вы уверены, что это 'fromname', которое является нулевым, а не 'msg.FromName', которое является нулевым?

0 голосов
/ 21 апреля 2009

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

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

0 голосов
/ 21 апреля 2009

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

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