生成可动态转换为有效SQL的表达式树,该树可以将字符串与双精度进行比较

本文关键字:字符串 双精度 比较 转换 动态 有效 SQL 表达式 | 更新日期: 2023-09-27 17:57:28

我在SQL Server中有下表:

产品属性

  • 名称:nvarchar(100)
  • 值:nvarchar(200)

这是通过实体框架映射到我的类中的:

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

真正令人讨厌的是,我需要动态构建这个表达式树

到目前为止,我已经处理好了这个问题(它将值视为十进制,而不是字符串,所以它甚至没有尝试做整个regex的事情):

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?

生成可动态转换为有效SQL的表达式树,该树可以将字符串与双精度进行比较

我认为Yacoub-Massad问SQL应该是什么样子是有道理的。如果没有办法编写执行查询的SQL,那么怎么可能有一个表达式树可以转换为所需的SQL呢?

主要问题是SQL Server本机不支持regex。您可以将CLR函数导入数据库并在UDF中使用,但这不是使其与EF一起工作的最简单方法。

因此,再一次,从对SQL进行成像开始,它将完成这项工作。

现在我发现了这个小宝石,它从字符串中提取数字(左)部分:

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

这将从"3000KB"返回"3000"。

幸运的是,我们可以使用SqlFunctions.PatIndex在LINQ语句中重现这一点:

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

这显然会从您的示例中返回83000

现在困难的部分已经完成,您可以使用这个结果将谓词应用于这个数字部分:

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

您将看到,每次在LINQ语句中使用numPart时,整个PatIndex内容都会在SQL语句中重复(即使您将其封装在子查询中)。不幸的是,SQL就是这样工作的。它不能在语句中存储临时结果。嗯,语言规范已经有40多年的历史了,一点也不差。

不要这样做。原因:与双打相比,我建议你可以说:RAM>4,但4什么?如果您存储2000KB,那么它将是真的,但如果您存储8MB,则不会,这显然是假的。相反:将double的标准化值存储在字段旁边的db中,并与之通信。如果您已经拥有数据,那么最好进行迁移。

我将选择不可能。

如何使用SQL可靠地提取数字?您不能使用regex。你能做的最好的事情是在可能的数字和文本之间搜索某种分隔符,而你的测试数据并不总是这样:"RAM"有一个"8GB"的空间,但"Cache"没有"300kb"的空间。