2016-07-18 3 views
6

У меня есть следующая таблица в SQL Server:Сложение Expression Tree конвертированы в действительный SQL динамически, который может сравнить строку с двойниками

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

EDIT: с помощью представленной ниже, это работает:

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 из моего выражения?

+0

Каким будет SQL, что вы хотите сгенерировать? –

+0

Честно говоря, я не знаю, но что-то подобное этому. –

+0

Если данные заданы, вы можете столкнуться с проблемой производительности, если этот запрос повторяется снова и снова, возможно, INDEX не может использоваться. Поэтому лучший способ состоит в том, чтобы иметь отдельный числовой столбец с числовым значением, вы можете обновить этот столбец во время вставки/обновления. – sallushan

ответ

2

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

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

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

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

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 лет, неплохая.

+0

Я немного отредактировал этот вопрос. Ваш ответ почти срабатывает, но мне также нужна строка литья -> двойное + сравнение. Вы знаете, как я могу это достичь? –

+0

Извините, это не поддерживается в LINQ to Entities. Преобразование типов - один большой недостаток в EF. –

0

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

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

1

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