生成可动态转换为有效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?
我认为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)
这显然会从您的示例中返回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
您将看到,每次在LINQ语句中使用numPart
时,整个PatIndex
内容都会在SQL语句中重复(即使您将其封装在子查询中)。不幸的是,SQL就是这样工作的。它不能在语句中存储临时结果。嗯,语言规范已经有40多年的历史了,一点也不差。
不要这样做。原因:与双打相比,我建议你可以说:RAM>4,但4什么?如果您存储2000KB,那么它将是真的,但如果您存储8MB,则不会,这显然是假的。相反:将double的标准化值存储在字段旁边的db中,并与之通信。如果您已经拥有数据,那么最好进行迁移。
我将选择不可能。
如何使用SQL可靠地提取数字?您不能使用regex。你能做的最好的事情是在可能的数字和文本之间搜索某种分隔符,而你的测试数据并不总是这样:"RAM"有一个"8GB"的空间,但"Cache"没有"300kb"的空间。