C# 从多个源筛选、合并、排序和分页数据

C# 从多个源筛选、合并、排序和分页数据,c#,caching,merge,pagination,iqueryable,C#,Caching,Merge,Pagination,Iqueryable,目前,我正在通过一种方法从数据库中检索数据,该方法检索IQueryable,对其进行过滤、排序,然后对其进行分页(所有这些基本上都在数据库中),然后将结果返回到UI以在分页表中显示 我需要集成来自另一个数据库的结果,分页似乎是主要问题 模型相似但不完全相同(相同的字段、不同的名称,在返回之前需要映射到通用域模型) 不可能在DB级别加入 两个DBs之间目前约有1000条记录(在 过去18个月),并可能以大致相同的速度增长(缓慢) 步调 结果总是需要按1-2个字段排序(按日期) 我目前在这两种解

目前,我正在通过一种方法从数据库中检索数据,该方法检索
IQueryable
,对其进行过滤、排序,然后对其进行分页(所有这些基本上都在数据库中),然后将结果返回到UI以在分页表中显示

我需要集成来自另一个数据库的结果,分页似乎是主要问题

  • 模型相似但不完全相同(相同的字段、不同的名称,在返回之前需要映射到通用域模型)
  • 不可能在DB级别加入
  • 两个DBs之间目前约有1000条记录(在 过去18个月),并可能以大致相同的速度增长(缓慢) 步调
  • 结果总是需要按1-2个字段排序(按日期)
我目前在这两种解决方案之间左右为难:

  • 从两个源检索所有数据,合并、排序并缓存它们;然后,在接收请求时,只需对所述缓存进行过滤和分页,但当集合被修改时,我需要使缓存无效(我可以这样做)
  • 过滤每个源上的数据(同样,在DB级别),然后检索、合并、排序和分页,然后返回

  • 我正在寻找一种性能上合适的算法。理想的解决方案可能是两者的结合(缓存+在DB级别进行过滤),但我目前还没有考虑到这一点。

    我认为您可以使用以下算法。假设页面大小为10,则对于页面0:

  • 从数据库A获取10个结果,在db级别进行过滤和排序
  • 从数据库B获得10个结果,在数据库级别进行过滤和排序(与上述查询并行)
  • 将这两个结果结合起来,以正确的排序顺序获得10条记录。所以您有20条已排序的记录,但只取前10条并显示在UI中
  • 第1页:

  • 请注意,在上一步中,您曾经在UI中显示数据库A和B中的多少项。例如,您使用了数据库A中的2个项目和数据库B中的8个项目
  • 从数据库A中获取10个结果,进行过滤和排序,但从位置2开始(跳过2),因为这两个结果已经显示在UI中
  • 从数据库B获得10个结果,进行过滤和排序,但从位置8开始(跳过8)
  • 以与上述相同的方式合并,从20条记录中获得10条记录。假设现在您使用了A中的5项和B中的5项。现在,您总共显示了A中的7项和B中的13项。在下一步中使用这些数字
  • 这将不允许(轻松)跳过页面,但据我所知,这不是一个要求


    性能应该与查询单个数据库时完全相同,因为对A和B的查询可以并行完成。

    我在这里创建了一些东西,如果需要,我会回来解释。 我不确定我的算法是否适用于所有的边缘情况,它涵盖了我想到的所有情况,但你永远不知道。我将把代码留在这里让你高兴,我会回答并解释那里做了什么,如果你需要的话,请留下评论

    并使用值之间存在较大差距的项目列表执行多个测试

    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; }
        }
    }
    
    使用系统;
    使用System.Collections.Generic;
    使用System.Linq;
    命名空间控制台应用程序1
    {
    班级计划
    {
    //每次访问这些对象时,将其视为数据库调用
    私有静态可查询dbsetModel_1;
    私有静态可查询dbsetModel_2;
    私有静态void InitDBSets()
    {
    var rnd=新随机数();
    List dbsetModel1=新列表();
    List dbsetModel2=新列表();
    对于(int i=1;i<300;i++)
    {
    如果(i%2==0)
    {
    添加(newmodel1(){Id=i,OrderNumber=rnd.Next(1,10),Name=“Test”+i.ToString()});
    }
    其他的
    {
    添加(newmodel2(){Id2=i,OrderNumber2=rnd.Next(1,10),Name2=“Test”+i.ToString()});
    }
    }
    dbsetModel_1=dbsetModel1.AsQueryable();
    dbsetModel_2=dbsetModel2.AsQueryable();
    }
    公共静态void Main()
    {
    //生成数据库数据的排序
    InitDBSets();
    //试验
    var result2=GetPage(new PagingFilter(){Page=5,Limit=10});
    var result3=GetPage(new PagingFilter(){Page=6,Limit=10});
    var result5=GetPage(新分页过滤器(){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});
    }
    专用静态页面列表GetPage(PagingFilter筛选器)
    {
    int pos=0;
    //仅从两个数据库加载起始页和页边距
    //这部分需要在db one、skip、take上的存储过程中进行转换,以返回每帧的间隔起始值
    var framesBordersModel1=新列表();
    dbsetModel_1.OrderBy(x=>x.Id).ThenBy(z=>z.OrderNumber).ToList().ForEach(i=>{
    pos++;
    如果(位置-1==0)
    {
    framesBordersModel1.Add(新项(){criteria1=i.Id,criteria2=i.OrderNumber,model=i});
    }
    如果((位置-1)%filter.Limit==0),则为else
    {
    framesBordersModel1.Add(新项(){criteria1=i.Id,criteria2=i.OrderNumber,model=i});
    }
    });
    pos=0;
    //这一部分需要在dbtwo、skip、take上的存储过程中进行转换,以返回每个帧的间隔开始值
    var framesBordersModel2=新列表();
    dbsetModel_2.Ord