2013-02-12 126 views
1

不确定这是否完全可能;我发现了一些关于表达式和谓词构建器的东西,但到目前为止,没有什么能让你在事先不知道它们的情况下运行任意查询。在运行时动态生成谓词

基本上,我有一个大的SQL数据库对象的集合,和我建立一个网页(ASP.NET MVC 4)允许用户显示和过滤这些对象。用户将要输入的查询的复杂程度会有所不同。让他们输入这些查询的最简单最简单的方法就像Visual Studio TFS插件让您搜索工作项目的方式:一个条件表,您可以在其中添加行。您可以选择“和”或“或”为连接条件,然后选择一个字段中输入一个值,并选择是否要的东西,做或不匹配它:

1. show items where [Field] [is|is not] [value] 
2.   [and|or] [Field] [is|is not] [value] 
3.   [and|or] [Field] [is|is not] [value] 
etc... 

什么是把最简单的方法那到LINQ-ish的东西,我可以坚持一个.ToList()年底?唯一的解决办法,我拿出迄今已参与的情况下相当大的和丑陋的交换机组匹配上.Where()的各个领域和粘性,但允许用户选择“或”为条件,那么我结束了做这样的事情:

  • 虽然条件是AND:
    • 使用大型交换机到外地
    • query = query.Where(ThisField == value);
  • 匹配当你点击一个条件是OR:
    • 从完全未筛选列表追加当前结果与临时列表
    • 新的查询
    • 使用大开关当您运行的条件来满足现场
    • query = fullList.Where(ThisField == value);
    • 继续像以前
  • ,将当前结果集追加到您一直使用的临时列表中,然后返回该列表。

这似乎比我想不太优雅。

+0

你想查询一个数据库(使用类似LINQ to SQL的东西)还是内存列表? – svick 2013-02-12 15:53:27

+0

数据库,最好。如果容易,我可以将负载放入内存并在其中工作,但我宁愿不要因为数据库相当庞大。 – anaximander 2013-02-13 09:01:22

回答

6

你可以这样说:

class Program 
{ 
    public enum Operator 
    { 
     And, 
     Or 
    } 

    public class Condition 
    { 
     public Operator Operator { get; set; } 
     public string FieldName { get; set; } 
     public object Value { get; set; } 
    } 

    public class DatabaseRow 
    { 
     public int A { get; set; } 
     public string B { get; set; } 
    } 

    static void Main(string[] args) 
    { 
     var conditions = new List<Condition> 
     { 
      new Condition { Operator = Operator.And, FieldName = "A", Value = 1 }, 
      new Condition { Operator = Operator.And, FieldName = "B", Value = "Asger" }, 
      new Condition { Operator = Operator.Or, FieldName = "A", Value = 2 }, 
     }; 

     var parameter = Expression.Parameter(typeof (DatabaseRow), "x"); 
     var currentExpr = MakeExpression(conditions.First(), parameter); 
     foreach (var condition in conditions.Skip(1)) 
     { 
      var nextExpr = MakeExpression(condition, parameter); 
      switch (condition.Operator) 
      { 
       case Operator.And: 
        currentExpr = Expression.And(currentExpr, nextExpr); 
        break; 
       case Operator.Or: 
        currentExpr = Expression.Or(currentExpr, nextExpr); 
        break; 
       default: 
        throw new ArgumentOutOfRangeException(); 
      } 
     } 

     var predicate = Expression.Lambda<Func<DatabaseRow, bool>>(currentExpr, parameter).Compile(); 

     var input = new[] 
     { 
      new DatabaseRow {A = 1, B = "Asger"}, 
      new DatabaseRow {A = 2, B = "Hans"}, 
      new DatabaseRow {A = 3, B = "Grethe"} 
     }; 

     var results = input.Where(predicate).ToList(); 
    } 

    static BinaryExpression MakeExpression(Condition condition, ParameterExpression parameter) 
    { 
     return Expression.Equal(
      Expression.MakeMemberAccess(parameter, typeof (DatabaseRow).GetMember(condition.FieldName)[0]), 
      Expression.Constant(condition.Value)); 
    } 
} 

这里假设你有一个类作为数据库行与正确类型的模型。然后,您可以通过正则表达式将您的条件解析为上面显示的类型条件列表,并且提供的代码可以将其转换为表达式树。得到的表达式可以被编译并运行(如图所示)或转换为SQL(而不是将谓词填充到IQueryable.Where中)。

2

您可以使用PredicateBuilder from LINQKit做到这一点。利用其And()Or()扩展方法,你可以建立您的查询的expression tree。然后,您可以使用该表达式树作为您的Where()的条件。您还需要透过电话AsExpandable()或您query,或对产生的表达调用Expand()

+0

与此相关的问题是我添加的这个软件是一个工作项目,添加的东西很痛苦......如果可能的话,使用标准.NET库这样做的方式将是首选。如果不是,那么我总是可以向上级提交请求。 – anaximander 2013-02-13 11:40:59

+0

@anaximander嗯,你已经在页面上获得了完整的源代码 - 它确实只是~30行代码。我还建议进行一些修改 - 在'IQueryable'上对'True'和'False'进行扩展方法是非常方便的,例如,通过推理来获得正确的类型(所以你可以输入'input.True()'而不是'PredicateBuilder.True ()'。当然,这是在谓词中使用匿名类型的唯一方法:) – Luaan 2015-03-24 07:21:19