web-dev-qa-db-fra.com

String.IsNullOrWhiteSpace dans l'expression LINQ

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 je reçois cette erreur lorsque j'essaie 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?

136

Vous devez remplacer

!string.IsNullOrWhiteSpace(b.Diameter)

avec

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

Pour Linq to Entities, cela se traduit en:

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 la même chose

DECLARE @p0 NVarChar(1000) = ''
...
WHERE NOT (LTRIM(RTRIM([t0].[TypeName])) = @p0)
245
Phil

Dans ce cas, il est important de faire la distinction entre IQueryable<T> et IEnumerable<T>. En bref IQueryable<T> est traité par un fournisseur LINQ pour fournir une requête optimisée. Au cours 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 du back-end (SQL, par exemple) ou parce que l’implémenteur n’a pas prévu la nécessité de l’instruction.

En revanche IEnumerable<T> est exécuté sur les objets concrets et ne sera donc pas transformé. Donc, il est courant que des constructions, utilisables avec IEnumerable<T>, ne peut pas être utilisé avec IQueryable<T> et aussi que IQueryables<T> supportés par différents fournisseurs LINQ ne prennent pas en charge le même ensemble de fonctions.

Cependant, il existe des solutions de contournement (comme réponse de Phil ), qui modifient la requête. En outre, en tant qu’approche plus générale, il est possible de revenir à un IEnumerable<T> avant de continuer avec la spécification de la requête. Toutefois, cela pourrait avoir un impact négatif sur les performances - en particulier lorsqu’il est utilisé sur des restrictions (par exemple, des clauses where). En revanche, lorsqu’il s’agit de transformations, la performance est beaucoup moins importante, parfois même inexistante, en fonction de votre requête.

Donc, le code ci-dessus pourrait aussi ê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
    );

NOTE: Ce code aura un impact plus important sur la performance que réponse de Phil . Cependant, cela montre le principe.

18
AxelEckenberger

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 ci-dessous un visiteur étendu et une méthode d'extension pour l'utiliser. Il ne nécessite aucune configuration particulière, il suffit d'appeler 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 exécutez myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace()), il sera converti en !(c.Name == null || x.Trim() == "") avant d'être transmis à tout ce qui est (linq en sql/entités) et converti en sql.

9
Sam

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

!String.IsNullOrEmpty(b.Diameter.Trim())
3
Majid
!String.IsNullOrEmpty(b.Diameter.Trim()) 

va lancer une exception si b.Diameter est null.
Si vous souhaitez toujours utiliser votre déclaration, utilisez plutôt cette vérification.

!String.IsNullOrWhiteSpace(b.Diameter), IsNullOrWhiteSpace = IsNullOrEmpty + WhiteSpace
0
Duy Tran