2011-03-07 113 views
0

我試圖讓頁面查詢正常使用LINQ和NHibernate。在一張桌子上,這是完美的,但是當多個桌子結合在一起時,這會導致我大失所望。這是我到目前爲止。NHibernate的 - LINQ查詢使用COUNT(DISTINCT)

public virtual PagedList<Provider> GetPagedProviders(int startIndex, int count, System.Linq.Expressions.Expression<Func<Record, bool>> predicate) { 

    var firstResult = startIndex == 1 ? 0 : (startIndex - 1) * count; 

    var query = (from p in session.Query<Record>() 
       .Where(predicate) 
       select p.Provider); 

    var rowCount = query.Select(x => x.Id).Distinct().Count(); 

    var pageOfItems = query.Distinct().Skip(firstResult).Take(count).ToList<Provider>(); 
    return new PagedList<Provider>(pageOfItems, startIndex, count, rowCount); 
} 

我遇到了一些問題。首先,rowCount變量在具有一對多連接的連接上拉回一個COUNT(*)。所以我的計數是應該的,我期待129,但回到1K以上。

我正在發生的第二個問題是結果。如果我在第一頁(firstResult = 0),那麼我會得到正確的結果。但是,如果firstResult不是0,它會生成完全不同的SQL。下面是兩種情況下生成的SQL,我已經刪除了一些脂肪,使其更具可讀性。

--firstResult = 0 
select distinct TOP (30) provider1_.Id as Id12_, provider1_.TaxID as TaxID12_, 
provider1_.Facility_Name as Facility5_12_, provider1_.Last_name as Last6_12_, 
provider1_.First_name as First7_12_, provider1_.MI as MI12_ 
from PPORecords record0_ left outer join PPOProviders provider1_ on record0_.Provider_id=provider1_.Id, 
PPOProviders provider2_ where record0_.Provider_id=provider2_.Id 
and provider2_.TaxID='000000000' 

--firstResult = 30 
SELECT TOP (30) Id12_, TaxID12_, Facility5_12_, Last6_12_, First7_12_, MI12_, 
FROM (select distinct provider1_.Id as Id12_, provider1_.TaxID as TaxID12_, 
provider1_.Facility_Name as Facility5_12_, 
provider1_.Last_name as Last6_12_, 
provider1_.First_name as First7_12_, 
provider1_.MI as MI12_, 
ROW_NUMBER() OVER(ORDER BY CURRENT_TIMESTAMP) as __hibernate_sort_row 
from PPORecords record0_ left outer join PPOProviders provider1_ on record0_.Provider_id=provider1_.Id, 
PPOProviders provider2_ 
where record0_.Provider_id=provider2_.Id and provider2_.TaxID='000000000') as query 
WHERE query.__hibernate_sort_row > 30 
ORDER BY query.__hibernate_sort_row 

第二個查詢的問題是「distinct」關鍵字不存在於外部查詢中,只存在於內部查詢中。任何想法如何糾正這一點?

感謝您的任何建議!

[UPDATE]

這是構建LINQ查詢謂詞的代碼。

private Expression<Func<Record, bool>> ParseQueryExpression(string Query) { 
      Expression<Func<Record, bool>> mExpression = x => true; 
      string[] splitQuery = Query.Split('|'); 
      foreach (string query in splitQuery) { 
       if (string.IsNullOrEmpty(query)) 
        continue; 
       int valStartIndex = query.IndexOf('('); 
       string variable = query.Substring(0, valStartIndex); 
       string value = query.Substring(valStartIndex + 1, query.IndexOf(')') - valStartIndex - 1); 

       switch (variable) { 
        case "tax": 
         mExpression = x => x.Provider.TaxID == value; 
         break; 
        case "net": 
         mExpression = Combine<Record>(mExpression, x => x.Network.Id == int.Parse(value)); 
         break; 
        case "con": 
         mExpression = Combine<Record>(mExpression, x => x.Contract.Id == int.Parse(value)); 
         break; 
        case "eff": 
         mExpression = Combine<Record>(mExpression, x => x.Effective_Date >= DateTime.Parse(value)); 
         break; 
        case "trm": 
         mExpression = Combine<Record>(mExpression, x => x.Term_Date <= DateTime.Parse(value)); 
         break; 
        case "pid": 
         mExpression = Combine<Record>(mExpression, x => x.Provider.Id == long.Parse(value)); 
         break; 
        case "rid": 
         mExpression = Combine<Record>(mExpression, x => x.Rate.Id == int.Parse(value)); 
         break;       
       } 
      } 
      return mExpression; 
     } 

回答

1

看起來好像Linq提供程序在那裏有一些「意外」行爲。我可以用您正在描述的rowCount重現問題,並在嘗試使用子查詢修復它時遇到一些問題。

如果我正確理解你的意圖,你基本上想要一個供應商的分頁列表,其記錄符合特定的標準。 在這種情況下,我會建議使用子查詢。但是,我試圖用Query()方法實現子查詢,但它不起作用。所以我嘗試了完美工作的QueryOver()方法。在你的情況下,所需的查詢將如下所示:

Provider pAlias = null; 
var query = session.QueryOver<Provider>(() => pAlias).WithSubquery 
         .WhereExists(QueryOver.Of<Record>() 
          .Where(predicate) 
          .And(r => r.Provider.Id == pAlias.Id) 
          .Select(r => r.Provider)); 

var rowCount = query.RowCount(); 

var pageOfItems = query.Skip(firstResult).Take(count).List<Provider>(); 

這樣你就不必與Distinct()掙扎。就像我個人喜歡和使用NHibernate.Linq一樣,我想,如果在特定情況下它不起作用,那麼應該使用其他可行的東西。

編輯:將查詢拆分爲更小的單位。

// the method ParseQueryExpression() does not need to be modified, the Expression should work like that 
System.Linq.Expressions.Expression<Func<Record, bool>> predicate = x => x.Provider.TaxID == "000000000"; 

// Query to evaluate predicate 
IQueryable<Record> queryId = session.Query<Record>().Where(predicate).Select(r => r.Provider); 
// extracting the IDs to use in a subquery 
List<int> idList = queryId.Select(p => p.Id).Distinct().ToList(); 
// total count of distinct Providers 
int rowCount = idList.Count; 

// new Query on Provider, using the distinct Ids 
var query = session.Query<Provider>().Where(p => idList.Contains(p.Id)); 
// the List<Provider> to display on the page 
var pageOfItems = query.Skip(firstResult).Take(count).ToList(); 

這裏的問題是,你有多個查詢,因此多次往返數據庫。當您檢查生成的sql時會出現另一個問題。子查詢idList.Contains(p.Id)將生成帶有大量參數的in-clause。

正如你所看到的,這些是NHibernate.Linq查詢。這是因爲新的QueryOver功能不是真正的Linq提供程序,並且不適用於動態Linq表達式。

您可以通過使用Detached QueryOvers來避開該限制。這裏的缺點是你必須以某種方式修改你的ParseQueryExpression(),例如像這樣:

private QueryOver<Record> ParseQueryExpression(string Query) 
{ 
    Record rAlias = null; 
    var detachedQueryOver = QueryOver.Of<Record>(() => rAlias) 
       .JoinQueryOver(r => r.Provider) 
       .Where(() => rAlias.Provider.TaxID == "000000000") 
       .Select(x => x.Id); 
    // modify your method to match the return value 
    return detachedQueryOver; 
} 

// in your main method 
var detachedQueryOver = QueryOver.Of<Record>() 
    .WithSubquery 
    .WhereProperty(r => r.Id 
    .In(this.ParseQueryExpression(...)); 

var queryOverList = session.QueryOver<Provider>() 
    .WithSubquery 
    .WhereProperty(x => x.Id) 
    .In(detachedQueryOver.Select(r => r.Provider.Id)); 

int rowCount = queryOverList.RowCount(); 
var pageOfItems = queryOverList.Skip(firstResult).Take(count).List(); 

嗯,我希望你不要太困惑。

+0

感謝您的迴應!這種方法絕對有效,但它似乎並不喜歡我傳入的謂詞。我得到的錯誤是「值不能爲null。參數名稱:key」。 謂詞是一個linq查詢,我從MVC應用程序中的控制器動態構建。實質上,我將一個字符串解析爲一個大的linq查詢。我將使用該代碼更新上述帖子。無論如何,如果我嘗試手動插入查詢「.Where(x => x.Provider.TaxID ==」000000000「),我會得到相同的錯誤,但以下工作--- .Where((=) > pAlais.TaxID ==「000000000」) – Brosto 2011-03-08 14:52:18

+0

剛剛更新了上面的代碼,正如你所看到的,當這個查詢被完全利用時,它可以跨越多個表格,我的「Record」表格就是將它們連接在一起的粘合劑。實際上有幾個分頁列表使用相同的方法,並採用相同的確切參數(這就是爲什麼我在一個位置創建這個linq查詢...)我不確定錯誤消息是指什麼。任何想法? – Brosto 2011-03-08 15:10:55

+0

似乎Linq表達式並不真正支持。我的建議是通過在多個查詢中拆分查詢來降低查詢的複雜性。不幸的是,這意味着您將有更多的往返數據庫。我會更新我的答案,舉例說明我的意思。 – 2011-03-09 12:39:49