我正在为我们这个庞大的项目创建一个更精细的过滤系统。主要谓词之一是能够通过字符串参数传递比较。这用以下形式表示:“>50”或“5-10”或“<123.2”
我有什么(举个例子来说明)
View 模型:
TotalCost (string) (value: "<50")
Required (string) (value: "5-10")
EF 模型:
TotalCost (double)
Required(double)
我想使用的表达方式:
model => model.Where(field => field.TotalCost.Compare(viewModel.TotalCost) && field.Required.Compare(viewModel.Required));
我希望收到的表达:
model => model.Where(field => field.TotalCost < 50 && field.Required > 5 && field.Required < 10);
或者类似的东西
但是...我不知道从哪里开始。我已将范围缩小到
public static Expression Compare<T>(this Expression<Func<T, bool>> value, string compare)
它甚至可能不正确,但这就是我的全部。比较生成器不是问题,这很容易。困难的部分实际上是返回表达式。我从未尝试过将表达式作为函数值返回。所以基本上我需要保留的是字段并返回比较表达式。
有什么帮助吗? :x
更新:
唉,这不能解决我的问题。可能是因为我过去23小时一直在 sleep ,但是我对如何将其变成扩展方法一无所知。正如我所说,我想要的...基本上是一种写作方式:
var ex = new ExTest();
var items = ex.Repo.Items.Where(x => x.Cost.Compare("<50"));
我构建该函数的方式(可能完全错误)是
public static Expression<Func<decimal, bool>> Compare(string arg)
{
if (arg.Contains("<"))
return d => d < int.Parse(arg);
return d => d > int.Parse(arg);
}
它缺少“this -something- value”来首先进行比较,我还没有设法弄清楚如何让它能够获得表达式输入...至于 ReSharper,它建议我将其转换为 bool 值...
此刻我的脑子里满是绒毛......
更新 2:
我设法找到了一种方法,可以让一段代码在控制台应用程序的内存存储库中运行。不过,我还没有尝试使用 Entity Framework。
public static bool Compare(this double val, string arg)
{
var arg2 = arg.Replace("<", "").Replace(">", "");
if (arg.Contains("<"))
return val < double.Parse(arg2);
return val > double.Parse(arg2);
}
但是,我非常怀疑这就是我所追求的
更新 3:
是的,在坐下来再次查看 lambda 表达式之后,在最后一个答案之前,我想出了类似于以下的东西,它没有满足“Compare()”的确切要求,但它是一个“过载” -ish' 方法:
public static IQueryable<T> WhereExpression<T>(this IQueryable<T> queryable, Expression<Func<T, double>> predicate, string arg)
{
var lambda =
Expression.Lambda<Func<T, bool>>(Expression.LessThan(predicate.Body, Expression.Constant(double.Parse(50.ToString()))));
return queryable.Where(lambda);
}
然而,尽管在我看来,一切似乎都是合乎逻辑的,但我得到了以下运行时异常:
System.ArgumentException was unhandled
Message=Incorrect number of parameters supplied for lambda declaration
Source=System.Core
StackTrace:
at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters)
at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable`1 parameters)
at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, Boolean tailCall, IEnumerable`1 parameters)
at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, ParameterExpression[] parameters)
这显然是罪魁祸首:
var lambda =
Expression.Lambda<Func<T, bool>>(Expression.LessThan(predicate.Body, Expression.Constant(double.Parse(50.ToString()))));
我非常接近解决方案。如果我能摆脱这个错误,我相信 EF 应该能够将其转换为 SQL。否则……好吧,最后的回复可能会消失。
最佳答案
要生成将被翻译成 SQL (eSQL) 的表达式,您应该手动生成 Expression
。以下是创建GreaterThan 过滤器的示例,其他过滤器可以使用类似技术制作。
static Expression<Func<T, bool>> CreateGreaterThanExpression<T>(Expression<Func<T, decimal>> fieldExtractor, decimal value)
{
var xPar = Expression.Parameter(typeof(T), "x");
var x = new ParameterRebinder(xPar);
var getter = (MemberExpression)x.Visit(fieldExtractor.Body);
var resultBody = Expression.GreaterThan(getter, Expression.Constant(value, typeof(decimal)));
return Expression.Lambda<Func<T, bool>>(resultBody, xPar);
}
private sealed class ParameterRebinder : ExpressionVisitor
{
private readonly ParameterExpression _parameter;
public ParameterRebinder(ParameterExpression parameter)
{ this._parameter = parameter; }
protected override Expression VisitParameter(ParameterExpression p)
{ return base.VisitParameter(this._parameter); }
}
这里是使用的例子。 (假设,我们有 StackEntites
EF 上下文和 TestEntity
实体的实体集 TestEnitities)
static void Main(string[] args)
{
using (var ents = new StackEntities())
{
var filter = CreateGreaterThanExpression<TestEnitity>(x => x.SortProperty, 3);
var items = ents.TestEnitities.Where(filter).ToArray();
}
}
更新:
为了创建复杂的表达式,您可以使用如下代码:
(假设已经创建了 CreateLessThanExpression
和 CreateBetweenExpression
函数)
static Expression<Func<T, bool>> CreateFilterFromString<T>(Expression<Func<T, decimal>> fieldExtractor, string text)
{
var greaterOrLessRegex = new Regex(@"^\s*(?<sign>\>|\<)\s*(?<number>\d+(\.\d+){0,1})\s*$");
var match = greaterOrLessRegex.Match(text);
if (match.Success)
{
var number = decimal.Parse(match.Result("${number}"));
var sign = match.Result("${sign}");
switch (sign)
{
case ">":
return CreateGreaterThanExpression(fieldExtractor, number);
case "<":
return CreateLessThanExpression(fieldExtractor, number);
default:
throw new Exception("Bad Sign!");
}
}
var betweenRegex = new Regex(@"^\s*(?<number1>\d+(\.\d+){0,1})\s*-\s*(?<number2>\d+(\.\d+){0,1})\s*$");
match = betweenRegex.Match(text);
if (match.Success)
{
var number1 = decimal.Parse(match.Result("${number1}"));
var number2 = decimal.Parse(match.Result("${number2}"));
return CreateBetweenExpression(fieldExtractor, number1, number2);
}
throw new Exception("Bad filter Format!");
}
关于c# - 通过比较返回 lambda 表达式的扩展方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10599831/