164

I have the following code:

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

And I get this error when I try to run the code:

LINQ to Entities does not recognize the method 'Boolean IsNullOrWhiteSpace(System.String)' method, and this method cannot be translated into a store expression."

How can I solve this problem and write code better than this?

Hossein Moradinia
  • 6,116
  • 14
  • 59
  • 85

5 Answers5

283

You need to replace

!string.IsNullOrWhiteSpace(b.Diameter)

with

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

For Linq to Entities this gets translated into:

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

and for Linq to SQL almost but not quite the same

DECLARE @p0 NVarChar(1000) = ''
...
WHERE NOT (LTRIM(RTRIM([t0].[TypeName])) = @p0)
Phil
  • 42,255
  • 9
  • 100
  • 100
  • 4
    Why? This code compiles: `List my = new List(); var i = from m in my where !string.IsNullOrWhiteSpace(m) select m;` – Eric J. Mar 07 '12 at 18:24
  • 40
    It may compile, but it won't be translated into SQL by Linq to entities. *Method 'Boolean IsNullOrWhiteSpace(System.String)' has no supported translation to SQL.* The same applies for IsNullOrEmpty. – Phil Mar 07 '12 at 18:27
  • 1
    The same is true for Linq to SQL – Phil Mar 07 '12 at 18:40
  • 3
    A word of caution: It's of paramount importance to employ 'string.Empty' over "" (aka the empty string). The former works the latter doesn't (at least as far as Oracle's EF driver is concerned). Aka if you use: b.Diameter.Trim() == "" <-- this won't work as intended (crazy I know ...) – XDS Dec 14 '18 at 13:59
  • 1
    seems that Trim() is also not supported at least for queries using MongoDB.Driver – HNL Apr 15 '20 at 17:20
21

In this case it is important to distinguish between IQueryable<T> and IEnumerable<T>. In short IQueryable<T> is processed by a LINQ provider to deliver an optimized query. During this transformation not all C# statements are supported, as it either is not possible to translate them to a back-end specific query (e.g. SQL) or because the implementer did not foresee the need for the statement.

In contrast IEnumerable<T> is executed against the concrete objects and, therefore, will not be transformed. So, it is quite common that constructs, which are useable with IEnumerable<T>, cannot be used with IQueryable<T> and also that IQueryables<T> backed by different LINQ providers do not support the same set of functions.

However, there are some workarounds (like Phil's answer), which modify the query. Also, as a more general approach it is possible to drop back to an IEnumerable<T> before continuing with the specification of the query. This, however, might have a performance hit - especially when using it on restrictions (e.g. where clauses). In contrast, when dealing with transformations the performance hit is a lot smaller, sometimes even non existent - depending on your query.

So the above code could also be rewritten like this:

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: Ths code will have an higher performance impact than Phil's answer. However, it shows the principle.

Dan Narsavage
  • 325
  • 3
  • 13
AxelEckenberger
  • 16,628
  • 3
  • 48
  • 70
12

Use an expression visitor to detect references to string.IsNullOrWhiteSpace and break them down into a simpler expression (x == null || x.Trim() == string.Empty).

So below is an extended visitor and an extension method to make use of it. It requires no special config to use, simply call WhereEx instead of 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"));
    }
}

So if you run myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace()) it will be converted to !(c.Name == null || x.Trim() == "") before being passes to whatever (linq to sql/entities) and converted to sql.

Sam
  • 1,725
  • 1
  • 17
  • 28
  • 1
    A lot more complex than Phil's answer for a such simple requirement, but very interesting for educational purpose regarding ExpressionVisitor, thanks – AFract Dec 02 '16 at 10:40
2

You can also use this to check for whitespace:

b.Diameter!=null && !String.IsNullOrEmpty(b.Diameter.Trim())
Majid
  • 13,853
  • 15
  • 77
  • 113
1
!String.IsNullOrEmpty(b.Diameter.Trim()) 

will throw exception if b.Diameter is null.
If you still want to use your statement, better use this check

!String.IsNullOrWhiteSpace(b.Diameter), IsNullOrWhiteSpace = IsNullOrEmpty + WhiteSpace
Hille
  • 2,123
  • 22
  • 39
Duy Tran
  • 11
  • 1
  • 2
    Welcome to StackOverflow! First of all, thank you for participating on SO as an answerer. Please take a look at [formatting](https://stackoverflow.com/editing-help) to create a clear and easy to read answer. – Hille Apr 24 '19 at 14:18