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 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 до 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;
Ерік Дж.

37
Він може компілювати, але Linq не буде переведений на SQL сутностями. Метод "Boolean IsNullOrWhiteSpace (System.String)" не підтримує переклад на SQL. Те саме стосується IsNullOrEmpty.
Філ

1
Те саме стосується Linq до SQL
Phil

3
Слово обережності: першорядне значення має використовувати "string.Empty" over "" (він же порожній рядок). Перший працює останній (принаймні, що стосується драйвера EF Oracle). Ака, якщо ви використовуєте: 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>перш ніж продовжувати специфікацію запиту. Це, однак, може спричинити показник продуктивності - особливо, коли його застосовують для обмежень (наприклад, де пункти). Навпаки, при роботі з трансформаціями, показник ефективності набагато менший, іноді навіть не існує - залежно від вашого запиту.

Отже, наведений вище код також може бути переписаний так:

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

ПРИМІТКА: Цей код матиме більший вплив на продуктивність, ніж відповідь Філа . Однак він показує принцип.


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 до sql / сутності) та перетворений у 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 як відповідь. Будь ласка, погляньте на форматування, щоб створити зрозумілий і простий у читанні відповідь.
Хілл
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.