|| (или) Оператор в 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)!! - Я получаю классическое исключение "Ссылка на объект, не установленное на экземпляр объекта".
Любая помощь?
Ответы
Ответ 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" содержится в парафазах.