2016-11-10 42 views
1

目前我通過檢索IQueryable<T1>的方法從數據庫中檢索數據,過濾,排序然後將其分頁(所有這些都基本在數據庫上),之前將結果返回到UI以顯示在分頁表中。來自多個來源的過濾,合併,排序和頁面數據

我需要集成來自另一個數據庫的結果,而分頁似乎是主要問題。

  • 模型類似但不相同(相同的字段,不同的名稱,在返回之前需要映射到通用域模型);
  • 加入數據庫級別是不可能的;
  • 目前在兩個數據庫之間有〜1000條記錄(在過去18個月內的 期間添加),並且可能以大致相同(緩慢)的速度增長;
  • 結果總是需要按1-2個字段排序(日期明智)。

我目前正在撕裂這2個解決方案之間:

  1. 檢索來自兩個來源的所有數據,合併,排序,然後緩存他們。然後在接收請求時簡單地篩選和分頁緩存 - 但是當收集被修改時(我可以),我需要使緩存無效。
  2. 過濾每個數據源的數據(同樣在數據庫級別),然後在返回之前檢索,合併,排序&頁面。

我正在尋找一個體面的算法性能明智。理想的解決方案可能是它們之間的組合(緩存+在數據庫級別進行過濾),但目前我還沒有圍繞過這個方向。

+0

對面的數據庫包含重複? – SilentTremor

+0

否定,沒有重複 –

+0

我喜歡你的問題;)我做了類似的事情,但沒有分頁,我會嘗試看看是否可以將分頁添加到我在那裏。 – SilentTremor

回答

0

我認爲你可以使用下面的算法。假設你的頁面大小是10,那麼對於頁面0:

  1. 從數據庫A獲得10個結果,在數據庫級別進行過濾和排序。
  2. 從數據庫B獲得10個結果,在db級別進行過濾和排序(與上面的查詢並行)
  3. 將這兩個結果組合起來,以正確的排序順序獲得10條記錄。所以,你有20個記錄排序,但把他們和顯示的只有前10的UI

然後第1頁:你如何使用用戶界面來顯示從數據庫A和B的許多項目

  1. 公告在上一步。例如,您使用數據庫A中的2個項目和數據庫B中的8個項目。
  2. 從數據庫A獲得10個結果,已篩選和排序,但從位置2開始(跳過2),因爲已經在UI中顯示了這兩個結果。
  3. 從數據庫B獲得10個結果,過濾並排序,但從位置8開始(跳過8)。
  4. 合併與上述相同的方法從20中獲得10條記錄。現在假設您使用了A中的5個項目和B中的5個項目。現在總共顯示了來自A的7個項目和來自B的13個項目。下一步的數字。

這不會允許(很容易)跳過頁面,但據我所知,這不是一個要求。

性能應該與查詢單個數據庫時的效率相同,因爲對A和B的查詢可以並行完成。

+0

我理解你提出的解決方案,並想到它之前,但我需要以RESTful的方式做到這一點,而不必記住單個項目索引 - 此外,我討厭不得不擴大up –

+0

但是你需要記住現在你在哪個頁面,爲什麼不記得2個數字?您將所有內容存儲在客戶端上,而不是服務器上,因此不確定它與RESTful ness的關係如何。 – Evk

+0

您可能意思是您不希望將兩個不明確的參數添加到REST API中。那麼,這可能是合理的。至於規模 - 將所有數據拉入內存和在那裏工作的規模很大。 – Evk

0

我在這裏創建了一些東西,如果需要的話,我會回來解釋。 我不確定我的算法對所有邊緣情況都能正確運行,它涵蓋了我所想到的所有情況,但你永遠不知道。我將把代碼留在這裏以供您的樂趣,我會回答並解釋如果您需要這些代碼,請發表評論。

然後執行多個測試,其中的值之間有很大差距的項目列表。

using System; 
using System.Collections.Generic; 
using System.Linq; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     //each time when this objects are accessed, consider as a database call 
     private static IQueryable<model1> dbsetModel_1; 
     private static IQueryable<model2> dbsetModel_2; 

     private static void InitDBSets() 
     { 
      var rnd = new Random(); 
      List<model1> dbsetModel1 = new List<model1>(); 
      List<model2> dbsetModel2 = new List<model2>(); 
      for (int i = 1; i < 300; i++) 
      { 
       if (i % 2 == 0) 
       { 
        dbsetModel1.Add(new model1() { Id = i, OrderNumber = rnd.Next(1, 10), Name = "Test " + i.ToString() }); 
       } 
       else 
       { 
        dbsetModel2.Add(new model2() { Id2 = i, OrderNumber2 = rnd.Next(1, 10), Name2 = "Test " + i.ToString() }); 
       } 
      } 
      dbsetModel_1 = dbsetModel1.AsQueryable(); 
      dbsetModel_2 = dbsetModel2.AsQueryable(); 
     } 

     public static void Main() 
     { 
      //generate sort of db data 
      InitDBSets(); 
      //test 
      var result2 = GetPage(new PagingFilter() { Page = 5, Limit = 10 }); 
      var result3 = GetPage(new PagingFilter() { Page = 6, Limit = 10 }); 
      var result5 = GetPage(new PagingFilter() { Page = 7, Limit = 10 }); 
      var result6 = GetPage(new PagingFilter() { Page = 8, Limit = 10 }); 
      var result7 = GetPage(new PagingFilter() { Page = 4, Limit = 20 }); 
      var result8 = GetPage(new PagingFilter() { Page = 200, Limit = 10 }); 

     } 


     private static PagedList<Item> GetPage(PagingFilter filter) 
     { 
      int pos = 0; 
      //load only start pages intervals margins from both database 
      //this part need to be transformed in a stored procedure on db one, skip, take to return interval start value for each frame 
      var framesBordersModel1 = new List<Item>(); 
      dbsetModel_1.OrderBy(x => x.Id).ThenBy(z => z.OrderNumber).ToList().ForEach(i => { 
       pos++; 
       if (pos - 1 == 0) 
       { 
        framesBordersModel1.Add(new Item() { criteria1 = i.Id, criteria2 = i.OrderNumber, model = i }); 
       } 
       else if ((pos - 1) % filter.Limit == 0) 
       { 
        framesBordersModel1.Add(new Item() { criteria1 = i.Id, criteria2 = i.OrderNumber, model = i }); 
       } 

      }); 
      pos = 0; 
      //this part need to be transformed in a stored procedure on db two, skip, take to return interval start value for each frame 
      var framesBordersModel2 = new List<Item>(); 
      dbsetModel_2.OrderBy(x => x.Id2).ThenBy(z => z.OrderNumber2).ToList().ForEach(i => { 
       pos++; 
       if (pos - 1 == 0) 
       { 
        framesBordersModel2.Add(new Item() { criteria1 = i.Id2, criteria2 = i.OrderNumber2, model = i }); 
       } 
       else if ((pos -1) % filter.Limit == 0) 
       { 
        framesBordersModel2.Add(new Item() { criteria1 = i.Id2, criteria2 = i.OrderNumber2, model = i }); 
       } 

      }); 

      //decide where is the position of your cursor based on start margins 
      //int mainCursor = 0; 
      int cursor1 = 0; 
      int cursor2 = 0; 
      //filter pages start from 1, filter.Page cannot be 0, if indeed you have page 0 change a lil' bit he logic 
      if (framesBordersModel1.Count + framesBordersModel2.Count < filter.Page) throw new Exception("Out of range"); 
      while (cursor1 + cursor2 < filter.Page -1) 
      { 
       if (framesBordersModel1[cursor1].criteria1 < framesBordersModel2[cursor2].criteria1) 
       { 
        cursor1++; 
       } 
       else if (framesBordersModel1[cursor1].criteria1 > framesBordersModel2[cursor2].criteria1) 
       { 
        cursor2++; 
       } 
       //you should't get here case main key sound't be duplicate, annyhow 
       else 
       { 
        if (framesBordersModel1[cursor1].criteria2 < framesBordersModel2[cursor2].criteria2) 
        { 
         cursor1++; 
        } 
        else 
        { 
         cursor2++; 
        } 
       } 
       //mainCursor++; 
      } 
      //magic starts 
      //inpar skipable 
      int skipEndResult = 0; 
      List<Item> dbFramesMerged = new List<Item>(); 
      if ((cursor1 + cursor2) %2 == 0) 
      { 
       dbFramesMerged.AddRange(
        dbsetModel_1.OrderBy(x => x.Id) 
         .ThenBy(z => z.OrderNumber) 
         .Skip(cursor1*filter.Limit) 
         .Take(filter.Limit) 
         .Select(x => new Item() {criteria1 = x.Id, criteria2 = x.OrderNumber, model = x}) 
         .ToList()); //consider as db call EF or Stored Procedure 
       dbFramesMerged.AddRange(
        dbsetModel_2.OrderBy(x => x.Id2) 
         .ThenBy(z => z.OrderNumber2) 
         .Skip(cursor2*filter.Limit) 
         .Take(filter.Limit) 
         .Select(x => new Item() {criteria1 = x.Id2, criteria2 = x.OrderNumber2, model = x}) 
         .ToList()); 
       ; //consider as db call EF or Stored Procedure 
      } 
      else 
      { 
       skipEndResult = filter.Limit; 
       if (cursor1 > cursor2) 
       { 
        cursor1--; 
       } 
       else 
       { 
        cursor2--; 
       } 
       dbFramesMerged.AddRange(
        dbsetModel_1.OrderBy(x => x.Id) 
         .ThenBy(z => z.OrderNumber) 
         .Skip(cursor1 * filter.Limit) 
         .Take(filter.Limit) 
         .Select(x => new Item() { criteria1 = x.Id, criteria2 = x.OrderNumber, model = x }) 
         .ToList()); //consider as db call EF or Stored Procedure 
       dbFramesMerged.AddRange(
        dbsetModel_2.OrderBy(x => x.Id2) 
         .ThenBy(z => z.OrderNumber2) 
         .Skip(cursor2 * filter.Limit) 
         .Take(filter.Limit) 
         .Select(x => new Item() { criteria1 = x.Id2, criteria2 = x.OrderNumber2, model = x }) 
         .ToList()); 
      } 

      IQueryable<Item> qItems = dbFramesMerged.AsQueryable(); 
      PagedList<Item> result = new PagedList<Item>(); 
      result.AddRange(qItems.OrderBy(x => x.criteria1).ThenBy(z => z.criteria2).Skip(skipEndResult).Take(filter.Limit).ToList()); 

      //here again you need db cals to get total count 
      result.Total = dbsetModel_1.Count() + dbsetModel_2.Count(); 
      result.Limit = filter.Limit; 
      result.Page = filter.Page; 
      return result; 
     } 
    } 

    public class PagingFilter 
    { 
     public int Limit { get; set; } 
     public int Page { get; set; } 
    } 



    public class PagedList<T> : List<T> 
    { 

     public int Total { get; set; } 
     public int? Page { get; set; } 
     public int? Limit { get; set; } 
    } 

    public class Item : Criteria 
    { 
     public object model { get; set; } 
    } 

    public class Criteria 
    { 
     public int criteria1 { get; set; } 
     public int criteria2 { get; set; } 
     //more criterias if you need to order 
    } 

    public class model1 
    { 
     public int Id { get; set; } 
     public int OrderNumber { get; set; } 
     public string Name { get; set; } 
    } 

    public class model2 
    { 
     public int Id2 { get; set; } 
     public int OrderNumber2 { get; set; } 
     public string Name2 { get; set; } 
    } 
} 
相關問題