2

我有一個web應用程序,顯示一些實體的細節,我們稱之爲Log。該實體通過實體框架4從SQL Server加載。如何通過Entity Framework選擇下一個和前一個實體?

我想提供'下一個'和'上一個'鏈接來雙向瀏覽日誌。

日誌由兩個屬性/列下令:

  • Date
  • Time

這兩列可能包含null,並沒有唯一性保證。如果這兩個值都爲空,那麼爲了保證穩定的排序,我通過數據庫Id訂購,該數據庫保證爲非空且唯一。

此外,在給定Log之前或之後可能實際上不存在實體。

some其他questions這個地址直接與SQL做這個。我想知道如何使用實體框架來做到這一點,理想情況下只做一次數據庫訪問,以便爲這對Logs(id,標題等)帶回幾個字段。

+0

如果數據和時間都爲空,則不能保證上一個和下一個值實際上是當前記錄的上一個和下一個日誌記錄。 – 2011-03-21 18:44:37

+0

爲了保證一個穩定的訂單,如果兩行中的日期和時間相等(包括它們都爲空),我將回退到行ID。我已經更新了這個問題,謝謝。 – 2011-03-21 19:15:01

+0

所以不會像這樣的工作? '.OrderBy(x => x.Id).FirstOrDefault(x => x.Id> currentSequenceId)'這就是說給我當前記錄的'ID',我會在數據庫中搜索更大的第一條記錄比你給我的身份證。 – 2012-09-28 04:00:38

回答

1

我不知道,如果這個工程,但我們把它一試:

var query =(
     from l in context.Logs 
     where l.UserId == log.UserId && 
      ( l.Date < log.Date 
       || (l.Date == log.Date && l.Time < log.Time) 
       || (l.Date == log.Date && l.Time == log.Time && l.Id < log.Id) 
      ) 
     orderby l.Date descending, l.Time descending 
     select l 
    ).Take(1) 
    .Concat((
     from l in context.Logs 
     where l.UserId == log.UserId && 
      ( l.Date > log.Date 
       || (l.Date == log.Date && l.Time > log.Time) 
       || (l.Date == log.Date && l.Time == log.Time && l.Id > log.Id) 
      ) 
     orderby l.Date, l.Time 
     select l 
    ).Take(1)); 
+0

由於'Concat'可用於'IQueryable',因此這似乎工作正常。有趣。我必須引入一個額外的屬性來指出哪一行是哪一行,因爲'Take(1)'可能會返回一個空序列,在這種情況下,單行是前一個日誌還是下一個日誌是不清楚的。 'Concat'似乎是我錯過的東西(現在看起來很明顯!)謝謝。 – 2011-03-22 06:23:57

1

不支持TakeSkip

LINQ的美妙之處在於,您可以用q.Skip(50).Take(50)來描述這個複雜的排序標準和剛纔所說的結果。如果每個頁面顯示50個結果,那麼這將帶您進入第二頁。當然,它轉換爲有效的T-SQL,它使用ROW_NUMBER窗口函數指示數據庫使用您指定的順序查找結果。

你甚至可以有一個非常複雜的查詢與大量的過濾器。最終的結果仍然可以管理,因爲您要麼有行,要麼不行。所有你需要考慮的是結果可能是空的。

關於身份的說明,因爲拉迪斯拉夫指出,在完全相同的排序鍵(即日期和時間都爲空)的條目之間不保證順序。所以你要做的是添加一個身份列,這是你最不重要的排序列。沒有身份的日誌表/實體有時會被認爲設計不當,因爲當Date和Time可能爲空時,數據的增長是不可預測的。這會導致頁面拆分不良。經驗法則是表應該有一個狹窄而唯一的集羣主鍵。身份專欄非常適合這一點。它還將確保插入是快速操作,這是您的日誌表將會欣賞的事情。

隨着視圖的幫助下,你可以把訂單和ROW_NUMBER東西在普通T-SQL然後查詢,使用EF這樣的:

var q = from x in source 
     join y in source on x.RowNumber equals y.RowNumber - 1 into prev 
     join z in source on x.RowNumber equals z.RowNumber + 1 into next 
     from p in prev.DefaultIfEmpty() 
     from n in next.DefaultIfEmpty() 
     select new { Current = x, Previous = p, Next = n } 
     ; 

...或可能:

var q = from x in source 
     join y in source on x.RowNumber equals y.RowNumber - 1 into prev 
     join z in source on x.RowNumber equals z.RowNumber + 1 into next 
     select new { 
      Current = x, 
      Previous = prev.DefaultIfEmpty(), 
      Next = next.DefaultIfEmpty() 
     } 
     ; 
+0

謝謝約翰。是EF支持Take和Skip。當我不知道我的實體在排序後的查詢中是什麼行號時,如何使用它們?如果我這樣做了,我可以使用'Skip(N-1).Take(3)',但在這種情況下我不能。我有正在查看的項目的「Id」,需要查找其他兩個。至於拉迪斯拉夫的觀點,我更新了這個問題,指出我使用'Id'列作爲第三層排序鑑別器,保證了穩定的排序。 – 2011-03-21 19:20:17

+0

我在這裏得到的真正問題是:我怎樣才能做到這一點與SQL一次?我有一個使用'FirstOrDefault'的實現,它使用兩組過濾和排序的'Log's,但這需要兩次到DB的往返。在普通的SQL中,我可以使用'union',但是我不知道如何在使用'FirstOrDefault'時如何在Linq中進行聯合,因爲該方法是評估IQueryable並終止一個對象的終結器,而不是一個可以結合的集合。 – 2011-03-21 19:22:29

+0

您需要跟蹤當前行號,如果您沒有它並需要根據id計算當前行號,ROW_NUMBER窗口函數可以執行此操作,但它需要編寫一些T-SQL ,如果你真的想用EF做它,我想這取決於產生索引的Select重載是否可以從類似'ROW_NUMBER'函數的項目中得到結果,但我對此表示懷疑。 – 2011-03-21 22:05:02

0

這是我一直在使用的解決方案。理想情況下,我希望通過一次數據庫調用完成此操作,因此如果任何人都可以告訴我該怎麼做,我會接受他們的答案。

// Prev 
var previousLog = (
     from l in context.Logs 
     where l.UserId == log.UserId && 
      ( l.Date < log.Date 
       || (l.Date == log.Date && l.Time < log.Time) 
       || (l.Date == log.Date && l.Time == log.Time && l.Id < log.Id) 
      ) 
     orderby l.Date descending, l.Time descending 
     select l 
    ).FirstOrDefault(); 

// Next 
var nextLog = (
    from l in context.Logs 
    where l.UserId == log.UserId && 
      ( l.Date > log.Date 
       || (l.Date == log.Date && l.Time > log.Time) 
       || (l.Date == log.Date && l.Time == log.Time && l.Id > log.Id) 
      ) 
    orderby l.Date, l.Time 
    select l 
).FirstOrDefault(); 
0

把記錄選擇邏輯到上一個和下一個按鈕seperatly。這樣你只需要每次點擊一次按鈕就可以調用數據庫。

+0

感謝您的回答。這可能會幫助其他人,但在我的情況下,我想在按鈕(實際上是鏈接)中包含next/prev記錄的詳細信息。 – 2013-03-14 01:49:54

相關問題