String.IsNullOrWhiteSpace в выражении LINQ

151

У меня есть следующий код:

return this.ObjectContext.BranchCostDetails.Where(
    b => b.TarrifId == tariffId && b.Diameter == diameter
        || (b.TarrifId==tariffId && !string.IsNullOrWhiteSpace(b.Diameter))
        || (!b.TarrifId.HasValue) && b.Diameter==diameter);

И я получаю эту ошибку при попытке запустить код:

LINQ to Entities не распознает метод метода Boolean IsNullOrWhiteSpace (System.String), и этот метод нельзя преобразовать в выражение хранилища. "

Как я могу решить эту проблему и написать код лучше, чем этот?

Хоссейн Морадиния
источник

Ответы:

263

Вам нужно заменить

!string.IsNullOrWhiteSpace(b.Diameter)

с участием

!(b.Diameter == null || b.Diameter.Trim() == string.Empty)

Для Linq to Entities это переводится на:

DECLARE @p0 VarChar(1000) = ''
...
WHERE NOT (([t0].[Diameter] IS NULL) OR (LTRIM(RTRIM([t0].[Diameter])) = @p0))

и для Linq to SQL почти, но не совсем то же самое

DECLARE @p0 NVarChar(1000) = ''
...
WHERE NOT (LTRIM(RTRIM([t0].[TypeName])) = @p0)
Фил
источник
3
Зачем? Этот код компилируется:List<string> my = new List<string>(); var i = from m in my where !string.IsNullOrWhiteSpace(m) select m;
Эрик Дж.
38
Он может скомпилироваться, но не будет преобразован в SQL с помощью Linq для сущностей. Метод 'Boolean IsNullOrWhiteSpace (System.String)' не поддерживает перевод на SQL. То же самое относится и к IsNullOrEmpty.
Фил
1
То же самое верно для Linq to SQL
Фил
3
Предостережение: крайне важно использовать «string.Empty» над «» (он же пустая строка). Первое работает, второе - нет (по крайней мере, в том, что касается драйвера Oracle EF). Ака, если вы используете: b.Diameter.Trim () == "" <- это не будет работать так, как задумано (без ума, я знаю ...)
XDS
Кажется, что Trim () также не поддерживается по крайней мере для запросов, использующих MongoDB.Driver
Leandro hereñu
20

В этом случае важно различать IQueryable<T>и IEnumerable<T>. Короче говоря IQueryable<T>, обрабатывается поставщиком LINQ для доставки оптимизированного запроса. Во время этого преобразования поддерживаются не все операторы C #, так как либо невозможно перевести их в специфичный для внутреннего интерфейса запрос (например, SQL), либо потому, что разработчик не предвидел необходимость в операторе.

В отличие от IEnumerable<T>выполненного против конкретных объектов и, следовательно, не будет трансформироваться. Таким образом, довольно распространено, что конструкции, которые можно использовать с IEnumerable<T>, не могут быть использованы, IQueryable<T>а также те, которые IQueryables<T>поддерживаются различными поставщиками LINQ, не поддерживают один и тот же набор функций.

Однако есть некоторые обходные пути (например , ответ Фила ), которые изменяют запрос. Кроме того, в качестве более общего подхода можно вернуться к описанию, IEnumerable<T>прежде чем продолжить со спецификацией запроса. Это, однако, может привести к снижению производительности - особенно при использовании его в ограничениях (например, в предложениях where). Напротив, при работе с преобразованиями снижение производительности намного меньше, а иногда и вовсе отсутствует - в зависимости от вашего запроса.

Таким образом, приведенный выше код также можно переписать так:

return this.ObjectContext.BranchCostDetails
    .AsEnumerable()
    .Where(
        b => b.TarrifId == tariffId && b.Diameter == diameter
        || (b.TarrifId==tariffId && !string.IsNullOrWhiteSpace(b.Diameter))
        ||(!b.TarrifId.HasValue) && b.Diameter==diameter
    );

ПРИМЕЧАНИЕ. Этот код окажет более сильное влияние на производительность, чем ответ Фила . Тем не менее, это показывает принцип.

AxelEckenberger
источник
10

Используйте посетитель выражения, чтобы обнаружить ссылки на string.IsNullOrWhiteSpace и разбить их на более простое выражение (x == null || x.Trim() == string.Empty).

Ниже приведен расширенный посетитель и метод расширения для его использования. Для этого не требуется никакой специальной конфигурации, просто вызовите WhereEx вместо Where.

public class QueryVisitor: ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.IsStatic && node.Method.Name == "IsNullOrWhiteSpace" && node.Method.DeclaringType.IsAssignableFrom(typeof(string)))
        {
            //!(b.Diameter == null || b.Diameter.Trim() == string.Empty)
            var arg = node.Arguments[0];
            var argTrim = Expression.Call(arg, typeof (string).GetMethod("Trim", Type.EmptyTypes));

            var exp = Expression.MakeBinary(ExpressionType.Or,
                    Expression.MakeBinary(ExpressionType.Equal, arg, Expression.Constant(null, arg.Type)),
                    Expression.MakeBinary(ExpressionType.Equal, argTrim, Expression.Constant(string.Empty, arg.Type))
                );

            return exp;
        }

        return base.VisitMethodCall(node);
    }
}

public static class EfQueryableExtensions
{
    public static IQueryable<T> WhereEx<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> where)
    {
        var visitor = new QueryVisitor();
        return queryable.Where(visitor.VisitAndConvert(where, "WhereEx"));
    }
}

Так что, если вы запустите myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace())его, он будет преобразован до !(c.Name == null || x.Trim() == "")того, как его передадут во что угодно (linq to sql / entity) и преобразует в sql.

Сэм
источник
Намного более сложный, чем ответ Фила на такое простое требование, но очень интересный для образовательных целей относительно ExpressionVisitor, спасибо
AFract
2

Вы также можете использовать это для проверки пробелов:

b.Diameter!=null && !String.IsNullOrEmpty(b.Diameter.Trim())
Маджид
источник
6
это вызовет исключение, если диаметр равен нулю.
Окан Коцигит
@OkanKocyigit Вы правы. Я отредактировал ответ. :)
Маджид
0
!String.IsNullOrEmpty(b.Diameter.Trim()) 

скинет исключение, если b.Diameterесть null.
Если вы все еще хотите использовать свое заявление, лучше используйте эту проверку

!String.IsNullOrWhiteSpace(b.Diameter), IsNullOrWhiteSpace = IsNullOrEmpty + WhiteSpace
Дуй Тран
источник
2
Добро пожаловать в StackOverflow! Прежде всего, спасибо за участие в SO в качестве ответчика. Пожалуйста, посмотрите на форматирование, чтобы создать четкий и легкий для чтения ответ.
Хилл