2014-02-15 37 views
0

我正在設計一個用於編寫SQL的流暢API。請記住,我的目標之一是讓API不建議無法在該鏈中調用的函數。例如,如果您剛剛在select子句中定義了一個字段,那麼在您調用From first之前,您無法調用Where。一個簡單的查詢如下所示:Fluent API中的遞歸

 string sql = SelectBuilder.Create() 
      .Select() 
       .Fld("field1") 
      .From("table1") 
      .Where() 
       .Whr("field1 > field2") 
       .Whr("CURRENT_TIMESTAMP > field3") 
      .Build() 
      .SQL; 

我的問題伴隨着SQL代碼中的遞歸。假設你想有一個字段包含另一個SQL語句,如下所示:

 string sql = SelectBuilder.Create() 
      .Select() 
       .Fld("field1") 
       .SQLFld() 
        .Select 
         .Count("field6") 
        .From("other table") 
       .EndSQLFld() 
       .FLd("field2") 
      .From("table1") 
      .Where() 
       .Whr("field1 > field2") 
       .Whr("CURRENT_TIMESTAMP > field3") 
      .Build() 
      .SQL; 

我正在使用方法鏈來構建我的流利API。它在很多方面都是遍佈許多代表每個狀態的類的狀態機。爲了添加這個功能,我需要複製我已經擁有的每個狀態,並將它們包裝在兩個SQLFld和EndSQLFld狀態中。我還需要另一個副本,如果你是更低級別的,並且將SQL語句嵌入到已經嵌入的SQL語句的字段中。這會延續到無窮大,所以對於無限深的嵌入式SQL查詢,我需要無限數量的類來表示無限狀態。

我想過編寫一個SelectBuilder查詢,它被帶到了Build方法的地方,然後將該SelectBuilder嵌入到另一個SelectBuilder中,並修復了我的無窮大問題,但它不是很優雅,這就是這個點API。

我也可以拋出這樣的想法,即API只在合適的時候提供函數,但我真的很討厭這麼做。我覺得這樣可以幫助你最好地發現如何使用API​​。在許多流利的API中,調用哪個順序並不重要,但我希望API儘可能接近實際的SQL語句並強制執行其語法。

任何人有任何想法如何解決這個問題?

+0

我還以爲你在談論遞歸的CTE。請注意,子查詢可以包含對外部環境的引用(實際上這是_normal_情況)。子查詢也可以用作表引用('FROM' /'JOIN'子句)和'WHERE'子句。我希望你不只是將字符串連接在一起,因爲人們可能會認爲這是SQL注入證明(至少,你需要提供一個可變參數化方法)。當然,C#已經有了Linq-to-SQL,儘管它有點不同。 –

回答

2

很高興看到您正在嘗試流暢的界面,我認爲它們非常優雅和富有表現力。

構建器模式不是流暢接口的唯一實現。考慮這種設計,並讓我們知道你在想什麼=)

這是一個例子,我給你留下最後實現的細節。

接口設計實例:

public class QueryDefinition 
    { 
     // The members doesn't need to be strings, can be whatever you use to handle the construction of the query. 
     private string select; 
     private string from; 
     private string where; 

     public QueryDefinition AddField(string select) 
     { 
      this.select = select; 
      return this; 
     } 

     public QueryDefinition From(string from) 
     { 
      this.from = from; 
      return this; 
     } 

     public QueryDefinition Where(string where) 
     { 
      this.where = where; 
      return this; 
     } 

     public QueryDefinition AddFieldWithSubQuery(Action<QueryDefinition> definitionAction) 
     { 
      var subQueryDefinition = new QueryDefinition(); 
      definitionAction(subQueryDefinition); 

      // Add here any action needed to consider the sub query, which should be defined in the object subQueryDefinition. 

      return this; 
     } 

用法示例:

static void Main(string[] args) 
     { 
      // 1 query deep 
      var def = new QueryDefinition(); 
      def 
       .AddField("Field1") 
       .AddField("Filed2") 
       .AddFieldWithSubQuery(subquery => 
       { 
        subquery 
         .AddField("InnerField1") 
         .AddField("InnerFiled2") 
         .From("InnerTable") 
         .Where("<InnerCondition>"); 
       }) 
       .From("Table") 
       .Where("<Condition>"); 


      // 2 queries deep 
      var def2 = new QueryDefinition(); 
      def2 
       .AddField("Field1") 
       .AddField("Filed2") 
       .AddFieldWithSubQuery(subquery => 
       { 
        subquery 
         .AddField("InnerField1") 
         .AddField("InnerField2") 
         .AddFieldWithSubQuery(subsubquery => 
          { 
           subsubquery 
            .AddField("InnerInnerField1") 
            .AddField("InnerInnerField2") 
            .From("InnerInnerTable") 
            .Where("<InnerInnerCondition>"); 
          }) 
         .From("InnerInnerTable") 
         .Where("<InnerCondition>"); 
       }) 
       .From("Table") 
       .Where("<Condition>"); 
     } 
+0

玩得很好。它看起來很不錯,完美地解決了這個問題。我想過使用lamdas的原因,但也適用於這個問題。 –

0

你不能「有隻適用法」沒有任何一個子的API爲子或明確包圍/結束所有內部結構級別(SELECT列,WHERE子句中的表達式,子查詢)。

即使這樣,通過一個API運行它將需要它是有狀態的&「模態」與「包圍」方法,以跟蹤您在decl中的位置。報告&錯誤報告會很麻煩。

以「流利」的方法結束包圍,對我來說,看起來不太流利。這將導致EndSelectEndWhere,EndSubquery等等的醜陋外觀我寧願建立子結構(如SUBQUERY選擇)到本地變量&補充說。

我不喜歡EndSQLFld()成語,它通過終止字段隱式終止子查詢。我更喜歡&猜測這將是更好的設計來終止子查詢本身,這是嵌套結構的複雜部分 - 而不是字段。

說實話,試圖對「聲明式」語言(SQL)強制執行「聲明式」API的排序似乎是浪費時間。


也許我會考慮更接近理想的使用:

SelectBuilder select = SelectBuilder.Create("CUSTOMER") 
     .Column("ID") 
     .Column("NAME") 
     /*.From("CUSTOMER")*/  // look, I'm just going to promote this onto the constructor. 
     .Where("field1 > field2") 
     .Where("CURRENT_TIMESTAMP > field3"); 

SelectBuilder countSubquery = SelectBuilder.Create("ORDER") 
     .Formula("count(*)"); 
     .Where("ORDER.FK_CUSTOMER = CUSTOMER.ID"); 
     .Where("STATUS = 'A'"); 

select.Formula(countSubquery, "ORDER_COUNT"); 
string sql = SelectBuilder.SQL; 

道歉Hibernate的標準API :)