通过比较返回lambda表达式的扩展方法

我正在为我们这个庞大的项目创建一个更精细的过滤系统。 其中一个主要谓词是能够通过字符串参数传递比较。 这表现为以下forms:“> 50”或“5-10”或“<123.2”

我有什么(作为一个例子来说明)

视图模型:

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  5 && field.Required < 10); 

或类似的东西

但是……我不知道从哪里开始。 我把它缩小到了

 public static Expression Compare(this Expression<Func> value, string compare) 

它甚至可能不正确,但这是我所拥有的一切。 比较构建器不是问题,这很容易。 困难的部分实际上是返回表达式。 我从未尝试将表达式作为函数值返回。 所以我基本上需要保留的是字段并返回一个比较表达式。

有帮助吗? :X

更新:

唉,这不能解决我的问题。 这可能是因为我在过去的23个小时里一直在努力,但我对如何将其变成一种扩展方法没有丝毫的线索。 正如我所说,我想要的……基本上是一种写​​作方式:

 var ex = new ExTest(); var items = ex.Repo.Items.Where(x => x.Cost.Compare("<50")); 

我塑造这个function的方式(可能是完全错误的)是

 public static Expression<Func> Compare(string arg) { if (arg.Contains(" d  d > int.Parse(arg); } 

它首先缺少“this -something- value”,而我还没有弄清楚如何让它能够获得表达式输入…至于ReSharper,它建议我转换它改为布尔值…

我的脑袋此刻充满了绒毛……

更新2:

我设法找到了一种方法,可以在控制台应用程序的内存存储库中使用一段代码。 我还没有尝试使用Entity Framework。

 public static bool Compare(this double val, string arg) { var arg2 = arg.Replace("", ""); if (arg.Contains("<")) return val  double.Parse(arg2); } 

但是,我非常怀疑这就是我所追求的

更新3:

是的,在坐下来再次查看lambda表达式之后,在最后一个答案之前,我想出了类似于下面的内容,它没有填写“Compare()”的确切要求但是它是’overload-ish’方法:

 public static IQueryable WhereExpression(this IQueryable queryable, Expression<Func> predicate, string arg) { var lambda = Expression.Lambda<Func>(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>(Expression.LessThan(predicate.Body, Expression.Constant(double.Parse(50.ToString())))); 

我非常接近解决方案。 如果我能从背后得到错误,我相信EF应该能够将其转换为SQL。 否则……好吧,最后的反应可能会去。

要生成表达式,将转换为SQL(eSQL),您应该手动生成Expression 。 以下是GreaterThanfilter创建的示例,其他filter可以使用类似的技术制作。

 static Expression> CreateGreaterThanExpression(Expression> 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>(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上下文,实体设置TestEnitities of TestEntity实体)

 static void Main(string[] args) { using (var ents = new StackEntities()) { var filter = CreateGreaterThanExpression(x => x.SortProperty, 3); var items = ents.TestEnitities.Where(filter).ToArray(); } } 

更新:为了创建复杂表达式,您可以使用以下代码:(假设已经创建了CreateLessThanExpressionCreateBetweenExpression函数)

 static Expression> CreateFilterFromString(Expression> fieldExtractor, string text) { var greaterOrLessRegex = new Regex(@"^\s*(?\>|\<)\s*(?\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*(?\d+(\.\d+){0,1})\s*-\s*(?\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#编译器的第一眼神奇function之一可以为您完成艰苦的工作。 你可能知道你可以这样做:

 Func totalCostIsUnder50 = d => d < 50m; 

也就是说,使用lambda表达式来指定Func 。 但是你知道你也可以这样做:

 Expression> totalCostIsUnder50Expression = d => d < 50m; 

也就是说, 使用lambda表达式来分配Expression FuncExpression ? 它非常整洁。

鉴于你说

比较构建器不是问题,这很容易。 困难的部分实际上是返回表达式

我假设你可以在这里填空; 假设我们将“<50”传递给:

 Expression> TotalCostCheckerBuilder(string criterion) { // Split criterion into operator and value // when operator is < do this: return d => d < value; // when operator is > do this: return d => d > value; // and so on } 

最后,要将Expression&&组合在一起(并且仍然有Expression ),请执行以下操作:

 var andExpression = Expression.And(firstExpression, secondExpression); 

困难的部分实际上是返回表达式。

将字符串转换为更多结构化的构造,如枚举和类,以定义属性,运算符和filter:

 Enum Parameter TotalCost Required End Enum Enum Comparator Less More Equals End Enum Class Criterion Public ReadOnly Parameter As Parameter Public ReadOnly Comparator As Comparator Public ReadOnly Value As Double Public Sub New(Parameter As Parameter, Comparator As Comparator, Value As Double) Me.Parameter = Parameter Me.Comparator = Comparator Me.Value = Value End Sub End Class 

然后定义了一个创建表达式的函数:

 Function CreateExpression(Criteria As IEnumerable(Of Criterion)) As Expression(Of Func(Of Field, Boolean)) Dim FullExpression = PredicateBuilder.True(Of Field)() For Each Criterion In Criteria Dim Value = Criterion.Value Dim TotalCostExpressions As New Dictionary(Of Comparator, Expression(Of Func(Of Field, Boolean))) From { {Comparator.Less, Function(Field) Field.TotalCost < Value}, {Comparator.More, Function(Field) Field.TotalCost > Value}, {Comparator.Equals, Function(Field) Field.TotalCost = Value} } Dim RequiredExpressions As New Dictionary(Of Comparator, Expression(Of Func(Of Field, Boolean))) From { {Comparator.Less, Function(Field) Field.Required < Value}, {Comparator.More, Function(Field) Field.Required > Value}, {Comparator.Equals, Function(Field) Field.Required = Value} } Dim Expressions As New Dictionary(Of Parameter, IDictionary(Of Comparator, Expression(Of Func(Of Field, Boolean)))) From { {Parameter.TotalCost, TotalCostExpressions}, {Parameter.Required, RequiredExpressions}} Dim Expression = Expressions(Criterion.Parameter)(Criterion.Comparator) FullExpression = Expression.And(Expression) Next Return FullExpression End Function 

这里采用的PredicateBuilder需要将两个表达式与AND运算符组合在一起。

用法:

 Function Usage() As Integer Dim Criteria = { New Criterion(Parameter.TotalCost, Comparator.Less, 50), New Criterion(Parameter.Required, Comparator.More, 5), New Criterion(Parameter.Required, Comparator.Less, 10)} Dim Expression = CreateExpression(Criteria) End Function 

它将创建与示例中提供的完全相同的表达式

 field => field.TotalCost < 50 && field.Required > 5 && field.Required < 10