String.IsNullOrWhiteSpace w LINQ Expression

151

Mam następujący kod:

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

I pojawia się ten błąd, gdy próbuję uruchomić kod:

LINQ to Entities nie rozpoznaje metody „Boolean IsNullOrWhiteSpace (System.String)” i nie można jej przetłumaczyć na wyrażenie magazynu. "

Jak mogę rozwiązać ten problem i napisać kod lepiej niż to?

Hossein Moradinia
źródło

Odpowiedzi:

263

Musisz wymienić

!string.IsNullOrWhiteSpace(b.Diameter)

z

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

W przypadku Linq to Entities jest to tłumaczone na:

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

a dla Linq do SQL prawie, ale nie całkiem to samo

DECLARE @p0 NVarChar(1000) = ''
...
WHERE NOT (LTRIM(RTRIM([t0].[TypeName])) = @p0)
Phil
źródło
3
Czemu? Ten kod kompiluje:List<string> my = new List<string>(); var i = from m in my where !string.IsNullOrWhiteSpace(m) select m;
Eric J.
37
Może się kompilować, ale nie zostanie przetłumaczony na SQL przez Linq na encje. Metoda „Boolean IsNullOrWhiteSpace (System.String)” nie ma obsługiwanego tłumaczenia na język SQL. To samo dotyczy IsNullOrEmpty.
Phil
1
To samo dotyczy Linq do SQL
Phil
3
Uwaga: niezwykle ważne jest, aby zastosować ciąg 'string.Empty' nad "" (inaczej pusty ciąg). Pierwsza działa, druga nie (przynajmniej jeśli chodzi o sterownik EF Oracle). Aka, jeśli użyjesz: b.Diameter.Trim () == "" <- to nie zadziała zgodnie z przeznaczeniem (szalone, wiem ...)
XDS
wygląda na to, że Trim () nie jest również obsługiwany, przynajmniej w przypadku zapytań używających MongoDB.Driver
Leandro hereñu
20

W takim przypadku ważne jest, aby rozróżnić IQueryable<T>i IEnumerable<T>. W skrócie IQueryable<T>jest przetwarzany przez dostawcę LINQ w celu dostarczenia zoptymalizowanego zapytania. Podczas tej transformacji nie wszystkie instrukcje C # są obsługiwane, ponieważ nie jest możliwe przetłumaczenie ich na zapytanie specyficzne dla zaplecza (np. SQL) lub ponieważ implementujący nie przewidział potrzeby tej instrukcji.

W przeciwieństwie IEnumerable<T>jest wykonywany w stosunku do konkretnych obiektów i dlatego nie zostanie przekształcony. Tak więc jest dość powszechne, że konstrukcje, które są używane z IEnumerable<T>, nie mogą być używane z, IQueryable<T>a także IQueryables<T>te obsługiwane przez różnych dostawców LINQ nie obsługują tego samego zestawu funkcji.

Istnieją jednak pewne obejścia (takie jak odpowiedź Phila ), które modyfikują zapytanie. Ponadto, jako bardziej ogólne podejście, można cofnąć się do IEnumerable<T>przed kontynuowaniem specyfikacji zapytania. Może to jednak mieć negatywny wpływ na wydajność - szczególnie podczas używania go na ograniczeniach (np. Klauzule gdzie). W przeciwieństwie do tego, gdy zajmujemy się transformacjami, wydajność jest dużo mniejsza, a czasami nawet nie istnieje - w zależności od zapytania.

Więc powyższy kod można również przepisać w ten sposób:

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

UWAGA: ten kod będzie miał większy wpływ na wydajność niż odpowiedź Phila . Jednak pokazuje zasadę.

AxelEckenberger
źródło
10

Użyj gościa wyrażenia, aby wykryć odwołania do string.IsNullOrWhiteSpace i rozbić je na prostsze wyrażenie (x == null || x.Trim() == string.Empty).

Poniżej znajduje się rozszerzony użytkownik i metoda rozszerzenia, aby z niego skorzystać. Nie wymaga specjalnej konfiguracji, po prostu wywołaj WhereEx zamiast 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"));
    }
}

Więc jeśli uruchomisz myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace()), zostanie przekonwertowany na !(c.Name == null || x.Trim() == "")przed przekazaniem do cokolwiek (linq do sql / entity) i przekonwertowany na sql.

Sam
źródło
O wiele bardziej złożona niż odpowiedź Phila na tak prosty wymóg, ale bardzo interesująca ze
względów
2

Możesz również użyć tego, aby sprawdzić, czy nie ma białych znaków:

b.Diameter!=null && !String.IsNullOrEmpty(b.Diameter.Trim())
Majid
źródło
6
spowoduje to zgłoszenie wyjątku, jeśli średnica jest równa null.
Okan Kocyigit
@OkanKocyigit Masz rację. Poprawiłem odpowiedź. :)
Majid
0
!String.IsNullOrEmpty(b.Diameter.Trim()) 

zgłosi wyjątek, jeśli b.Diameterjest null.
Jeśli nadal chcesz użyć swojego wyciągu, lepiej użyj tego czeku

!String.IsNullOrWhiteSpace(b.Diameter), IsNullOrWhiteSpace = IsNullOrEmpty + WhiteSpace
Duy Tran
źródło
2
Witamy w StackOverflow! Przede wszystkim dziękuję za udział w SO jako osoba odpowiadająca. Przyjrzyj się formatowaniu, aby uzyskać jasną i łatwą do odczytania odpowiedź.
Hille