2013-02-12 121 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