讓OData和NHibernate結合進行動態查詢

深藍發表於2016-05-05

OData是一個非常靈活的RESTful API,如果要做出強大的查詢API,那麼OData就強烈推薦了。http://www.odata.org/

OData的特點就是可以根據傳入引數動態生成Entity Framework的查詢,最終實現動態的SQL的查詢。但是在專案有時我們並沒有採用Entity Framework,而是採用的NHibernate,那麼該怎麼用OData呢?

經過一段時間的Google和研究,終於找到了一個好的方案。

在OData API查詢時,使用者前端是url跟引數,但是在伺服器端,我們是接收到的是一個ODataQueryOptions<T>物件,其實我們需要做的就是把這個物件進行解析,生成NHibernate能夠理解的查詢形式,比如HQL。網上找到微軟官方已經寫了這麼個轉換方法,主要是對ODataQueryOptions物件下的Filter和OrderBy進行轉換,另外兩個引數Top和Skip很簡單,就是一個整數。

public static string ToHql(this ODataQueryOptions query,out int top,out int skip) 
      { 
          string queryString = "from " + query.Context.ElementClrType.Name + " $it" + Environment.NewLine; 
          if (query.Filter != null
          { 
              // convert $filter to HQL where clause. 
              string where = ToString(query.Filter); 
              queryString += where
          } 
          if(query.OrderBy!=null
          { 
          // convert $orderby to HQL orderby clause. 
              string orderBy = ToString(query.OrderBy);
              // create a query using the where clause and the orderby clause. 
               queryString +=  orderBy; 
          } 
          top = query.Top?.Value ?? 0
          skip = query.Skip?.Value ?? 0
          return queryString; 
      } 

ODataQueryOptions轉換為HQL的專案在這裡:

http://aspnet.codeplex.com/SourceControl/changeset/view/72014f4c779e#Samples/WebApi/NHibernateQueryableSample/System.Web.Http.OData.NHibernate/NHibernateFilterBinder.cs

Filter和OrderBy屬性都會被轉換成HQL,然後我們就需要進行NHibernate的查詢了。

public QueryResult<T> FindByPaging(string hql, int top, int skip) 
       { 
           bool paging = top > 0
           var query = Session.CreateQuery(hql);
           var querys = Session.CreateMultiQuery(); 
           if (paging) 
           { 
               query = query.SetFirstResult(skip).SetMaxResults(top); 
           } 
           querys.Add(query); 
         
           if (paging) 
           { 
               var countQuery = Session.CreateQuery("select count(*) " + hql); 
               querys.Add(countQuery); 
           }
           var queryResults = querys.List(); 
           var result = new QueryResult<T>(); 
           result.TotalCount = paging 
               ? Convert.ToInt32( ((IList) queryResults[1])[0]) 
               : ((IList) queryResults[0]).Count; 
           result.ResultSet = ((IList) queryResults[0]).Cast<T>().ToList(); 
           return result; 
       } 

對於一般的分頁查詢來說,我們應該會有兩個查詢,一個是查詢滿足條件的資料總條數,另一個是返回當前頁的資料集。但是似乎OData並不支援返回這樣的資料型別,OData支援的是Entity的List,如果我們重新定義了一個物件QueryResult:

[DataContract] 
  public class QueryResult<T> 
  { 
      [DataMember] 
      public int TotalCount { getset; } 
      [DataMember] 
      public IList<T> ResultSet { getset; } 
      public QueryResult() 
      { } 
      public QueryResult(int count, IList<T> list) 
      { 
          this.TotalCount = count; 
          this.ResultSet = list; 
      } 
  } 

然後在Controller中返回QueryResult,那麼系統就會報406的錯誤。其實系統給我們提供了一個專門分頁返回的物件System.Web.Http.OData.PageResult<T>,我們可以將Service返回的QueryResult封裝成PageResult再返回即可。

PageResult裡面有個NextPage的URI引數,我們可以傳Null。 

相關文章