高性能分页 REST API 查询构建器






3.50/5 (3投票s)
在 REST API 上实现的分页
引言
满足对大型数据源进行 REST API 记录分页的需求。因此,我们使用 REST API 提出了这个解决方案,希望它也能帮助您。本文是我的前一篇 datatable.net 分页文章的扩展,因此请不要指控我抄袭。其目的是执行特定和多列搜索、排序、动态页面大小调整、拥有优雅的代码结构、最少的编码工作,以及最重要的性能。
先决条件
为了理解这篇文章,您需要对 MVC 框架有所了解。如果您认为您有足够的专业知识,那么您就可以轻松地进一步阅读这篇文章。
抽象
该解决方案使用 Swagger 进行文档编写,并使用 LINQKit 包。 LINQKit 用于构建灵活且高性能的查询。
客户端请求
我们使用 Postman 来查询我们的 REST API,json 请求结构如下所示
{
  "CurrentPage": 1,
  "PageSize": 1,
  "SearchText": "",
  "SortColumn": "Id",
  "SortDirection": "Desc",
  "HasPagination": true,
  "Columns": [
  ]
}
	| 属性 | 描述 | 
| CurrentPage | 请求的页码应在此属性中定义,例如,要获取第 3 页的数组响应,则应将 3 分配给它。 | 
| PageSize | 它定义了返回的 arraylist的大小或请求的页面大小。 | 
| SearchText(搜索文本) | 要搜索所有文本列,应填充此属性,例如,abbot 将搜索此情况下的所有文本列, ContactPerson,Phone,City,Region和Country | 
| SortColumn(排序列) | 我们需要用哪一列对数据进行排序,例如,要按联系人排序,传递的值应为 ContactPerson | 
| SortDirection(排序方向) | 对于升序排序,应传递 Asc,对于降序排序,应传递Desc | 
| HasPagination(是否有分页) | 要将此查询生成的每条数据作为 json 返回,需要将其设置为 false, 否则应传递true | 
| 
 | 它是一个用于特定搜索的列的 json 数组列表,它包含 Column对象的数组。 | 
| Column | 此 json 对象具有两个属性, 示例 1   { 
       "ColumnName":"Balance",
       "ColumnSearchText":"<=,50"
  } 它将返回余额小于或等于  示例 2    {
       "ColumnName":"Country",
       "ColumnSearchText":"Pakistan"
   }
                          它将仅返回来自国家/地区  示例 3 您也可以进行多场景搜索,例如    "Columns":[
   {
       "ColumnName":"Country",
       "ColumnSearchText":"Pakistan"
   }
   ,
   { 
        "ColumnName":"Balance",    
        "ColumnSearchText":"<=,50" 
   } 
]上述 json 查询将返回余额小于或等于  | 
通过 Postman 客户端使用 REST API
要使用此 REST API,我们使用 postman。您可以从他们的网站下载它。打开客户端后,将请求类型设置为 Post, 在 body 选项卡中,选择 raw ,如下所示,并将请求内容类型设置为 JSON,位于 组合框 的最右侧。

上述 API 请求返回余额大于 5000000 且分页在第一页的客户。

快照显示来自国家/地区 oman 的所有客户。

上述快照演示了按客户 ID 编号降序排列的记录。
REST API 服务器端
在服务器端,我们有 LINQKit 来使我们的愿望成为可能。其中一个控制器操作方法设置 PaginationRequest 对象,该对象将由客户端传递,作为我们在上述示例中定义的 json 对象。
    [HttpPost]
    [SwaggerResponseExample(HttpStatusCode.OK, typeof(CustomerPaginationResponseExample))]
    [SwaggerRequestExample(typeof(PaginationRequest<customerpaginationgridcolumns>), 
    typeof(CustomerPaginationRequestExample), jsonConverter: typeof(StringEnumConverter))]
    [ResponseType(typeof(PaginationResponse<customerpaginationmodel>))]
    [HelperMethods.DeflateCompression]
    [ValidateModelState]
    [CheckModelForNull]
    [SwaggerConsumes("application/json")]
    [SwaggerProduces("application/json")]
    [SwaggerResponse(HttpStatusCode.NotFound, 
    "No customer found", typeof(GenericResponseModel))]
    [Route("")]
    public async Task<system.web.http.ihttpactionresult> 
           Get(PaginationRequest<customerpaginationgridcolumns> request)
    {
        BusinessLayer.Entity.Customer obj = new BusinessLayer.Entity.Customer(this);
        PaginationResponse<customerpaginationmodel> response = obj.Get(request).Result;
        if (response.Items == null)
        {
            return APIResponse(HttpStatusCode.InternalServerError, 
                               $"Error: {obj.errorMessage}");
        }
        else
        if (response.Items.Count() == 0)
        {
            return APIResponse(HttpStatusCode.NotFound, $"No customer found");
        }
        return Ok(response);
    }
在深入研究我们请求的详细业务层方法时,其结构如下所示。它使用 LINQKit 和 Entity Framework 作为我们的 ORM 动态地构建查询。它将所有列解析为与此业务实体关联的列,并动态地组装查询并同时实现分页。
    public async Task<paginationresponse<customerpaginationmodel>> 
    Get(PaginationRequest<common.enum.customerpaginationgridcolumns> paginationRequest)
        {
            try
            {
                BusinessEntity.CustomerDBEntities obj = 
                               new BusinessEntity.CustomerDBEntities();
                records = (from cus in obj.customers.AsNoTracking()
                           // join count in obj.countries on 
                           // cus.countryId equals count.countryId
                           select new CustomerPaginationModel
                           {
                               Id = cus.customerID,
                               ContactPerson = cus.contactPerson,
                               Phone = cus.phone,
                               Fax = cus.phone,
                               City = cus.city,
                               Region = cus.region,
                               Country = cus.countryName,
                               CountryId = cus.countryId,
                               Balance = cus.balance
                           }).AsQueryable();
                if (paginationRequest.SortColumn != CustomerPaginationGridColumns.None)
                {
                    InitSorting(paginationRequest);
                }
                else
                {
                    paginationRequest.SortColumn = CustomerPaginationGridColumns.Id;
                    InitSorting(paginationRequest);
                }
                genericSearchText = paginationRequest.SearchText == null ? 
                                    null : paginationRequest.SearchText.Trim(); // set generic
                                                                                // search value
                ColumnParameter<common.enum.customerpaginationgridcolumns> column = 
                                     new ColumnParameter<customerpaginationgridcolumns>() { };
                // Iterate through filter grid column to construct query predicate
                // foreach (ColumnParameter<common.enum.customerpaginationgridcolumns> 
                // column in paginationRequest.Columns)
                foreach (CustomerPaginationGridColumns columnParse in Enum.GetValues
                        (typeof(CustomerPaginationGridColumns)))
                {
                   
                    if (!string.IsNullOrEmpty(genericSearchText))
                    {
                        // these is no specific column search
                        if (paginationRequest.Columns.Where
                           (x => x.ColumnName == columnParse).Count() == 0)
                        {
                            column = new ColumnParameter<customerpaginationgridcolumns>() 
                                      { ColumnName = columnParse, ColumnSearchText = "" };
                        }
                        else
                        {
                            column = paginationRequest.Columns.Where
                                     (x => x.ColumnName == columnParse).FirstOrDefault();
                        }
                    }
                    else
                    {
                        column = paginationRequest.Columns.Where
                                 (x => x.ColumnName == columnParse).FirstOrDefault();
                    }
                    if (column == null)
                    {
                        continue;
                    }
                    searchColumnText = 
                    (column.ColumnSearchText ?? "").Trim();      // set current column 
                                                                 // search value
                    switch (column.ColumnName)
                    {
                        case Common.Enum.CustomerPaginationGridColumns.Balance:
                            EvaluateNumericComparisonFilter(paginationRequest, column,
                               searchColumnText,
                               "Balance",
                               x => x.Balance
                           );
                            break;
                        case Common.Enum.CustomerPaginationGridColumns.City:
                            EvaluateFilter(paginationRequest, column,
                                x => x.City.StartsWith(searchColumnText),
                                x => x.City.StartsWith(genericSearchText),
                                x => x.City
                                );
                            break;
                        case Common.Enum.CustomerPaginationGridColumns.ContactPerson:
                            EvaluateFilter(paginationRequest, column,
                                x => x.ContactPerson.StartsWith(searchColumnText),
                                x => x.ContactPerson.StartsWith(genericSearchText),
                                x => x.ContactPerson
                                );
                            break;
                        case Common.Enum.CustomerPaginationGridColumns.Country:
                            EvaluateFilter(paginationRequest, column,
                                x => x.Country.StartsWith(searchColumnText),
                                x => x.Country.StartsWith(genericSearchText),
                                x => x.Country
                                );
                            break;
                        case Common.Enum.CustomerPaginationGridColumns.CountryId:
                            if (!IsNumber(searchColumnText))
                            {
                                continue;
                            }
                            string type = searchColumnText;
                            EvaluateFilter(paginationRequest, column,
                                x => x.CountryId == type,
                                null,
                                x => x.CountryId
                                );
                            break;
                        case Common.Enum.CustomerPaginationGridColumns.Fax:
                            EvaluateFilter(paginationRequest, column,
                                x => x.Fax.StartsWith(searchColumnText),
                                x => x.Fax.StartsWith(genericSearchText),
                                x => x.Fax
                                );
                            break;
                        case Common.Enum.CustomerPaginationGridColumns.Phone:
                            EvaluateFilter(paginationRequest, column,
                                x => x.Phone.StartsWith(searchColumnText),
                                x => x.Phone.StartsWith(genericSearchText),
                                x => x.Phone
                                );
                            break;
                        case Common.Enum.CustomerPaginationGridColumns.Region:
                            EvaluateFilter(paginationRequest, column,
                                x => x.Region.StartsWith(searchColumnText),
                                x => x.Region.StartsWith(genericSearchText),
                                x => x.Region
                                );
                            break;
                    }
                }
                PaginationResponse<customerpaginationmodel> response = 
                                     new PaginationResponse<customerpaginationmodel>();
                IQueryable<customerpaginationmodel> countQuery = records;
                response.Items = ForgeGridData(paginationRequest, x => x.ContactPerson).Result;
                response.RecordsTotal = totalRows;
                // Generating data
                return response;
            }
            catch (Exception exp)
            {
                CompileExceptionHandleMessage(exp);
                return new PaginationResponse<customerpaginationmodel>() { Items = null };
            }
            finally
            {
                records = null;
            }
        }
解决方案出错时
如果解决方案源代码拒绝编译,请重新构建解决方案,之后,如果出现运行时错误,请在您的主项目的包管理器控制台中运行以下命令,然后一切正常。
Update-Package Microsoft.CodeDom.Providers.DotNetCompilerPlatform -r
历史
- 2021 年 3 月 16 日:初始版本


