String.IsNullOrWhiteSpace dans l'expression LINQ

151

J'ai le code suivant:

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

Et j'obtiens cette erreur lorsque j'essaye d'exécuter le code:

LINQ to Entities ne reconnaît pas la méthode 'Boolean IsNullOrWhiteSpace (System.String)', et cette méthode ne peut pas être traduite en une expression de magasin. "

Comment puis-je résoudre ce problème et écrire du code mieux que cela?

Hossein Moradinia
la source

Réponses:

263

Vous devez remplacer

!string.IsNullOrWhiteSpace(b.Diameter)

avec

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

Pour Linq to Entities, cela se traduit par:

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

et pour Linq to SQL presque mais pas tout à fait pareil

DECLARE @p0 NVarChar(1000) = ''
...
WHERE NOT (LTRIM(RTRIM([t0].[TypeName])) = @p0)
Phil
la source
3
Pourquoi? Ce code compile:List<string> my = new List<string>(); var i = from m in my where !string.IsNullOrWhiteSpace(m) select m;
Eric J.
37
Il peut compiler, mais il ne sera pas traduit en SQL par Linq en entités. La méthode 'Boolean IsNullOrWhiteSpace (System.String)' n'a pas de traduction prise en charge en SQL. Il en va de même pour IsNullOrEmpty.
Phil
1
La même chose est vraie pour Linq to SQL
Phil
3
Un mot d'avertissement: il est d'une importance primordiale d'employer 'string.Empty' sur "" (c'est-à-dire la chaîne vide). Le premier fonctionne, le second ne fonctionne pas (du moins en ce qui concerne le pilote EF d'Oracle). Aka si vous utilisez: b.Diameter.Trim () == "" <- cela ne fonctionnera pas comme prévu (fou je sais ...)
XDS
semble que Trim () n'est pas non plus pris en charge au moins pour les requêtes utilisant MongoDB.Driver
Leandro hereñu
20

Dans ce cas, il est important de faire la distinction entre IQueryable<T>et IEnumerable<T>. En bref, il IQueryable<T>est traité par un fournisseur LINQ pour fournir une requête optimisée. Lors de cette transformation, toutes les instructions C # ne sont pas prises en charge, car il n'est pas possible de les traduire en une requête spécifique au back-end (par exemple SQL) ou parce que l'implémenteur n'a pas prévu la nécessité de l'instruction.

En revanche IEnumerable<T>est exécuté contre les objets concrets et, par conséquent, ne sera pas transformé. Ainsi, il est assez courant que les constructions, qui sont utilisables avec IEnumerable<T>, ne puissent pas être utilisées avec IQueryable<T>et que celles IQueryables<T>soutenues par différents fournisseurs LINQ ne prennent pas en charge le même ensemble de fonctions.

Cependant, il existe quelques solutions de contournement (comme la réponse de Phil ), qui modifient la requête. De plus, comme approche plus générale, il est possible de revenir à un IEnumerable<T>avant de continuer avec la spécification de la requête. Ceci, cependant, peut avoir un impact sur les performances - en particulier lors de son utilisation sur des restrictions (par exemple, clauses where). En revanche, lorsqu'il s'agit de transformations, l'impact sur les performances est beaucoup plus faible, parfois même inexistant, en fonction de votre requête.

Ainsi, le code ci-dessus pourrait également être réécrit comme ceci:

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

REMARQUE: Ce code aura un impact sur les performances supérieur à la réponse de Phil . Cependant, cela montre le principe.

AxelEckenberger
la source
10

Utilisez un visiteur d'expression pour détecter les références à string.IsNullOrWhiteSpace et les décomposer en une expression plus simple (x == null || x.Trim() == string.Empty).

Vous trouverez donc ci-dessous un visiteur étendu et une méthode d'extension pour l'utiliser. Il ne nécessite aucune configuration spéciale à utiliser, appelez simplement WhereEx au lieu 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"));
    }
}

Donc, si vous l'exécutez, myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace())il sera converti en !(c.Name == null || x.Trim() == "")avant d'être transmis à quoi que ce soit (linq en sql / entités) et converti en sql.

Sam
la source
Beaucoup plus complexe que la réponse de Phil pour une exigence aussi simple, mais très intéressant à des fins éducatives concernant ExpressionVisitor, merci
AFract
2

Vous pouvez également l'utiliser pour vérifier les espaces:

b.Diameter!=null && !String.IsNullOrEmpty(b.Diameter.Trim())
Majid
la source
6
cela lèvera une exception si le diamètre est nul.
Okan Kocyigit
@OkanKocyigit Vous avez raison. J'ai édité la réponse. :)
Majid
0
!String.IsNullOrEmpty(b.Diameter.Trim()) 

lèvera une exception si b.Diameterc'est null.
Si vous souhaitez toujours utiliser votre relevé, mieux vaut utiliser cette vérification

!String.IsNullOrWhiteSpace(b.Diameter), IsNullOrWhiteSpace = IsNullOrEmpty + WhiteSpace
Duy Tran
la source
2
Bienvenue dans StackOverflow! Tout d'abord, merci d'avoir participé à SO en tant que répondeur. Veuillez jeter un œil à la mise en forme pour créer une réponse claire et facile à lire.
Hille