65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.50/5 (3投票s)

2021年3月16日

CPOL

3分钟阅读

viewsIcon

13956

downloadIcon

377

在 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, RegionCountry
SortColumn(排序列) 我们需要用哪一列对数据进行排序,例如,要按联系人排序,传递的值应为 ContactPerson
SortDirection(排序方向) 对于升序排序,应传递 Asc ,对于降序排序,应传递 Desc
HasPagination(是否有分页) 要将此查询生成的每条数据作为 json 返回,需要将其设置为 false, 否则应传递 true

Columns[](列[])

它是一个用于特定搜索的列的 json 数组列表,它包含 Column 对象的数组。
Column

此 json 对象具有两个属性,ColumnNameColumnSearchText, 这些搜索提供特定列搜索功能,例如

示例 1

  { 
       "ColumnName":"Balance",
       "ColumnSearchText":"<=,50"
  } 

它将返回余额小于或等于 50 的所有客户。

示例 2

   {
       "ColumnName":"Country",
       "ColumnSearchText":"Pakistan"
   }
                          

它将仅返回来自国家/地区 Pakistan 的所有客户

示例 3

您也可以进行多场景搜索,例如

   "Columns":[
   {
       "ColumnName":"Country",
       "ColumnSearchText":"Pakistan"
   }
   ,
   { 
        "ColumnName":"Balance",    
        "ColumnSearchText":"<=,50" 
   } 
]

上述 json 查询将返回余额小于或等于 50 且仅来自国家/地区 Pakistan 客户

通过 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 日:初始版本
© . All rights reserved.