String.IsNullOrWhiteSpace na expressão LINQ

151

Eu tenho o seguinte código:

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

E eu recebo esse erro quando tento executar o código:

O LINQ to Entities não reconhece o método 'Boolean IsNullOrWhiteSpace (System.String)' e esse método não pode ser convertido em uma expressão de armazenamento. "

Como posso resolver esse problema e escrever código melhor que isso?

Hossein Moradinia
fonte

Respostas:

263

Você precisa substituir

!string.IsNullOrWhiteSpace(b.Diameter)

com

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

Para o Linq to Entities, isso é traduzido em:

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

e para Linq to SQL quase mas não exatamente o mesmo

DECLARE @p0 NVarChar(1000) = ''
...
WHERE NOT (LTRIM(RTRIM([t0].[TypeName])) = @p0)
Phil
fonte
3
Por quê? Este código compila:List<string> my = new List<string>(); var i = from m in my where !string.IsNullOrWhiteSpace(m) select m;
Eric J.
37
Pode ser compilado, mas não será convertido em SQL pelo Linq para entidades. O método 'Boolean IsNullOrWhiteSpace (System.String)' não tem tradução suportada para SQL. O mesmo se aplica a IsNullOrEmpty.
Phil
1
O mesmo é verdade para o Linq to SQL
Phil Phil
3
Uma palavra de cautela: é de suma importância empregar 'string.Empty' sobre "" (também conhecida como string vazia). O primeiro funciona, o último não (pelo menos no que diz respeito ao driver EF da Oracle). Aka se você usar: b.Diameter.Trim () == "" <- isto não vai funcionar como pretendido (louco eu sei ...)
XDS
Parece que Trim () também não é suportado, pelo menos, para consultas usando MongoDB.Driver
Leandro hereñu 15/04
20

Nesse caso, é importante distinguir entre IQueryable<T>e IEnumerable<T>. Em resumo, IQueryable<T>é processado por um provedor LINQ para fornecer uma consulta otimizada. Durante essa transformação, nem todas as instruções C # são suportadas, pois não é possível convertê-las em uma consulta específica de back-end (por exemplo, SQL) ou porque o implementador não previu a necessidade da instrução.

Por outro lado, IEnumerable<T>é executado contra objetos concretos e, portanto, não será transformado. Portanto, é bastante comum que construções, que são utilizáveis IEnumerable<T>, não possam ser usadas IQueryable<T>e também que IQueryables<T>suportadas por diferentes provedores LINQ não suportem o mesmo conjunto de funções.

No entanto, existem algumas soluções alternativas (como a resposta de Phil ), que modificam a consulta. Além disso, como uma abordagem mais geral, é possível retornar a um IEnumerable<T>antes de continuar com a especificação da consulta. Isso, no entanto, pode ter um impacto no desempenho - especialmente quando usado em restrições (por exemplo, cláusulas where). Por outro lado, ao lidar com transformações, o impacto no desempenho é muito menor, às vezes até inexistente - dependendo da sua consulta.

Portanto, o código acima também pode ser reescrito assim:

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

NOTA: Esse código terá um impacto no desempenho superior à resposta de Phil . No entanto, mostra o princípio.

AxelEckenberger
fonte
10

Use um visitante de expressão para detectar referências a string.IsNullOrWhiteSpace e divida-as em uma expressão mais simples (x == null || x.Trim() == string.Empty).

Então, abaixo, há um visitante estendido e um método de extensão para usá-lo. Não requer nenhuma configuração especial para usar, basta chamar WhereEx em vez de 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"));
    }
}

Portanto, se você executá- myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace())lo, ele será convertido em !(c.Name == null || x.Trim() == "")antes de ser passado para o que for (linq to sql / entity) e convertido em sql.

Sam
fonte
Muito mais complexa do que a resposta de Phil para tal exigência simples, mas muito interessante para fins educacionais sobre ExpressionVisitor, graças
AFract
2

Você também pode usar isso para verificar se há espaço em branco:

b.Diameter!=null && !String.IsNullOrEmpty(b.Diameter.Trim())
Majid
fonte
6
isso lançará uma exceção se o diâmetro for nulo.
Okan Kocyigit
@OkanKocyigit Você está certo. Eu editei a resposta. :)
Majid
0
!String.IsNullOrEmpty(b.Diameter.Trim()) 

lançará exceção se b.Diameterfor null.
Se você ainda deseja usar sua declaração, use melhor esta verificação

!String.IsNullOrWhiteSpace(b.Diameter), IsNullOrWhiteSpace = IsNullOrEmpty + WhiteSpace
Duy Tran
fonte
2
Bem-vindo ao StackOverflow! Antes de tudo, obrigado por participar do SO como respondedor. Dê uma olhada na formatação para criar uma resposta clara e fácil de ler.
Hille