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