Построить дерево выражений, преобразуемое в действительный SQL, которое может сравнивать строку с двойными

c# sql-server entity-framework expression-trees

374 просмотра

3 ответа

2546 Репутация автора

У меня есть следующая таблица в SQL Server:

ProductAttribute

  • Имя: nvarchar(100)
  • Стоимость: nvarchar(200)

Это отображается через Entity Framework в мой класс:

public class ProductAttribute
{
   public string Name {get;set;}
   public string Value {get;set;}
}

Некоторые строки ProductAttributesимеют следующую форму:

  • {Name: "RAM", Value: "8 GB"}, {Name: "Cache", Value: "3000KB"}

Мне нужно динамически построить ExpressionTree, который может быть преобразован в SQL и может выполнять следующие действия:

  • Если значение начинается с цифры, за которой следует буквенно-цифровая строка или нет, извлеките число и сравните его с заданным значением

    double value = ...;
    
    Expression<Func<ProductAttribute, bool>> expression = p => 
    {
                Regex regex = new Regex(@"\d+");
                Match match = regex.Match(value);
    
                if (match.Success && match.Index == 0)
                {
                    matchExpression = value.Contains(_parserConfig.TokenSeparator) ? 
                        value.Substring(0, value.IndexOf(_parserConfig.TokenSeparator)) : 
                        value;
    
                    string comparand = match.Value;
                    if(double.Parse(comparand)>value) 
                        return true;
                }
    
                return false;
    }
    

Действительно неприятно то, что мне нужно построить это дерево выражений динамически .

До сих пор я справился с этим (это рассматривает значение как десятичное число, а не как строку, поэтому он даже не пытается выполнить весь материал регулярных выражений):

private Expression GenerateAnyNumericPredicate(
        Type type, 
        string valueProperty,
        string keyValue, 
        double value) {
        ParameterExpression param = Expression.Parameter(type, "s"); 
        MemberExpression source = Expression.Property(param, valueProperty);
        ConstantExpression targetValue = GetConstantExpression(value, value.GetType());
        BinaryExpression comparisonExpression = Expression.GreaterThan(source, targetValue);  

        return Expression.Lambda(comparisonExpression, param);
    }  

РЕДАКТИРОВАТЬ : С помощью, представленной ниже, это работает:

  Expression<Func<ProductSpecification, bool>> expo = ps=> ps.Value.Substring(0, (SqlFunctions.PatIndex("%[^0-9]%", ps.Value + ".") ?? 0) - 1) == "1000";

Но мне также нужно удвоить приведение, а затем числовое сравнение:

  Expression<Func<ProductSpecification, bool>> expo = ps=> double.Parse(ps.Value.Substring(0, (SqlFunctions.PatIndex("%[^0-9]%", ps.Value + ".") ?? 0) - 1)) > 1000;

Очевидно , что это не конвертируются в SQL: double.Parse().

Как я могу построить приведение, чтобы оно могло быть проанализировано в SQL из моего выражения?

Автор: Tamas Ionut Источник Размещён: 18.07.2016 09:26

Ответы (3)


0 плюса

10101 Репутация автора

Я собираюсь пойти с невозможным.

Как бы вы надежно извлекли число с помощью SQL? Вы не можете использовать регулярные выражения. Лучшее, что вы можете сделать, - это найти какой-нибудь разделитель между возможным числом и текстом, которого у ваших тестовых данных не всегда есть: «RAM» имеет пробел в «8 ГБ», а «Cache» - в «300 КБ». ».

Автор: Shlomo Размещён: 18.07.2016 10:06

2 плюса

83702 Репутация автора

Решение

Я думаю, что у Якуба Массада есть смысл, спрашивая, как должен выглядеть SQL. Если нет способа написать SQL, который выполняет ваш запрос, как может существовать дерево выражений, которое преобразуется в требуемый SQL?

Основная проблема заключается в том, что регулярные выражения не поддерживаются SQL Server. Вы можете импортировать функцию CLR в свою базу данных и использовать ее в UDF, но это не самый простой способ заставить ее работать с EF.

Итак, опять же, начните с изображения SQL, который будет делать эту работу.

Теперь я нашел этот маленький драгоценный камень, который извлекает числовую (левую) часть из строки:

select left(@str, patindex('%[^0-9]%', @str+'.') - 1)

Это вернуло бы «3000» из «3000KB».

К счастью, мы можем использовать это SqlFunctions.PatIndexдля воспроизведения в выражении LINQ:

from pa in context.ProductAttributes
select pa.Value.Substring(0, (SqlFunctions.PatIndex("%[^0-9]%", pa.Value + ".") ?? 0) - 1)

Который, очевидно, вернется 8и 3000из ваших примеров.

Теперь сложная часть готова, вы можете использовать этот результат, чтобы применить предикат к этой числовой части:

from pa in context.ProductAttributes
let numPart = pa.Value.Substring(0, (SqlFunctions.PatIndex("%[^0-9]%", pa.Value + ".") ?? 0) - 1)
where numPart .... // go ahead

Вы увидите, что каждый раз, когда вы используете numPartв операторе LINQ, весь этот PatIndexматериал повторяется в операторе SQL (даже если вы поместите его в подзапрос). К сожалению, так работает SQL. Он не может хранить временный результат в выписке. Ну, языковая спецификация старше 40 лет, совсем неплохо.

Автор: Gert Arnold Размещён: 18.07.2016 10:57

1 плюс

940 Репутация автора

Не делай это. Причина: по сравнению с двойниками, можно предположить, что вы можете сказать: RAM> 4, но 4 что? если вы храните 2000 КБ, это будет верно, но если вы сохраните 8 МБ, это не так, что, очевидно, неверно. Вместо этого: сохраните нормализованное значение для двойного числа в БД рядом с вашим полем и сравните с этим. Если у вас уже есть данные, то лучше мигрировать.

Автор: MBoros Размещён: 19.07.2016 05:42
Вопросы из категории :
32x32