Подтвердить что ты не робот

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

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

Я понимаю, что при выполнении || операция - это С#, если первое выражение истинно, второе выражение не должно быть оценено.

например.

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

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

4b9b3361

Ответ 1

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

Поскольку выражения 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 на оба значения не равны нулю.

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

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, что вызывает исключение).

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

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

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;    
}

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

Ответ 2

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

Я изменил строку нарушения:

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

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

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

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

Ответ 3

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

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

  • Метод String.IsNullOrEmpty(String) не поддерживает перевод на SQL, поэтому вы не можете использовать его в LINQ to SQL.
  • Вы не получите исключение 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 "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 нулевым или нет. Если он равен нулю, он будет переведен без предложения WHERE. Если он не является нулевым, он будет транслироваться с помощью простого "WHERE @theValue = theValue", без нулевой проверки в T-SQL.

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

Ответ 4

Вы уверены, что это 'fromname', что null, а не 'msg.FromName', что null?

Ответ 5

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

Ответ 6

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

Обертывание всего объекта в скобках также для меня делает более ясным утверждение, поскольку условие "where" содержится в парафазах.