String.IsNullOrWhiteSpace в выражении LINQ

У меня есть следующий код:

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

И я получаю эту ошибку, когда пытаюсь запустить код:

LINQ to Entities не распознает метод 'Boolean IsNullOrWhiteSpace (System.String) ', и этот метод не может быть переведенный в выражение хранилища. "

Как я могу решить эту проблему и написать код лучше этого?

Ответ 1

Вам нужно заменить

!string.IsNullOrWhiteSpace(b.Diameter)

с

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

Для Linq to Entities это преобразуется в:

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

и для Linq to SQL почти, но не совсем то же самое

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

Ответ 2

В этом случае важно различать IQueryable<T> и IEnumerable<T>. Короче говоря, IQueryable<T> ist обрабатывается поставщиком LINQ для доставки оптимизированного запроса. Во время этого преобразования поддерживаются не все операторы С#, так как либо невозможно перевести их на конкретный запрос (например, SQL), либо потому, что разработчик не видел необходимости в заявлении.

В контракте IEnumerable<T> выполняется против конкретных объектов и, следовательно, не будет преобразовано. Таким образом, довольно часто конструкции, которые используются с IEnumerable<T>, не могут использоваться с IQueryable<T>, а также что IQueryables<T>, поддерживаемый различными поставщиками LINQ, не поддерживает один и тот же набор функций.

Однако есть некоторые обходные пути (например, Ответ Фила), которые изменяют запрос. Кроме того, в качестве более общего подхода можно вернуться к IEnumerable<T>, прежде чем продолжить спецификацию запроса. Однако это может привести к поражению производительностью - особенно при использовании его на ограничениях (например, где клаузулы). Напротив, при работе с преобразованиями производительность становится намного меньше, иногда даже не существует - в зависимости от вашего запроса.

Таким образом, приведенный выше код также можно переписать следующим образом:

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

ПРИМЕЧАНИЕ. Код Ths будет иметь более высокое влияние производительности, чем ответ Фила. Однако он показывает принцип.

Ответ 3

Используйте посетитель выражения для определения ссылок на string.IsNullOrWhiteSpace и разбивайте их на более простое выражение (x == null || x.Trim() == string.Empty).

Итак, ниже приведен расширенный посетитель и метод расширения, чтобы использовать его. Он не требует специальной конфигурации для использования, просто вызовите WhereEx вместо 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"));
    }
}

Итак, если вы запустите myqueryable.WhereEx(c=> !c.Name.IsNullOrWhiteSpace()), он будет преобразован в !(c.Name == null || x.Trim() == "") перед тем, как перейти к любому (linq to sql/entity) и преобразован в sql.

Ответ 4

Вы также можете использовать это для проверки пробелов:

!(String.IsNullOrEmpty(b.Diameter.Trim());