高性能分页 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 日:初始版本